C語言:如何給全域性變數起一個別名?

2022-06-13 06:02:29

作 者:道哥,10+年嵌入式開發老兵,專注於:C/C++、嵌入式、Linux

關注下方公眾號,回覆【書籍】,獲取 Linux、嵌入式領域經典書籍;回覆【PDF】,獲取所有原創文章( PDF 格式)。

目錄

別人的經驗,我們的階梯!

別名是啥玩意?

stackoverflow上看到一個有趣的話題:如何給一個變數設定一個別名?(How to assign to a variable an alias?

所謂的變數別名,就是通過通過不同的識別符號,來表示同一個變數。

我們知道,變數名稱是給程式設計師使用的。

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

請注意:這裡所討論的別名,僅僅是通過不同的識別符號來參照同一個變數。

與強符號、弱符號沒有關係,那是另一個話題。

在上面這個貼文中,作者首先想到的是通過宏定義,對變數進行重新命名。

這樣的做法,將會在編譯之前的預處理環節,把宏識別符號替換為變數識別符號。

在網友回覆的答案中,大部分都是通過指標來實現:讓不同的識別符號指向同一個變數。

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

但是,這些答案有一個侷限:這些程式碼必須一起進行編譯才可以,否則就可能出現無法找到符號的錯誤資訊。

現在非常流行外掛程式設計,如果開發者想在外掛中通過一個變數別名來參照主程式中的變數,這該如何處理呢?

本文提供兩個方法來實現這個目的,並通過兩個簡單的範例程式碼來進行演示。

文末有範例程式碼的下載地址。

方法1:反向註冊

之前我接觸過一些CodeSys的程式碼,裡面的程式碼質量真的是非常的高,特別是軟體架構設計部分。

傳說:CodySys 是工控界的 Android。

其中有個反向註冊的想法,正好可以用在變數別名上面。

範例程式碼中一共有 2 個檔案:main.cplugin.c

main.c中定義了一個全域性變數陣列,編譯成可執行程式main

plugin.c中通過一個別名來使用main.c中的全域性變數。

plugin.c被編譯成一個動態連結庫,被可執行程式main動態載入(dlopen)。

plugin.c中,提供一個函數func_init,當動態庫被main dlopen之後,這個函數就被呼叫,並且把真正的全域性變數的地址通過引數傳入

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

本質上,這仍然是通過指標來進行參照。

只不過利用動態註冊的思想,把指標與變數的繫結關係在時間和空間上進行隔離。

plugin.c 原始檔

#include <stdio.h>

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 <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>	

// 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

執行結果:

data[0] = 10 
data[1] = 20 
libplugin.so: func_init is called. 
libplugin.so: func_stage1 is called. 
data[0] = 100 
data[1] = 200

可以看一下動態連結庫的符號表:

readelf -s libplugin.so | grep data

可以看到alias_data識別符號,並且是在本檔案中定義的全域性變數。

【關於作者】

號主:道哥,十多年的嵌入式開發老兵,專注於嵌入式 + Linux 領域,玩過微控制器、搞過智慧家居、研究過 PLC工業機器人,專案開發經驗非常豐富。

他的文章主要包括 C/C++、Linux作業系統、物聯網、微控制器和嵌入式這幾個方面。

厚積薄發、換位思考,以讀者的角度來總結文章。

每一篇輸出,不僅僅是乾貨的呈現,更是引導你一步一步的深入思考,從底層邏輯來提升自己。

方法2:嵌入組合程式碼

在動態載入的外掛中使用變數別名,除了上面演示的動態註冊的方式,還可以通過嵌入組合程式碼來: 設定一個全域性標號來實現。

直接上範例程式碼:

plugin.c原始檔

#include <stdio.h>

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 <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>	

// 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

執行結果:

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

小結

這篇檔案通過兩個範例程式碼,討論瞭如何在外掛中(動態連結庫),通過別名來存取真正的變數。

不知道您會不會有這樣的疑問:直接使用extern來宣告一下外部定義的變數不就可以了,何必這麼麻煩?

道理是沒錯!

但是,在一些比較特殊的領域或場景中(比如一些二次開發中),這樣的需求是的確存在的,而且是強需求。

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


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

在公眾號【IOT物聯網小鎮】後臺回覆關鍵字:20522,即可獲取範例程式碼的下載地址。

既然看到這裡了,如果覺得不錯,請您隨手點個【贊】和【在看】吧!

如果轉載本文,文末務必註明:「轉自微信公眾號:IOT物聯網小鎮」。

推薦閱讀

【1】《Linux 從頭學》系列文章

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

【3】原來gdb的底層偵錯原理這麼簡單

【4】Linux中對【庫函數】的呼叫進行跟蹤的3種【插樁】技巧

【5】內聯組合很可怕嗎?看完這篇文章,終結它!

【6】gcc編譯時,連結器安排的【虛擬地址】是如何計算出來的?

【7】GCC 連結過程中的【重定位】過程分析

【8】Linux 動態連結過程中的【重定位】底層原理

其他系列專輯:精選文章應用程式設計物聯網C語言

星標公眾號,第一時間看文章!