學(xué)了這么久的C,作用域,存儲器,鏈接屬性該弄清楚了
本文轉(zhuǎn)載自微信公眾號「編程珠璣」,作者守望先生。轉(zhuǎn)載本文請聯(lián)系編程珠璣公眾號。
前言
這些是編程語言中的基本概念,如果你還不是非常明確地清楚標(biāo)題的問題,并且不知道作用域,鏈接屬性,存儲期等概念的具體含義,那么本文你不該錯(cuò)過。為了更加清晰的理解我們的問題,需要先了解三個(gè)概念:作用域,鏈接屬性,存儲期。
作用域
C語言中,作用域用來描述標(biāo)識符能夠被哪些區(qū)域訪問。
而常見作用域有以下幾種:
- 塊作用域,可見范圍是從定義處到包含該定義的塊結(jié)尾
- 函數(shù)作用域,goto語句的標(biāo)簽就具有函數(shù)作用域
- 文件作用域,從定義處到定義該文件的末尾都可見。定義在函數(shù)之外的變量,就具有文件作用域了。
- 函數(shù)原型作用域,從形參定義處到原型聲明結(jié)束
為了便于說明,我們來看一個(gè)例子,就很容易理解了:
- /****************************
- 作者:守望先生
- 來源:公眾號編程珠璣
- 個(gè)人博客:https://www.yanbinghu.com
- ***************************************/
- #include <stdio.h>
- int num1 = 222; //定位在函數(shù)外,具有文件作用域
- static int num2 = 111; //定義在函數(shù)外,具有文件作用域
- int swap(int *a,int *b); //這里的a,b是函數(shù)原型作用域
- int swap(int *a,int *b)
- {
- if(NULL== a || NULL == b)
- goto error;
- else
- {
- int temp = *a; //定義在函數(shù)內(nèi),塊作用域
- *a = *b;
- *b = temp;
- return 0;
- }
- //printf("temp is %d\n",temp); //因?yàn)?/span>temp具有塊作用域,因此在這里不能直接使用
- error://goto語句的標(biāo)簽,函數(shù)作用域,因此在前面就可以引用
- {
- printf("input para is NULL\n");
- return -1;
- }
- }
- int main(void)
- {
- printf("num1=%d,num2=%d\n",num1,num2);
- swap(&num1,&num2); //num1 num2具有文件作用域,可以在main函數(shù)中直接使用
- printf("num1=%d,num2=%d",num1,num2);
- return 0;
- }
可以看到,error標(biāo)簽具有函數(shù)作用域,整個(gè)函數(shù)內(nèi)都可見,而temp具有塊作用域,因此在大括號外部,不能直接使用它。而num1和num2具有文件作用域,因此main函數(shù)可以直接使用它。
鏈接屬性
在《hello程序是如何變成可執(zhí)行文件的》我們說到了編譯的過程,最后一個(gè)步驟就是鏈接。鏈接屬性決定了在不同作用域的同名標(biāo)識符能否綁定到同一個(gè)對象或者函數(shù)。或者說,不同作用域的標(biāo)識符在編譯后是否是同一個(gè)實(shí)體。
c變量有三種鏈接屬性:
- 外部鏈接,extern修飾的,或者沒有static修飾的具有文件作用域的變量具有外部鏈接屬性
- 內(nèi)部鏈接,static修飾的具有文件作用域的變量具有內(nèi)部鏈接屬性
- 無鏈接,塊作用域,函數(shù)作用域和函數(shù)原型作用域的變量無鏈接屬性
再稍作解釋,沒有static修飾,且具有文件作用域的變量,他們在鏈接時(shí),多個(gè)同名標(biāo)識符的變量最終都綁定到同一個(gè)實(shí)體。而static修飾的具有文件作用域的變量就不一樣了,不同文件內(nèi),即便標(biāo)識符名字相同,它們也綁定到了不同的實(shí)體。
因此,如果我們希望某個(gè)變量或函數(shù)只在某一個(gè)文件使用,那么使用static修飾是一個(gè)很好的做法。
同樣的,來看一個(gè)例子。
- /****************************
- 作者:守望先生
- 來源:公眾號編程珠璣
- 個(gè)人博客:https://www.yanbinghu.com
- ***************************************/
- #include <stdio.h>
- int a = 5; //文件作用域,外部鏈接屬性,其他文件可通過extern int a的方式使用該文件的a
- static b = 6; //文件作用域,內(nèi)部鏈接屬性,即便其他文件也有同名標(biāo)識符,它們也是不同的
- int main(void)
- {
- int sum = 0 ; //無鏈接屬性
- sum = a + b;
- printf("sum is %d\n",sum);
- return 0;
- }
從代碼中可以看到,a和b都具有文件作用域,a具有外部鏈接屬性,而b具有內(nèi)部鏈接屬性,sum具有塊作用域,因此無鏈接屬性。
存儲期
實(shí)際上作用域和鏈接屬性都描述了標(biāo)識符的可見性,而存儲期則描述了這些標(biāo)識符對應(yīng)的對象的生存期。存儲期,也分下面幾種:
- 靜態(tài)存儲期,程序執(zhí)行期間一直都在,文件作用域的變量具有靜態(tài)存儲期
- 自動(dòng)存儲期,它(變長數(shù)組除外)從塊開始,到塊末尾,因此,塊作用域的變量具有自動(dòng)存儲期,它在棧中存儲,需要顯式初始化。
- 動(dòng)態(tài)分配存儲期,即通過malloc分配內(nèi)存的變量。它在堆中存儲,需要顯式初始。
- 線程存儲期,從名字可以知道, 它與線程相關(guān),使用關(guān)鍵字_Thread_local聲明的變量具有線程存儲期,它從聲明到線程結(jié)束一直存在。
- 關(guān)于初始化,可參考《C語言入坑指南-被遺忘的初始化》。
同樣地,我們通過下面的代碼來更好地理解存儲期
- /****************************
- 作者:守望先生
- 來源:公眾號編程珠璣
- 個(gè)人博客:https://www.yanbinghu.com
- ***************************************/
- #include <stdio.h>
- int num1 = 222; //靜態(tài)存儲期
- static int num2 = 111; //靜態(tài)存儲期
- int add(int a,int b)
- {
- static int tempSum = 0; //靜態(tài)存儲期
- tempSum = tempSum + a + b;
- return tempSum;
- }
- int main(void)
- {
- printf("num1=%d,num2=%d\n",num1,num2);
- int sum = 0; //自動(dòng)存儲期
- sum = add(num1,num2);
- printf("first time sum=%d\n",sum);//sum = 333
- sum = add(num1,num2);
- printf("second time sum=%d\n",sum); //sum = 666
- return 0;
- }
另外,如果我們通過nm命令查看編譯出來的程序文件的符號表,我們可以找到num1,num2,tempSum,而沒有sum,前者所用的內(nèi)存數(shù)量在編譯時(shí)就確定了。
- $ 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
- $
什么全局變量,局部變量,靜態(tài)局部變量,靜態(tài)全局變量
到這里,我們就可以很容易區(qū)分上面的變量類型了。實(shí)際上這里只是換了一種說法:
全局:具有文件作用域的變量
靜態(tài):具有靜態(tài)存儲期或內(nèi)部鏈接屬性
局部:具有函數(shù)或塊作用域的變量
因而結(jié)合起來,也就很好理解了。
- 局部變量:函數(shù)或塊作用域的變量
- 靜態(tài)局部變量:函數(shù)或塊作用域,靜態(tài)存儲期
- 全局變量:具有文件作用域的變量
- 靜態(tài)全局變量:內(nèi)部鏈接屬性的,具有文件作用域的變量
當(dāng)然,這僅僅是為了區(qū)分它們,這并不是它們的嚴(yán)格定義。更好的方法,是通過代碼來理解:
- #include <stdio.h>
- int num1 = 222; //全局變量
- static int num2 = 111; //靜態(tài)全局變量
- int add(int a,int b)
- {
- static int tempSum = 0; //靜態(tài)局部變量
- tempSum = tempSum + a + b;
- return tempSum;
- }
- int main(void)
- {
- printf("num1=%d,num2=%d\n",num1,num2);
- int sum = 0; //局部變量
- sum = add(num1,num2);
- printf("first time sum=%d\n",sum);//sum = 333
- return 0;
- }
總結(jié)
本文總結(jié)如下:
- 具有文件作用域的變量具有靜態(tài)存儲期,并且具有鏈接屬性
- 不希望其他文件訪問的文件作用域變量最好使用static修飾
- static關(guān)鍵字的含義需要結(jié)合上下文來理解
- 如果可以,全局變量應(yīng)該盡量避免使用,因?yàn)樗赡軒碜兞勘灰馔庑薷?/li>
- 使用動(dòng)態(tài)內(nèi)存通常比棧內(nèi)存慢,但是棧內(nèi)存很有限
參考
https://en.wikipedia.org/wiki/Global_variables
https://en.wikipedia.org/wiki/Local_variable
《C11標(biāo)準(zhǔn)文檔》
作者:守望,linux應(yīng)用開發(fā)者,目前在公眾號【編程珠璣】分享Linux/C/C++/數(shù)據(jù)結(jié)構(gòu)與算法/工具等原創(chuàng)技術(shù)文章和學(xué)習(xí)資源。