C語言的強符號,弱符號以及它們的用途

引言

雖然用C語言這麼多年,但強符號,弱符號這類東西沒有多大關注,最近關注到,感覺挺實用的。

什麼是強符號,弱符號

大家都知道,我們的程序裡,不論是變量名,還是函數名,都是符號,它存在於編譯鏈接整個過程,甚至在最終的可執行文件中也有(一般用於程序調試),是各目標文件之間的"粘合劑",符號貫穿了我們的程序從無到有的整個過程。

我們在編譯代碼的過程中,經常會碰到類似這樣錯誤:

其實這就是因為鏈接目標文件的過程中,鏈接器遇到了兩個強符號,而同時存在兩個相同的強符號,會導致鏈接器不知道應該選哪一個,所以就直接報錯。

那麼怎麼樣的才算強符號,怎麼樣的才算弱符號呢?

正式開始前,先來了解一下GCC的attribute特性裡支持weak屬性,這個和符號的強弱有直接關係,關於該屬性,GNU的官方定義如下:

The weak attribute causes a declaration of an external symbol to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions that can be overridden in user code, though it can also be used with non-function declarations. The overriding symbol must have the same type as the weak symbol. In addition, if it designates a variable it must also have the same size and alignment as the weak symbol. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker.

大概的意思就是,weak屬性可以將一個符號聲明為弱符號,可以用於函數庫中,方便用戶定義自己的符號以覆蓋庫中的符號。

先來看看全局變量的強符號與弱符號,看下下面幾個測試用例。按如下方式,生成文件test.c:

<code>#include <stdio.h>

int tc_0;
int tc_0 = 6;
int __attribute__((weak)) tc_0;
int __attribute__((weak)) tc_0=1;

int main(void)
{
printf("tc_0:%d\\r\\n",tc_0);
return 0;
}/<stdio.h>/<code>

編譯產生如下錯誤:

看來,即便變量定義的時候,賦了weak屬性,但是變量初始化了,也會被認為是強符號,嘗試將"int __attribute__((weak)) tc_0=1;"這一行定義註釋掉:

<code>#include <stdio.h>

int tc_0;
int tc_0 = 6;
int __attribute__((weak)) tc_0;
//int __attribute__((weak)) tc_0=1;

int main(void)
{
printf("tc_0:%d\\r\\n",tc_0);
return 0;
}/<stdio.h>/<code>

編譯通過了,執行結果如下:

這裡可以得出,沒有初始化的全局變量為弱符號,而初始化過的全局變量為強符號。但是對於賦了weak屬性又初始化過的變量,雖然在同一個文件裡和初始化過的全局變量同樣表現為強符號,但是如果定義在不同的文件裡會是什麼情況呢?這一點需要確認清楚。因此,再建立文件test2.c:

<code>#include <stdio.h>

int __attribute__((weak)) tc_0 = 1;/<stdio.h>/<code>

此時和前面的現象不同,同樣可以編譯通過。執行結果和上一次一樣,符合預期,因為test2.c的變量被添加了weak屬性:

將兩個文件裡的全局變量強弱屬性交換一下,再次驗證:

test.c:

<code>#include <stdio.h>

int tc_0;
int __attribute__((weak)) tc_0 = 6;
int __attribute__((weak)) tc_0;

int main(void)
{
printf("tc_0:%d\\r\\n",tc_0);
return 0;
}/<stdio.h>/<code>

test2.c

<code>#include <stdio.h>

int tc_0 = 1;/<stdio.h>/<code>

編譯執行結果如下:

可以確定,在不同的文件中,符號的強弱關係和weak屬性強相關,不存在模糊地帶,一般的應用或者容易出問題的地方也都是在不同的文件之間。

瞭解完全局變量,再來了解一下函數。如果直接兩個文件有完全一樣的函數,肯定會報定義衝突,就不再討論,主要來看看將其中一個函數添加weak屬性的情況

test.c:

<code>#include <stdio.h>

void __attribute__((weak)) tc_0(void)
{
printf("tc_o in test.c\\r\\n");
return;
}

int main(void)
{
tc_0();
return 0;
}/<stdio.h>/<code>

test2.c

<code>#include <stdio.h>

void tc_0(void)
{
printf("tc_o in test2.c\\r\\n");
return;
}/<stdio.h>/<code>

編譯執行後,結果如下:

可見,可以編譯通過,且main函數里實際調用的是test2.c裡的函數。

強符號,弱符號有何用處

那麼這個有什麼用呢?其實在GNU的定義裡已經提到了,強弱符號最大的用處在於,設計框架代碼或函數庫的時候,可以將默認接口或變量定義弱符號,用戶可在自己的代碼裡定義自己的接口或變量,這樣編譯鏈接的時候鏈接器會鏈接用戶自己定義的接口或變量。這樣可以增加框架或函數庫的彈性,更方便滿足用戶自定義的需求。