作者:守望,Linux應用開發者,目前在公衆号【程式設計珠玑】 分享Linux/C/C++/資料結構與算法/工具等原創技術文章和學習資源。
前言
這些是程式設計語言中的基本概念,如果你還不是非常明确地清楚标題的問題,并且不知道作用域,連結屬性,存儲期等概念的具體含義,那麼本文你不該錯過。為了更加清晰的了解我們的問題,需要先了解三個概念:作用域,連結屬性,存儲期。
作用域
C語言中,作用域用來描述辨別符能夠被哪些區域通路。
而常見作用域有以下幾種:
- 塊作用域,可見範圍是從定義處到包含該定義的塊結尾
- 函數作用域,goto語句的标簽就具有函數作用域
- 檔案作用域,從定義處到定義該檔案的末尾都可見。定義在函數之外的變量,就具有檔案作用域了。
- 函數原型作用域,從形參定義處到原型聲明結束
為了便于說明,我們來看一個例子,就很容易了解了:
可以看到,error标簽具有函數作用域,整個函數内都可見,而temp具有塊作用域,是以在大括号外部,不能直接使用它。而num1和num2具有檔案作用域,是以main函數可以直接使用它。
連結屬性
在《hello程式是如何變成可執行檔案的》我們說到了編譯的過程,最後一個步驟就是連結。連結屬性決定了在不同作用域的同名辨別符能否綁定到同一個對象或者函數。或者說,不同作用域的辨別符在編譯後是否是同一個實體。
c變量有三種連結屬性:
- 外部連結,extern修飾的,或者沒有static修飾的具有檔案作用域的變量具有外部連結屬性
- 内部連結,static修飾的具有檔案作用域的變量具有内部連結屬性
- 無連結,塊作用域,函數作用域和函數原型作用域的變量無連結屬性
再稍作解釋,沒有static修飾,且具有檔案作用域的變量,他們在連結時,多個同名辨別符的變量最終都綁定到同一個實體。而static修飾的具有檔案作用域的變量就不一樣了,不同檔案内,即便辨別符名字相同,它們也綁定到了不同的實體。
是以,如果我們希望某個變量或函數隻在某一個檔案使用,那麼使用static修飾是一個很好的做法。
同樣的,來看一個例子。
/****************************
作者:守望先生
來源:公衆号程式設計珠玑
個人部落格:https://www.yanbinghu.com***************************************/
#include int a = 5; //檔案作用域,外部連結屬性,其他檔案可通過extern int a的方式使用該檔案的astatic b = 6; //檔案作用域,内部連結屬性,即便其他檔案也有同名辨別符,它們也是不同的int main(void)
{int sum = 0 ; //無連結屬性
sum = a + b;
printf("sum is %d
",sum);return 0;
}
從代碼中可以看到,a和b都具有檔案作用域,a具有外部連結屬性,而b具有内部連結屬性,sum具有塊作用域,是以無連結屬性。
存儲期
實際上作用域和連結屬性都描述了辨別符的可見性,而存儲期則描述了這些辨別符對應的對象的生存期。存儲期,也分下面幾種:
- 靜态存儲期,程式執行期間一直都在,檔案作用域的變量具有靜态存儲期
- 自動存儲期,它(變長數組除外)從塊開始,到塊末尾,是以,塊作用域的變量具有自動存儲期,它在棧中存儲,需要顯式初始化。
- 動态配置設定存儲期,即通過malloc配置設定記憶體的變量。它在堆中存儲,需要顯式初始。
- 線程存儲期,從名字可以知道, 它與線程相關,使用關鍵字_Thread_local聲明的變量具有線程存儲期,它從聲明到線程結束一直存在。
關于初始化,可參考《C語言入坑指南-被遺忘的初始化》。
同樣地,我們通過下面的代碼來更好地了解存儲期:
/****************************
作者:守望先生
來源:公衆号程式設計珠玑
個人部落格:https://www.yanbinghu.com***************************************/
#include int num1 = 222; //靜态存儲期static int num2 = 111; //靜态存儲期int add(int a,int b)
{static int tempSum = 0; //靜态存儲期
tempSum = tempSum + a + b;return tempSum;
}int main(void)
{
printf("num1=%d,num2=%d
",num1,num2);int sum = 0; //自動存儲期
sum = add(num1,num2);
printf("first time sum=%d
",sum);//sum = 333
sum = add(num1,num2);
printf("second time sum=%d
",sum); //sum = 666return 0;
}
另外,如果我們通過nm指令檢視編譯出來的程式檔案的符号表,我們可以找到num1,num2,tempSum,而沒有sum,前者所用的記憶體數量在編譯時就确定了。關于nm指令的使用可以參考《linux常用指令-開發調試篇》。
$ gcc -g -o lifetime lifetime.c
$ nm lifetime|grep num1
0000000000601038 D num1
$ nm lifetime|grep num2
000000000060103c d num2
$ nm lifetime|grep tempSum
0000000000601044 b tempSum.2289
$ nm lifetime|grep sum
$
什麼全局變量,局部變量,靜态局部變量,靜态全局變量
到這裡,我們就可以很容易區分上面的變量類型了。實際上這裡隻是換了一種說法:
全局:具有檔案作用域的變量
靜态:具有靜态存儲期或内部連結屬性
局部:具有函數或塊作用域的變量
因而結合起來,也就很好了解了。
- 局部變量:函數或塊作用域的變量
- 靜态局部變量:函數或塊作用域,靜态存儲期
- 全局變量:具有檔案作用域的變量
- 靜态全局變量:内部連結屬性的,具有檔案作用域的變量
當然,這僅僅是為了區分它們,這并不是它們的嚴格定義。更好的方法,是通過代碼來了解:
#include
int num1 = 222; //全局變量
static int num2 = 111; //靜态全局變量
int add(int a,int b){
static int tempSum = 0; //靜态局部變量
tempSum = tempSum + a + b;
return tempSum;
}
int main(void){
printf("num1=%d,num2=%d
",num1,num2);
int sum = 0; //局部變量
sum = add(num1,num2);
printf("first time sum=%d
",sum);//sum = 333
return 0;
}
總結
本文總結如下:
- 具有檔案作用域的變量具有靜态存儲期,并且具有連結屬性
- 不希望其他檔案通路的檔案作用域變量最好使用static修飾
- static關鍵字的含義需要結合上下文來了解
- 如果可以,全局變量應該盡量避免使用,因為它可能帶來變量被意外修改
- 使用動态記憶體通常比棧記憶體慢,但是棧記憶體很有限
參考
https://en.wikipedia.org/wiki/Global_variables
https://en.wikipedia.org/wiki/Local_variable
《C11标準文檔》
●編号507,輸入編号直達本文
●輸入m擷取文章目錄
C語言與C++程式設計
分享C/C++技術文章