計算機系統篇之鏈接(11):爲什麼要避免在 C/C++ 中使用全局變量
Author:stormQ
Friday, 08. May 2020 10:20PM
避免使用全局變量的原因
避免使用全局變量的原因之一是全局變量的不正確使用容易導致難以發現的程序錯誤。
不正確地使用全局變量會引發哪些錯誤
不正確使用全局變量會引發的典型錯誤:
1)全局變量的值被同名的全局變量修改
$ cat test1.c
int g_val_1 = 0;
int g_val_2;
void func()
{
g_val_1 = 1;
g_val_2 = 2;
}
$ cat main.c
#include <stdio.h>
int g_val_1;
int g_val_2 = 3;
void func();
int main()
{
func();
printf("g_val_1=%d, g_val_2=%d\n", g_val_1, g_val_2);
return 0;
}
$ gcc -c test1.c -o test1.o
$ gcc -c main.c -o main.o
$ gcc -o main test1.o main.o
$ ./main
g_val_1=1, g_val_2=2
main.c 中定義的全局變量 g_val_2 的值被 test1.c 中定義的 func() 函數由 3 改爲 2 了。
2)全局變量的值被不同名的全局變量修改
$ cat test2.c
double g_val_1;
void func()
{
g_val_1 = 1.1;
}
$ cat main2.c
#include <stdio.h>
int g_val_0 = 1;
int g_val_1 = 2;
int g_val_2 = 3;
void func();
int main()
{
func();
printf("g_val_0=%d, g_val_1=%d, g_val_2=%d\n", g_val_0, g_val_1, g_val_2);
return 0;
}
$ gcc -o main2 test2.c main2.c
/usr/bin/x86_64-linux-gnu-ld: Warning: alignment 4 of symbol `g_val_1' in /tmp/ccIc3noO.o is smaller than 8 in /tmp/ccXjyx8c.o
/usr/bin/x86_64-linux-gnu-ld: Warning: size of symbol `g_val_1' changed from 8 in /tmp/ccXjyx8c.o to 4 in /tmp/ccIc3noO.o
$ ./main2
g_val_0=1, g_val_1=-1717986918, g_val_2=1072798105
main2.c 中定義的全局變量 g_val_2 的值被意外地修改了。
如何正確地使用全局變量
要想正確地使用全局變量,需要遵循以下原則:
-
原則 1:儘量使用靜態局部變量而非全局變量。
-
原則 2:每個定義的全局變量必須顯式地初始化。
-
原則 3:每個引用的全局變量必須用
extern
關鍵字聲明。 -
原則 4:利用編譯器檢查全局變量的不正確使用。
1) 爲什麼要遵循原則 1
遵循原則 1 的理由很簡單,不用全局變量就不會產生全局變量可能引發的錯誤。
如果一個目標模塊中多個函數都會引用同一個變量,並且其他目標模塊不會引用該變量,那麼將該變量聲明爲靜態局部變量。如果一個變量除了定義它的目標模塊以外還會被其他目標模塊引用,那麼本原則就不適用了。
注:一個目標模塊指的是一個源文件。
2) 爲什麼要遵循原則 2
遵循原則 2 的理由:編譯器和鏈接器對未初始化的全局變量的處理規則會導致影響程序正確性的行爲,但它們不會報任何錯誤。要想真正理解這一點,需要先理解符號解析
和gcc/g++ 處理 .c 和 .cpp 源文件時的區別
,可以參考作者的另一篇文章——計算機系統篇之鏈接(4):符號解析
。
3) 爲什麼要遵循原則 3
遵循原則 3 的理由:對於 C++ 程序(以 .cpp 或 .cc 結尾的源文件),對全局變量的引用如果不用extern
關鍵字修飾,鏈接器會直接報錯。對於 C 程序(以 .c 結尾的源文件),對全局變量的引用如果不用extern
關鍵字修飾,雖然不會報錯,但容易引發上述典型錯誤。因此,無論是 C 程序還是 C++ 程序,用extern
關鍵字修飾對全局變量的引用都是一個好習慣。
4) 爲什麼要遵循原則 4
遵循原則 4 的理由:對於 C 程序,如果使用 gcc 編譯,容易引發上述典型錯誤。因爲使用 gcc 編譯 C 程序時,實際調用的編譯器是c1
,而編譯器c1
的默認行爲是將未初始化的全局變量放到COMMON
塊中,從而允許鏈接器合併臨時定義,可以理解爲弱符號,所以容易引發上述典型錯誤。因此,對於該情形,在編譯時添加選項-fno-common
從而指示編譯器c1
將未初始化的全局變量放到.bss
section 中,從而不允許鏈接器合併臨時定義,可以理解爲強符號。對於 C++ 程序,無論是 gcc 還是 g++ 都不會引發上述典型錯誤。因爲對於 C++ 程序,無論是用 gcc 編譯還是用 g++編譯,實際調用的編譯器都是cc1plus
,而編譯器cc1plus
的默認行爲是將未初始化的全局變量放到.bss
section 中。
如果你覺得本文對你有所幫助,歡迎關注公衆號,支持一下!