C語言中的 __attribute__宏之section屬性
文章目錄
- C語言中的 __attribute__宏之section屬性
- 一、起因
- 二、解釋
前言
無論是GNU還是ARM的編譯器, 都支持 __attribute__所指定的編譯屬性,這里著重講解一下在KEIL 環境下__attribute__中的section的使用方法,
一、起因
我們先來看一個宏
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
這是RT_Thread里面的一個宏,我是在移植RT-Thread到W601上遇到的,因為在MDK中除錯,之前所有都在掌握之中,突然程式跳到rt_components_board_init();中,突然看到一串像亂碼的代碼,如下:
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
………………
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
#endif
}
不重要的部分省略了,看到**__rt_init_rti_board_start**和 __rt_init_rti_board_end東西一臉懵,跟蹤代碼進去看看,結果停在 INIT_EXPORT(rti_board_start, “0.end”);處,還是一臉懵,再追蹤INIT_EXPORT,最終現實這個宏定義:
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
在追中 SECTION , 結果是#define SECTION(x) __attribute__((section(x)))
所以就有了這篇文章,
二、解釋
經過查找資料
section關鍵字可以將變數定義到指定的輸入段中,比如 :
int test __attribute__((section("show"))) = 0;
這句話的意思是把整形變數test放到一個名為show的輸入段中,
下面我把RT-Thread中的代碼截取下來分析:
typedef int (*init_fn_t)(void);
#define RT_USED __attribute__((used)) //標記為attribute__((used))的函式被標記在目標檔案中,以避免聯結器洗掉未使用的節,
#define SECTION(x) __attribute__((section(x)))
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
差點忘了,在宏定義中兩個##號相當于無縫連接的意思,比如
#define DEF_INT(a,b) int a##b = 0
DEF_INT(a,b);
//展開結果就是
int ab = 0;
下面我們就把下面的宏展開:
INIT_EXPORT(rti_board_start, "0.end");
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
//一步步來
__attribute__((used)) const init_fn_t __rt_init_rti_board_start __attribute__((section(x)))
__attribute__((used)) const init_fn_t __rt_init_rti_board_start __attribute__((section(".rti_fn.""0.end"))) = rti_board_start
//可以看成:
const init_fn_t __rt_init_rti_board_start __attribute__((section(".rti_fn.""0.end"))) = rti_board_start
那么問題來了,使用section將變數放到我們自定義的輸入段中有什么意義呢?
section先事先將所有的初始化函式加入到我們自己定義的輸入段中,然后再在main函式中將這個輸入段中初始化函式依次取出,這樣就可在不修改main函式的前提下完成對系統的初始化了,我想這也是RT-Thread這樣做的目的吧,
那么section是怎么將這些初始化函式放入輸入段中,并且系統還可以獲取這些初始化函式的地址呢?
其實上面已經講過了,呼叫INIT_EXPORT(fn, level)這個宏的時候會產生一個名為__rt_init_xx的變數,并且把這個變數放到了我們自定義的輸入段,而level其實就是段名的后綴名,前面是前面是**.rti_fn.** ,所以段名就是**.rti_fn.**+level
所以我們只需要在需要的函式后面使用這個宏,在呼叫這個宏之后,預編譯器會將名為__rt_init__xxx的函式加入到我們自己定義的輸入段中,由于變數在輸入段中的地址是連續的,并且順序先按 section 名排一遍,section 內再按函式名稱排,
下面我就來寫個測驗:
/*命令函式段起始位置*/
static int cmd_start(void)
{
return 0;
}
INIT_EXPORT(cmd_start, "0");
static int testhello(void)
{
printf("hello world\r\n");
return 0;
}
INIT_EXPORT(testhello, "1");
static int demo(void)
{
printf("hello world\r\n");
return 0;
}
INIT_EXPORT(demo, "1");
/*命令函式段結束位置*/
static int cmd_end(void)
{
return 0;
}
INIT_EXPORT(cmd_end, "1.end");
int test(void)
{
return 0;
}
__attribute__((used)) const init_fn_t __rt_init_test __attribute__((section(".rti_fn.""0.end"))) = test; //這個我就直接寫,不用宏

編譯只有連Warning都沒有哈……
下面打開.map檔案查看,為了方便找到,我們直接Ctrl + C 復制我們自己定義的函式變數名__rt_init_test,在.map檔案中Ctrl + F,如圖:

可以看到,其實就是按照段名進行排序的,然后段內在進行排序,
其中函式地址每次加4是因為我們的變數其實是個函式指標型別,所以是4個位元組,
所以也就可以接上第一眼看到想亂碼的東西了,
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
………………
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
#endif
}
這不就是在我們自己定義的段內回圈呼叫我們的函式嘛!
蕪湖~~
豁然開朗!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/294295.html
標籤:其他
上一篇:什么是歸并排序?
下一篇:物聯網設備上云小demo_02
