1. 程式人生 > 其它 >全域性變數_C語言:什麼是全域性變數,區域性變數,靜態全域性變數,靜態區域性變數...

全域性變數_C語言:什麼是全域性變數,區域性變數,靜態全域性變數,靜態區域性變數...

技術標籤:全域性變數

作者:守望,Linux應用開發者,目前在公眾號【程式設計珠璣】分享Linux/C/C++/資料結構與演算法/工具等原創技術文章和學習資源。

前言

這些是程式語言中的基本概念,如果你還不是非常明確地清楚標題的問題,並且不知道作用域,連結屬性,儲存期等概念的具體含義,那麼本文你不該錯過。為了更加清晰的理解我們的問題,需要先了解三個概念:作用域,連結屬性,儲存期。

作用域

C語言中,作用域用來描述識別符號能夠被哪些區域訪問。

而常見作用域有以下幾種:

  • 塊作用域,可見範圍是從定義處到包含該定義的塊結尾

  • 函式作用域,goto語句的標籤就具有函式作用域

  • 檔案作用域,從定義處到定義該檔案的末尾都可見。定義在函式之外的變數,就具有檔案作用域了。

  • 函式原型作用域,從形參定義處到原型宣告結束

為了便於說明,我們來看一個例子,就很容易理解了:

/****************************
作者:守望先生
來源:公眾號程式設計珠璣
個人部落格:https://www.yanbinghu.com
***************************************/
#include
intnum1=222;//定位在函式外,具有檔案作用域
staticintnum2=111;//定義在函式外,具有檔案作用域
intswap(int*a,int*b);//這裡的a,b是函式原型作用域
intswap(int*a,int*b){
if(NULL==a||NULL==b)
gotoerror;
else
{
inttemp=*a;//定義在函式內,塊作用域
*a=*b;
*b=temp;
return0;
}
//printf("tempis%d
",temp);//因為temp具有塊作用域,因此在這裡不能直接使用
error://goto語句的標籤,函式作用域,因此在前面就可以引用
{
printf("inputparaisNULL
");
return-1;
}
}
intmain(void){
printf("num1=%d,num2=%d
",num1,num2);
swap(&num1,&num2);//num1num2具有檔案作用域,可以在main函式中直接使用
printf("num1=%d,num2=%d",num1,num2);
return0;
}

可以看到,error標籤具有函式作用域,整個函式內都可見,而temp具有塊作用域,因此在大括號外部,不能直接使用它。而num1和num2具有檔案作用域,因此main函式可以直接使用它。

連結屬性

在《hello程式是如何變成可執行檔案的》我們說到了編譯的過程,最後一個步驟就是連結。連結屬性決定了在不同作用域的同名識別符號能否繫結到同一個物件或者函式。或者說,不同作用域的識別符號在編譯後是否是同一個實體。

c變數有三種連結屬性:

  • 外部連結,extern修飾的,或者沒有static修飾的具有檔案作用域的變數具有外部連結屬性

  • 內部連結,static修飾的具有檔案作用域的變數具有內部連結屬性

  • 無連結,塊作用域,函式作用域和函式原型作用域的變數無連結屬性

再稍作解釋,沒有static修飾,且具有檔案作用域的變數,他們在連結時,多個同名識別符號的變數最終都繫結到同一個實體。而static修飾的具有檔案作用域的變數就不一樣了,不同檔案內,即便識別符號名字相同,它們也繫結到了不同的實體。

因此,如果我們希望某個變數或函式只在某一個檔案使用,那麼使用static修飾是一個很好的做法。

同樣的,來看一個例子。

/****************************
作者:守望先生
來源:公眾號程式設計珠璣
個人部落格:https://www.yanbinghu.com***************************************/
#includeinta=5;//檔案作用域,外部連結屬性,其他檔案可通過externinta的方式使用該檔案的astaticb=6;//檔案作用域,內部連結屬性,即便其他檔案也有同名識別符號,它們也是不同的intmain(void)
{intsum=0;//無連結屬性
sum=a+b;
printf("sumis%d
",sum);return0;
}

從程式碼中可以看到,a和b都具有檔案作用域,a具有外部連結屬性,而b具有內部連結屬性,sum具有塊作用域,因此無連結屬性。

儲存期

實際上作用域和連結屬性都描述了識別符號的可見性,而儲存期則描述了這些識別符號對應的物件的生存期。儲存期,也分下面幾種:

  • 靜態儲存期,程式執行期間一直都在,檔案作用域的變數具有靜態儲存期

  • 自動儲存期,它(變長陣列除外)從塊開始,到塊末尾,因此,塊作用域的變數具有自動儲存期,它在棧中儲存,需要顯式初始化。

  • 動態分配儲存期,即通過malloc分配記憶體的變數。它在堆中儲存,需要顯式初始。

  • 執行緒儲存期,從名字可以知道, 它與執行緒相關,使用關鍵字_Thread_local宣告的變數具有執行緒儲存期,它從宣告到執行緒結束一直存在。

關於初始化,可參考《C語言入坑指南-被遺忘的初始化》。
同樣地,我們通過下面的程式碼來更好地理解儲存期:

/****************************
作者:守望先生
來源:公眾號程式設計珠璣
個人部落格:https://www.yanbinghu.com***************************************/
#includeintnum1=222;//靜態儲存期staticintnum2=111;//靜態儲存期intadd(inta,intb)
{staticinttempSum=0;//靜態儲存期
tempSum=tempSum+a+b;returntempSum;
}intmain(void)
{
printf("num1=%d,num2=%d
",num1,num2);intsum=0;//自動儲存期
sum=add(num1,num2);
printf("firsttimesum=%d
",sum);//sum=333
sum=add(num1,num2);
printf("secondtimesum=%d
",sum);//sum=666return0;
}

另外,如果我們通過nm命令檢視編譯出來的程式檔案的符號表,我們可以找到num1,num2,tempSum,而沒有sum,前者所用的記憶體數量在編譯時就確定了。關於nm命令的使用可以參考《linux常用命令-開發除錯篇》。

$gcc-g-olifetimelifetime.c
$nmlifetime|grepnum1
0000000000601038Dnum1
$nmlifetime|grepnum2
000000000060103cdnum2
$nmlifetime|greptempSum
0000000000601044btempSum.2289
$nmlifetime|grepsum
$

什麼全域性變數,區域性變數,靜態區域性變數,靜態全域性變數

到這裡,我們就可以很容易區分上面的變數型別了。實際上這裡只是換了一種說法:
全域性:具有檔案作用域的變數
靜態:具有靜態儲存期或內部連結屬性
區域性:具有函式或塊作用域的變數

因而結合起來,也就很好理解了。

  • 區域性變數:函式或塊作用域的變數

  • 靜態區域性變數:函式或塊作用域,靜態儲存期

  • 全域性變數:具有檔案作用域的變數

  • 靜態全域性變數:內部連結屬性的,具有檔案作用域的變數

當然,這僅僅是為了區分它們,這並不是它們的嚴格定義。更好的方法,是通過程式碼來理解:

#include
intnum1=222;//全域性變數
staticintnum2=111;//靜態全域性變數
intadd(inta,intb){
staticinttempSum=0;//靜態區域性變數
tempSum=tempSum+a+b;
returntempSum;
}
intmain(void){
printf("num1=%d,num2=%d
",num1,num2);
intsum=0;//區域性變數
sum=add(num1,num2);
printf("firsttimesum=%d
",sum);//sum=333
return0;
}

總結

本文總結如下:

  • 具有檔案作用域的變數具有靜態儲存期,並且具有連結屬性

  • 不希望其他檔案訪問的檔案作用域變數最好使用static修飾

  • static關鍵字的含義需要結合上下文來理解

  • 如果可以,全域性變數應該儘量避免使用,因為它可能帶來變數被意外修改

  • 使用動態記憶體通常比棧記憶體慢,但是棧記憶體很有限

參考

https://en.wikipedia.org/wiki/Global_variables

https://en.wikipedia.org/wiki/Local_variable

《C11標準文件》


●編號507,輸入編號直達本文

●輸入m獲取文章目錄

C語言與C++程式設計

b4cf38e003b12694ca7faf6af8868a83.png

分享C/C++技術文章