1. C語言:如何給全局變量起一個(gè)別名?

        共 4471字,需瀏覽 9分鐘

         ·

        2022-05-27 00:43

        作 ?者:道哥,10+年嵌入式開發(fā)老兵,專注于:C/C++、嵌入式、Linux

        關(guān)注下方公眾號(hào),回復(fù)【書籍】,獲取 Linux、嵌入式領(lǐng)域經(jīng)典書籍;回復(fù)【PDF】,獲取所有原創(chuàng)文章( PDF 格式)。

        目錄

        • 別名是啥玩意?

        • 方法1:反向注冊(cè)

        • 方法2:嵌入?yún)R編代碼

        • 小結(jié)

        別人的經(jīng)驗(yàn),我們的階梯!

        別名是啥玩意?

        stackoverflow上看到一個(gè)有趣的話題:如何給一個(gè)變量設(shè)置一個(gè)別名?(How to assign to a variable an alias?

        所謂的變量別名,就是通過通過不同的標(biāo)識(shí)符,來表示同一個(gè)變量。

        我們知道,變量名稱是給程序員使用的。

        編譯器的眼中,所有的變量都變成了地址。

        請(qǐng)注意:這里所討論的別名,僅僅是通過不同的標(biāo)識(shí)符來引用同一個(gè)變量。

        與強(qiáng)符號(hào)、弱符號(hào)的概念沒有任何關(guān)系,那是另一個(gè)話題。

        在上面這個(gè)帖子中,作者首先想到的是通過宏定義,對(duì)變量進(jìn)行重新命名。

        這樣的做法,將會(huì)在編譯之前的預(yù)處理環(huán)節(jié),把宏標(biāo)識(shí)符替換為變量標(biāo)識(shí)符。

        在網(wǎng)友回復(fù)的答案中,大部分都是通過指針來實(shí)現(xiàn):讓不同的標(biāo)識(shí)符指向同一個(gè)變量。

        不管怎么說,這也算是一種別名了。

        但是,這些答案有一個(gè)局限:這些代碼必須一起進(jìn)行編譯才可以,否則就可能出現(xiàn)無法找到符號(hào)的錯(cuò)誤信息。

        現(xiàn)在非常流行插件編程,如果開發(fā)者想在插件中通過一個(gè)變量別名來引用主程序中的變量,這該如何處理呢?

        本文提供兩個(gè)方法來實(shí)現(xiàn)這個(gè)目的,并通過兩個(gè)簡(jiǎn)單的示例代碼來進(jìn)行演示。

        文末有示例代碼的下載地址。

        方法1:反向注冊(cè)

        之前我接觸過一些CodeSys的代碼,里面的代碼質(zhì)量真的是非常的高,特別是軟件架構(gòu)設(shè)計(jì)部分。

        傳說:CodySys 是工控界的 Android。

        其中有個(gè)反向注冊(cè)的想法,正好可以用在變量別名上面。

        示例代碼中一共有 2 個(gè)文件:main.cplugin.c。

        main.c中定義了一個(gè)全局變量數(shù)組,編譯成可執(zhí)行程序main

        plugin.c中通過一個(gè)別名來使用main.c中的全局變量。

        plugin.c被編譯成一個(gè)動(dòng)態(tài)鏈接庫(kù),被可執(zhí)行程序main動(dòng)態(tài)加載(dlopen)。

        plugin.c中,提供一個(gè)函數(shù)func_init,當(dāng)動(dòng)態(tài)庫(kù)被main dlopen之后,這個(gè)函數(shù)就被調(diào)用,并且把真正的全局變量的地址通過參數(shù)傳入

        這樣的話,在插件中就可以通過一個(gè)別名來使用真正的變量了(比如:修改變量的值)。

        本質(zhì)上,這仍然是通過指針來進(jìn)行引用。

        只不過利用動(dòng)態(tài)注冊(cè)的思想,把指針與變量的綁定關(guān)系在時(shí)間和空間上進(jìn)行隔離。

        plugin.c 源文件

        #include 

        int *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;
        }
        }

        main.c 源文件

        #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

        可以看一下動(dòng)態(tài)鏈接庫(kù)的符號(hào)表:

        readelf -s libplugin.so | grep data

        可以看到alias_data標(biāo)識(shí)符,并且是在本文件中定義的全局變量。

        【關(guān)于作者】

        號(hào)主:道哥,十多年的嵌入式開發(fā)老兵,專注于嵌入式 + Linux 領(lǐng)域,玩過單片機(jī)、搞過智能家居、研究過 PLC工業(yè)機(jī)器人,項(xiàng)目開發(fā)經(jīng)驗(yàn)非常豐富。

        他的文章主要包括 C/C++、Linux操作系統(tǒng)、物聯(lián)網(wǎng)、單片機(jī)和嵌入式這幾個(gè)方面。

        厚積薄發(fā)、換位思考,以讀者的角度來總結(jié)文章。

        每一篇輸出,不僅僅是干貨的呈現(xiàn),更是引導(dǎo)你一步一步的深入思考,從底層邏輯來提升自己。

        方法2:嵌入?yún)R編代碼

        在動(dòng)態(tài)加載的插件中使用變量別名,除了上面演示的動(dòng)態(tài)注冊(cè)的方式,還可以通過嵌入?yún)R編代碼來: 設(shè)置一個(gè)全局標(biāo)號(hào)來實(shí)現(xiàn)。

        直接上示例代碼:

        plugin.c源文件

        #include 

        asm(".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;
        }

        main.c源文件

        #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中的符號(hào)信息:

        readelf -s libplugin.so | grep data

        小結(jié)

        這篇文檔通過兩個(gè)示例代碼,討論了如何在插件中(動(dòng)態(tài)鏈接庫(kù)),通過別名來訪問真正的變量。

        不知道您會(huì)不會(huì)有這樣的疑問:直接使用extern來聲明一下外部定義的變量不就可以了,何必這么麻煩?

        道理是沒錯(cuò)!

        但是,在一些比較特殊的領(lǐng)域或場(chǎng)景中(比如一些二次開發(fā)中),這樣的需求是的確存在的,而且是強(qiáng)需求。

        如果你有任何疑問,或者文中有任務(wù)錯(cuò)誤,歡迎留言討論、指正。


        ------ End ------

        在公眾號(hào)【IOT物聯(lián)網(wǎng)小鎮(zhèn)】后臺(tái)回復(fù)關(guān)鍵字:20522,即可獲取示例代碼的下載地址。

        既然看到這里了,如果覺得不錯(cuò),請(qǐng)您隨手點(diǎn)個(gè)【贊】和【在看】吧!

        如果轉(zhuǎn)載本文,文末務(wù)必注明:“轉(zhuǎn)自微信公眾號(hào):IOT物聯(lián)網(wǎng)小鎮(zhèn)”。


        推薦閱讀

        【1】《Linux 從頭學(xué)》系列文章

        【2】C語言指針-從底層原理到花式技巧,用圖文和代碼幫你講解透徹

        【3】原來gdb的底層調(diào)試原理這么簡(jiǎn)單

        【4】Linux中對(duì)【庫(kù)函數(shù)】的調(diào)用進(jìn)行跟蹤的3種【插樁】技巧

        【5】?jī)?nèi)聯(lián)匯編很可怕嗎?看完這篇文章,終結(jié)它!

        【6】gcc編譯時(shí),鏈接器安排的【虛擬地址】是如何計(jì)算出來的?

        【7】GCC 鏈接過程中的【重定位】過程分析

        【8】Linux 動(dòng)態(tài)鏈接過程中的【重定位】底層原理

        其他系列專輯:精選文章、應(yīng)用程序設(shè)計(jì)、物聯(lián)網(wǎng)、 C語言

        星標(biāo)公眾號(hào),第一時(shí)間看文章!


        瀏覽 89
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 很黄很黄小说 | 成人A片一区二区三区免费视频 | 美女的胸又黄又大jk | 欧美成人视频网站 | 日本黄网|