C語言實現反射


高級語言的反射機制,簡單來說就是可以通過字符串型獲取對應的類或者函數。

  1. 基礎形式,c語言結構化編程基礎實現
    1)聲明

    typedef void (*callback)(void);

    typedef struct {
    const char *name;
    callback fn;
    }callback_t;

    void f0();
    void f1();
    callback_t callbacks[] = {
    { "cmd0", f0},
    {"cmd1", f1},
    }

    void f0()
    {
    }

    void f1()
    {
    }

2)調用

void do_callback(const char *name) 
{
    size_t i;
    for (i = 0; i < sizeof(callbacks) / sizeof(callbacks[0]); i++) {
        if (!strcmp(callbacks[i].name, name)) {
            return callbacks[i].fn();
       }
    }
}

這種方式的不便之處在於,當需要映射的函數因分散在不同文件時,每增加一個新的映射都需要修改這個數組,以及頭文件。

2 利用自定義段
gcc支持通過使用__attribute__((section())),將函數、變量放到指定的數據段中。
也就是說,可以讓編譯器幫我們完成1中向數組添加成員的動作。
借助此機制,回調函數可以在任意文件申明,不需要修改其他文件。

雖然成員可以自動添加到段中,但由於分散在不同文件中定義,由於文件編譯順序不確定,無法直接得知段的起始結束地址,也就無法實現遍歷。

  1. 方式一
    棄用gcc默認的分散加載文件,使用自己的分散加載文件,並在其中顯示聲明自定義段的起始結束地址。
    聲明時需要注意地址對齊,由於結構中保存有函數地址,因此需要按CPU字長對齊
    如32bit cpu, 需要4字節對齊;64位需要8字節對齊。
    分散加載文件可以通過ld --verbose 獲取到
    gcc的編譯參數需要增加-T選項,聲明使用指定的分散加載文件
    如使用修改過的custom.lds文件
    gcc xxx.c -o xxx -Tcustom.lds

方式一的缺陷在於,需要修改分散加載文件,需要修改Makefile,如果開發人員沒有足夠權威應該是不允許做這種操作的。

2)方式二
在結構中增加標志位,如:

typedef struct {
    size_t magic1;
    size_t magic2;
    const char *name;
    callback fn;
}callback_t;

#define CALLBACK __attribute__((section(".callback"), used))

並聲明一個用於定位的flag結構。

CALLBACK  callback_t flag;
#define MAGIC1 0xa0a0a0a0
#define MAGIC2 0x0a0a0a0a

// 以下兩個變量用於記錄段起始結束地址
callback_t *start = &flag;
callback_t *end = &flag;

定義一個初始化函數,用於確定段的起始結束地址

void callback_init();

該函數的原理是,
i) 從flag的地址往低地址遍歷,如果magic值校驗正確則更新起始地址,重復直到校驗失敗

while (1) {
    callback_t *tmp = start - 1;
    if (tmp->magic1 == MAGIC1 && tmp->magic2 == MAGIC2) {
        start--;
    } else {
        break;    
    }
}

ii) 從flag的地址往高地址遍歷,如果magic值校驗正確則更新結束地址,重復直到校驗失敗

while (1) {
    callback_t *tmp = end + 1;
    if (tmp->magic1 == MAGIC1 && tmp->magic2 == MAGIC2) {
        end++;
    } else {
        break;    
    }
}

到這里,自定義段的地址范圍可以確定了。

void do_callback(const char *name) 
{
    size_t i;
    callback_t *tmp = start;

    while (tmp != end) {
        // 注意跳過flag哦
        if (tmp != &flag && !strcmp(tmp->name, name)) {
            return tmp->fn();
       }
       tmp++;
    }
}

然而還有一個問題,callback_init()需要手動調用。

抱歉,讓我手動調用是不可能的,想也不要想。
一是懶
二是這么個東西放哪都覺着別扭,破壞代碼結構,影響美感。

怎么解決?
很簡單,還是利用gcc的擴展數據,給callback_init加上__attribute__((constructor))
這樣,在main函數前,callback_init就會自動調用完成初始化。

思路提供給大家了,具體代碼靠大家自己實現咯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM