__attribute__ 是gcc编译器支持的一个编译特性(arm编译器也支持此特性,比如我们常用的keil就是用的ARMGCC编译器),也就是通过给函数或者变量声明属性值,以便让编译器能够对要编译的程序进行优化处理。
更多详细内容,请看这篇官网文档:《Unixwiz.net - Software Consulting Central -- Using GNU C __attribute__》:http://www.unixwiz.net/techtips/gnu-c-attributes.html而对于 section 这个关键字,我们可以通过它将指定的变量定义到指定的输入段中。keil中对于此用法的描述是:ARM Compiler v5.06 for uVision armcc User Guide
section 属性指定变量必须放置在特定数据部分中,通常,ARM 编译器将其生成的对象放在 .data 和 .bss 等部分中。但是,您可能需要其他数据部分,或者您可能希望变量出现在特殊部分中,例如,映射到特殊硬件。
如果使用 section 属性,则只读变量将放置在 RO 数据部分中,读写变量将放置在 RW 数据部分中,除非您使用 zero_init 属性。在这种情况下,变量放置在 ZI 部分中。
/* in RO section */
const int descriptor[3] __attribute__((section ("descr"))) = { 1,2,3 };
/* in RW section */
long long rw_initialized[10] __attribute__((section ("INITIALIZED_RW"))) = {5};
/* in RW section */
long long rw[10] __attribute__((section ("RW")));
/* in ZI section */
long long altstack[10] __attribute__((section ("STACK"), zero_init));
先来看一段代码(摘自CSDN,如有侵权,联系删除):
#include
#define SEC __attribute__((__section__("ss"), aligned(sizeof(void *))))
void func_1(int a, int b)
{
printf("%s %d %d\n", __func__, __LINE__, a + b);
}
void func_2(int a, int b)
{
printf("%s %d %d\n", __func__, __LINE__, a * b);
}
// 编译器会自动提供__start_ss,__stop_ss标志段ss的起止地址
extern size_t __start_ss;
extern size_t __stop_ss;
typedef struct
{
void (*p)(int, int);
} node_t;
// 结构体变量a位于自定义段ss
SEC node_t a = {
.p = func_1,
};
SEC node_t b = {
.p = func_2,
};
int main(int argc, char **argv)
{
int a = 3, b = 4;
node_t *p;
// 遍历段ss,执行node_t结构中的p指向的函数
for (p = (node_t *)&__start_ss; p < (node_t *)&__stop_ss; p++)
{
p->p(a, b);
a += 1;
b += 2;
}
}
来看一下运行的结果:
1、section关键字可以将变量定义到指定的输入段中,
#define SECTION(level) __attribute__((used,__section__(".fn_cmd."level)))
#define CMD_START_EXPORT(func,func_s) const struct CMD_LIST cmd_fn_##func SECTION("0.end") = {func,func_s}
CMD_START_EXPORT(start_fun,"start_fun");
const struct CMD_LIST cmd_fn_##func SECTION("0.end") = {func,func_s}
const struct CMD_LIST cmd_fn_func SECTION("0.end") = {start_fun,start_fun}
const struct CMD_LIST cmd_fn_func __attribute__((used,__section__(".fn_cmd.""0.end"))) = {start_fun,start_fun}
这个时候再来看会发现其实就是定义了一个struct CMD_LIST 类型的变量,变量的名字是cmd_fn_start_fun,并且这个变量被放到了我们所希望的一个输入段.fn_cmd.0.end中了
typedef void (*fun)();
struct CMD_LIST
{
fun funs;
const INT8 *cmd;
};
2、使用section将变量放到我们自定义的输入段中有什么意义呢?
#define SECTION(level) __attribute__((used,__section__(".fn_cmd."level)))
#define CMD_START_EXPORT(func,func_s) const struct CMD_LIST cmd_fn_##func SECTION("0.end") = {func,func_s}
#define CMD_EXPORT(func,func_s) const struct CMD_LIST cmd_fn_##func SECTION("1") = {func,func_s}
#define CMD_END_EXPORT(func,func_s) const struct CMD_LIST cmd_fn_##func SECTION("1.end") = {func,func_s}
当这几个宏被调用时将会产生名为cmd_fn_xx的变量,并且这个变量根据被调用的宏来把这个变量放到相应的输入段。例如CMD_START_EXPORT这个宏,这个宏其实上面已经讲过了, 调用这个宏的时候会产生一个名为cmd_fn_xx的变量,并且把这个变量放到了我们自定义的输入段.fn_cmd.0.end中了。
其他两个宏的其实也是差不多的,不同之处就是输入段有些区别。言归正传,现在继续来讲如何使用section将不同的函数放到我们想要的输入段中,并且获得他们的起始地址和结束地址。
我们可以在每个XXX_Init函数后面都调用宏CMD_EXPORT,在调用这个宏时编译器会将XXX_init这个函数加入到输入段中,由于变量在输入段中的地址是连续的,并且顺序先按 section 名 01234排一遍,section 内再按函数名称排。
所以可以按照输入段中顺序来逐个调用这些初始化函数来完成系统的初始化。具体实现我会根据我的一个自定义命令行的应用来进行部分的说明。
/*命令函数段起始位置*/
int cmd_start(void)
{
return 0;
}
CMD_START_EXPORT(cmd_start,"int cmd_start(void)");
/*命令函数段结束位置*/
int cmd_end(void)
{
return 0;
}
CMD_END_EXPORT(cmd_end,"int cmd_end(void)");
void test(void)
{
printf("hello world\r\n");
}
CMD_EXPORT(test,"void test(void)");
void demo(void)
{
printf("hello world\r\n");
}
CMD_EXPORT(demo,"void demo(void)");
先定义start和end函数并且分别使用CMD_START_EXPORT和CMD_END_EXPORT来将其放到输入段.fn_cmd.0.end和.fn_cmd.1.end中,按照上面的说明输入段.fn_cmd.0.end是排在输入段.fn_cmd.1.end前面的,而使用的CMD_EXPORT这个宏对应的输入段.fn_cmd.1是排在.fn_cmd.0.end和.fn_cmd.1.end之间的,这里可以看一下编译产生的.map会更加的形象一些。具体在MAP文件的位置如下所示
cmd_fn_cmd_start 0x080042f0 Data 8 serialcmd.o(.fn_cmd.0.end)
cmd_fn_test 0x080042f8 Data 8 application.o(.fn_cmd.1)
cmd_fn_demo 0x08004300 Data 8 application.o(.fn_cmd.1)
cmd_fn_cmd_end 0x08004308 Data 8 serialcmd.o(.fn_cmd.1.end)
typedef void (*fun)();
struct CMD_LIST
{
fun funs;
const INT8 *cmd;
};
const static struct CMD_LIST *CmdList;
static UINT8 CmdSize = 0;
/*命令函数初始化*/
void SerialCmdInit(void)
{
const struct CMD_LIST *cmd_ptr;
CmdList = &cmd_fn_cmd_start;
CmdList++;
for (cmd_ptr = CmdList; cmd_ptr < &cmd_fn_cmd_end;cmd_ptr++)
{
/*这里如果用于初始化的话可以使用下面这种方式来执行初始化函数,因为我的应用并不是用于初始
化,所以就没有进行函数调用。
(*cmd_ptr->fun)();
*/
CmdSize++;
}
}
其中cmd_fn_cmd_start是在.fn_cmd.0.end这个输入段中的,而各个要执行的函数是在.fn_cmd.1这个输入段中的,cmd_fn_cmd_end作为结束的标志在.fn_cmd.1.end输入段中。
其余的就不做过多讲解了,从上面的.map文件中的地址很容易就可以看出在得到了起始地址和结束地址之后怎样一个个遍历这些函数。
然而上述功能只能对 GCC 平台有效, 如果是 ARMCC 或是其他平台, 因为编译器不同, 方法可能不一样, 为了跨平台, 就不得不添加平台检测的宏, 比如将下面的代码替换获取 myfun_section 所在的内存区间部分即可支持 ARMCC 平台。
#ifdef __ARMCC_VERSION /* ARM C Compiler */
extern test_command_t myfun_section$$Base;
extern test_command_t myfun_section$$Limit;
test_command_t *myfun_section_begin = &(myfun_section$$Base);
test_command_t *myfun_section_end = &(myfun_section$$Limit);
#elif defined (__GNUC__)
extern test_command_t __start_myfun_section;
extern test_command_t __stop_myfun_section;
test_command_t *myfun_section_begin = &__start_myfun_section;
test_command_t *myfun_section_end = &__stop_myfun_section;
#else
#error "The platform is not supported"
#endif
以上就是关于attribute(section)在GCC和ARMGCC中的使用,下章节小飞哥会带大家如何利用这个功能实现代码的分模块设计,值得期待,欢迎关注小飞哥