目錄:
別名是啥玩意?
方法1:反向注冊
方法2:嵌入?yún)R編代碼
小結(jié)
在stackoverflow上看到一個有趣的話題:如何給一個變量設(shè)置一個別名?(How to assign to a variable an alias?)
所謂的變量別名,就是通過通過不同的標(biāo)識符,來表示同一個變量。
我們知道,變量名稱是給程序員使用的。
在編譯器的眼中,所有的變量都變成了地址。
注意:這里所討論的別名,僅僅是通過不同的標(biāo)識符來引用同一個變量。
與強(qiáng)符號、弱符號的概念沒有任何關(guān)系,那是另一個話題。
在上面這個帖子中,作者首先想到的是通過宏定義,對變量進(jìn)行重新命名。
這樣的做法,將會在編譯之前的預(yù)處理環(huán)節(jié),把宏標(biāo)識符替換為變量標(biāo)識符。
在網(wǎng)友回復(fù)的答案中,大部分都是通過指針來實(shí)現(xiàn):讓不同的標(biāo)識符指向同一個變量。
不管怎么說,這也算是一種別名了。
但是,這些答案有一個局限:這些代碼必須一起進(jìn)行編譯才可以,否則就可能出現(xiàn)無法找到符號的錯誤信息。
現(xiàn)在非常流行插件編程,如果開發(fā)者想在插件中通過一個變量別名來引用主程序中的變量,這該如何處理呢?
本文提供兩個方法來實(shí)現(xiàn)這個目的,并通過兩個簡單的示例代碼來進(jìn)行演示。
之前我接觸過一些CodeSys的代碼,里面的代碼質(zhì)量真的是非常的高,特別是軟件架構(gòu)設(shè)計(jì)部分。
傳說:CodySys 是工控界的 Android。
其中有個反向注冊的想法,正好可以用在變量別名上面。
示例代碼中一共有 2 個文件:main.c和plugin.c。
main.c中定義了一個全局變量數(shù)組,編譯成可執(zhí)行程序main。
plugin.c中通過一個別名來使用main.c中的全局變量。
plugin.c被編譯成一個動態(tài)鏈接庫,被可執(zhí)行程序main動態(tài)加載(dlopen)。
在plugin.c中,提供一個函數(shù)func_init,當(dāng)動態(tài)庫被maindlopen之后,這個函數(shù)就被調(diào)用,并且把真正的全局變量的地址通過參數(shù)傳入。
這樣的話,在插件中就可以通過一個別名來使用真正的變量了(比如:修改變量的值)。
本質(zhì)上,這仍然是通過指針來進(jìn)行引用。
只不過利用動態(tài)注冊的思想,把指針與變量的綁定關(guān)系在時間和空間上進(jìn)行隔離。
#includeint *alias_data = NULL; void func_init(int *data) { printf("libplugin.so: func_init is called. \n"); alias_data = data; } void func_stage1(void) { printf("libplugin.so: func_stage1 is called. \n"); if (alias_data) { alias_data[0] = 100; alias_data[1] = 200; } }
#include#include #include // defined in libplugin.so typedef void (*pfunc_init)(int *); typedef void (*pfunc_stage1)(void); int data[100] = { 0 }; void main(void) { data[0] = 10; data[1] = 20; printf("data[0] = %d \n", data[0]); printf("data[1] = %d \n", data[1]); // open libplugin.so void *handle = dlopen("./libplugin.so", RTLD_NOW); if (!handle) { printf("dlopen failed. \n"); return; } // get and call init function in libplugin.so pfunc_init func_init = (pfunc_init) dlsym(handle, "func_init"); if (!func_init) { printf("get func_init failed. \n"); return; } func_init(data); // get and call routine function in libplugin.so pfunc_stage1 func_stage1 = (pfunc_stage1) dlsym(handle, "func_stage1"); if (!func_stage1) { printf("get func_stage1 failed. \n"); return; } func_stage1(); printf("data[0] = %d \n", data[0]); printf("data[1] = %d \n", data[1]); return; }
編譯指令如下:
gcc -m32 -fPIC --shared plugin.c -o libplugin.so gcc -m32 -o main main.c -ldl
執(zhí)行結(jié)果:
data[0] = 10 data[1] = 20 libplugin.so: func_init is called. libplugin.so: func_stage1 is called. data[0] = 100 data[1] = 200
可以看一下動態(tài)鏈接庫的符號表:
readelf -s libplugin.so | grep data
可以看到alias_data標(biāo)識符,并且是在本文件中定義的全局變量。
在動態(tài)加載的插件中使用變量別名,除了上面演示的動態(tài)注冊的方式,還可以通過嵌入?yún)R編代碼來: 設(shè)置一個全局標(biāo)號來實(shí)現(xiàn)。
直接上示例代碼:
#includeasm(".Global alias_data"); asm("alias_data = data"); extern int alias_data[]; void func_stage1(void) { printf("libplugin.so: func_stage1 is called. \n"); *(alias_data + 0) = 100; *(alias_data + 1) = 200; }
#include#include #include // defined in libplugin.so typedef void (*pfunc_init)(int *); typedef void (*pfunc_stage1)(void); int data[100] = { 0 }; void main(void) { data[0] = 10; data[1] = 20; printf("data[0] = %d \n", data[0]); printf("data[1] = %d \n", data[1]); // open libplugin.so void *handle = dlopen("./libplugin.so", RTLD_NOW); if (!handle) { printf("dlopen failed. \n"); return; } // get and call routine function in libplugin.so pfunc_stage1 func_stage1 = (pfunc_stage1) dlsym(handle, "func_stage1"); if (!func_stage1) { printf("get func_stage1 failed. \n"); return; } func_stage1(); printf("data[0] = %d \n", data[0]); printf("data[1] = %d \n", data[1]); return; }
編譯指令:
gcc -m32 -fPIC --shared plugin.c -o libplugin.so gcc -m32 -rdynamic -o main main.c -ldl
執(zhí)行結(jié)果:
data[0] = 10 data[1] = 20 libplugin.so: func_stage1 is called. data[0] = 100 data[1] = 200
也來看一下libplugin.so中的符號信息:
readelf -s libplugin.so | grep data
這篇文檔通過兩個示例代碼,討論了如何在插件中(動態(tài)鏈接庫),通過別名來訪問真正的變量。
不知道您會不會有這樣的疑問:直接使用extern來聲明一下外部定義的變量不就可以了,何必這么麻煩?
道理是沒錯!
但是,在一些比較特殊的領(lǐng)域或場景中(比如一些二次開發(fā)中),這樣的需求是的確存在的,而且是強(qiáng)需求。
如果你有任何疑問,或者文中有任務(wù)錯誤,歡迎留言討論、指正