什麼是全局變數,局部變數,靜態全局變數和靜態局部變數?
前言
這些是編程語言中的基本概念,如果你還不是非常明確地清楚標題的問題,並且不知道作用域,鏈接屬性,存儲期等概念的具體含義,那麼本文你不該錯過。為了更加清晰的理解我們的問題,需要先了解三個概念:作用域,鏈接屬性,存儲期。當然需要注意的是,本文說明的是這些名稱在C語言中的含義。
作用域
C語言中,作用域用來描述標識符能夠被哪些區域訪問。
而常見作用域有以下幾種:
- 塊作用域,可見範圍是從定義處到包含該定義的塊結尾
- 函數作用域,goto語句的標籤就具有函數作用域
- 文件作用域,從定義處到定義該文件的末尾都可見。定義在函數之外的變數,就具有文件作用域了。
- 函數原型作用域,從形參定義處到原型聲明結束
為了便於說明,我們來看一個例子,就很容易理解了:
/****************************
作者:守望先生
來源:公眾號編程珠璣
個人博客:https://www.yanbinghu.com
***************************************/
#include <stdio.h>
int num1 = 222; //定位在函數外,具有文件作用域
static int num2 = 111; //定義在函數外,具有文件作用域
int swap(int *a,int *b); //這裡的a,b是函數原型作用域
int swap(int *a,int *b)
{
if(NULL== a || NULL == b)
goto error;
else
{
int temp = *a; //定義在函數內,塊作用域
*a = *b;
*b = temp;
return 0;
}
//printf("temp is %d
",temp); //因為temp具有塊作用域,因此在這裡不能直接使用
error://goto語句的標籤,函數作用域,因此在前面就可以引用
{
printf("input para is NULL
");
return -1;
}
}
int main(void)
{
printf("num1=%d,num2=%d
",num1,num2);
swap(&num1,&num2); //num1 num2具有文件作用域,可以在main函數中直接使用
printf("num1=%d,num2=%d",num1,num2);
return 0;
}
可以看到,error標籤具有函數作用域,整個函數內都可見,而temp具有塊作用域,因此在大括弧外部,不能直接使用它。而num1和num2具有文件作用域,因此main函數可以直接使用它。
鏈接屬性
在《hello程序是如何變成可執行文件的》我們說到了編譯的過程,最後一個步驟就是鏈接。鏈接屬性決定了在不同作用域的同名標識符能否綁定到同一個對象或者函數。或者說,不同作用域的標識符在編譯後是否是同一個實體。
c變數有三種鏈接屬性:
- 外部鏈接,extern修飾的,或者沒有static修飾的具有文件作用域的變數具有外部鏈接屬性
- 內部鏈接,static修飾的具有文件作用域的變數具有內部鏈接屬性
- 無鏈接,塊作用域,函數作用域和函數原型作用域的變數無鏈接屬性
再稍作解釋,沒有static修飾,且具有文件作用域的變數,他們在鏈接時,多個同名標識符的變數最終都綁定到同一個實體。而static修飾的具有文件作用域的變數就不一樣了,不同文件內,即便標識符名字相同,它們也綁定到了不同的實體。
因此,如果我們希望某個變數或函數只在某一個文件使用,那麼使用static修飾是一個很好的做法。
同樣的,來看一個例子。
/****************************
作者:守望先生
來源:公眾號編程珠璣
個人博客:https://www.yanbinghu.com
***************************************/
#include <stdio.h>
int a = 5; //文件作用域,外部鏈接屬性,其他文件可通過extern int a的方式使用該文件的a
static 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 <stdio.h>
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 = 666
return 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 <stdio.h>
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標準文檔》
微信公眾號【編程珠璣】:專註但不限於分享計算機編程基礎,Linux,C語言,C++,數據結構與演算法,工具,資源等編程相關[原創]技術文章,號內包含大量經典電子書和視頻學習資源。歡迎一起交流學習,一起修鍊計算機「內功」,知其然,更知其所以然。