C|一個小實例瞭解變量存儲屬性、作用域、生命週期

一個變量的聲明、定義包括存儲類型、類型修飾符、類型以及標識符或初始值:

static unsinged int i = 22;

變量根據其存儲類型聲明,聲明位置(函數外、函數內不同的語句塊)而具有不同的存儲屬性、作用域和生命週期。

1 變量與常量

在C/C++程序裡,參與計算的數據都是通過存放在內存中不同類型的常量或者變量來表示的。

變量是內存或寄存器中用一個標識符命名的存儲單元,可以用來存儲一個特定類型的數據,並且數據的值在程序運行過程中可以進行修改。可見,變量首先是一個標識符或者名稱,就像一個客房的編號一樣,有了這個編號我們在交流中就可方便言表,否則,我們只可意會,那多不方便。為了方便,我們在給變量命名時,最好能符合大多數人的習慣,基本可以望名知義,便於交流和維護;其次,變量是唯一確定的對應內存若干存儲單元或者某個寄存器。這些是編譯器來保證的,用戶一般不用費心。

程序員一旦定義了變量,那麼,變量就至少可為我們提供兩個信息:一是變量的地址,即操作系統為變量在內存中分配的若干內存單元的首地址;二是變量的值,也就是,變量在內存中所分配的那些內存單元中所存放的數據。

由於程序的多樣需要,我們對變量也有各種各樣的要求,比如:變量的生命期,變量的初始狀態,變量的有效區域,變量的開闢地和變量的開闢區域的大小等等;為了滿足這些要求,C語言的發明者就設置了以下各類變量:

1.1 不同數據類型的變量

如:

<code>char cHar;
int iTimes;
float faverage;/<code>

I 全局變量;

II 局部變量;

III 靜態變量,靜態全局變量和靜態局部變量,關鍵詞:static

IV 寄存器變量,關鍵詞,register;

V 外部變量,關鍵詞,extern;

C語言中,變量在內存中開闢地的大小由數據類型決定的,由於PC機中規定一個地址單元存一個字節, 不同的數據類型的變量,為其分配的地址單元數是不一樣的。 C語言中除幾種基本的數據類型外用戶還可以通過typedef來自己定義所需要的數據類型。 比如:

<code>typedef int INT;
typedef unsigned int UINT;
typedef unsigned short USHORT;
typedef char CHAR;

typedef struct _Node
{
    int val;
    struct _Node *next;
}Node, *PNode, NodeArr[6];/<code>

其中的INT、UINT、USHORT、CHAR、Node、PNode就是定義的新的數據類型。為什麼有了int、char等類型,還要重新定義一個INT和CHAR呢?實際上這樣的好處是一旦平臺發生了變化,char和int的定義發生了變化,自己的程序庫因為使用的是自己的CHAR,INT等類型,可以改動很小,而MS等程序庫也正是這麼做的。

在函數內部說明的變量為局部變量,只有在函數執行時,局部變量才存在,當函數執行完退出後,局部變量隨之消失。 也就是,當函數執行完退出後,原先在函數內定義的變量現在不能用。

與局部變量不同,全局變量在整個程序都是可見的,可在整個程序運行過程中,對於任何一個程序都是可用的。全局變量的說明的位置在所有函數之外,但可被任何一個函數使用,讀取或者寫入。但是在多個線程裡訪問全局變量的時候,需要注意多線程安全問題。

靜態變量是分配在存儲器中C程序所佔據的數據段內, C程序運行的整個過程中一直保留,不會被別的變量佔用。靜態變量可以定義成全局靜態變量或局部靜態變量,當定義為全局靜態變量時,在它定義的程序的整個運行期間均存在並且保持原來的存儲單元位置不會改變,但是不能被同一項目內的其它文件通過extern關鍵字聲明訪問。

同靜態全局變量定義一樣,當在局部變量名前加靜態變量說明符static,該變量就定義為靜態局部變量。編譯器為該變量建立永久存儲單元。永久是指C程序運行的整個過程中一直保留,不會被別的變量佔用。靜態局部變量和靜態全局變量的根本區別在作用區域上,靜態局部變量只在它被說明的函數或複合語句中有效,並且在兩次函數調用期間仍然保存其值不變,即就是有記憶功能;它們的生命期是相同的,和C程序整個運行期同在。

普通局部變量函數退出後,值就無效了。而靜態局部變量具有記憶功能,能夠記住上次執行後的值。

靜態局部變量只初始化一次,並且默認初始化為0。

以下面的程序為例:

<code>int func1(int x)
{
    int z = 0; //z是一個普通的局部變量,每次函數調用,z都為0
    z += x;
    return z;
}
int func2(int x)
{
    static int z = 0; //z是一個靜態局部變量,只進行第一次初始化,以後每次函數調用,都保留上次調用的值
    z += x;
    return z;
}
int main(void)
{
    for(int i = 0; i < 10; i++)
    {
        //此處10次循環,func1的輸出分別為0,1,2,3,4,5,...,9;func2的輸出分別為:0,1,3,6,10,...
        printf("func1(%d) = %d, func2(%d) = %d\n", i, func1(i), i, func2(i));
    }

    return 0;
}/<code>

寄存器變量不像其他變量那樣在內存中存放數據,而是在CPU的寄存器中暫存數據,使用寄存器變量比使用內存變量的操作速度快得多。只有整型和字符型變量可定義為寄存器變量。定義方式如下:

register int i;

由於CPU中寄存器有限,儘量減少使用數量和和佔用時間,用完馬上釋放;不能定義為全局變量,也不能定義在結構或者類中。如果寄存器已經無可用資源,那麼寄存器變量將轉化為普通局部變量。

在大型程序中,會將一個大型的程序分成若干個獨立的模塊和文件分別編譯,然後統一鏈接在一起。為了解決全局變量和函數的共用問題,就引入了 extern關鍵字。這樣只需在一個文件中定義全局變量和函數,在另一個文件中要用到這些變量和函數時,只需將那個文件中的變量和函數說明表複製過來,在前面加上extern,告訴編譯器,這些變量和函數已經在別的文件中定義說明,通過extern聲明的變量就稱為外部變量

常量,就是不同類型中不變的值。比如’A’, 100, 3.1415926,"hello world"等字面量,對於字符串字面量,存入在靜態區只讀數據段,對於其它字面量,是程序代碼的一部分。

另外,還可以通過宏來定義一些常量,比如:

#define PI 3.1415926;

上面就用一個常量符號PI來定義了圓周率的值。以後就可以在程序中使用這個PI來計算圓相關的數值。

也可以用const來聲明:

const int up = 9999;

2 變量的存儲空間

我們知道,無論是程序的代碼還是數據,都是被加載和保存到內存中,從而被運行和訪問的。以下圖的X86系統為例。在X86系統中, 內存的有效範圍為4GB,其中高2GB為內核空間,用來存放內核的代碼和數據;低2GB為用戶空間,運行進程的用戶態代碼,並且低2GB的內存是進程私有空間。一個進程不能跨進程訪問別的進程空間的用戶態地址。

C|一個小實例瞭解變量存儲屬性、作用域、生命週期


如下圖所示,程序在內存中分為如下幾部分(section):

C|一個小實例瞭解變量存儲屬性、作用域、生命週期

1).text 代碼段,這段是存放代碼的,從彙編角度來看就是指令。

2).rdata 只讀數據段,不能修改,存放常量,字符常量,const常量。

3).data 數據段,存放已經初始化好的全局變量和靜態變量。

4).bss(Block Started by Symbol) 存放未初始化的全局變量和靜態變量。

.rdata、.data、.bss都是用來存放全局/靜態數據的。除了.bss段,.rdata、.data段的值都是在編譯的時候就確定了,並且將其編譯進了可執行文件,經過反彙編都能找得到。bss段是在代碼運行的時候將其初始化為0的(這就是未初始化的全局和靜態變量默認值為0的根源)

5).stack,如下圖所示,棧中存放普通的局部變量、形參返回地址等,大小有限制,Windows應用層棧大小默認為1MB,內核棧系統根據CPU架構而定,x86系統上為12KB,x64系統上為24KB,安騰系統上為32KB ;Linux應用層程序默認棧大小為10MB,內核棧大小4KB或8KB。

C|一個小實例瞭解變量存儲屬性、作用域、生命週期

6)堆:存放任意數據。

所以,綜上所述:

1)全局初始化變量存放在靜態存儲區中的.data區;

2)全局未初始化變量存放在靜態存儲區中的.bss區;

3)局部變量存放在棧;

4)全局和局部靜態變量存放在靜態存儲區中的.data區和.bss區;

5)常量存放在靜態區中的.rdata區,.rdata區域是隻讀內存,不能修改;

6)寄存器變量存放在寄存器中。

3 變量的作用域

變量的作用域是指變量能在代碼什麼地方被訪問到。比如下面的代碼:

<code>int g_mycount = 10;
int func1(void)
{
    int x = 10;
    printf("%d,%d\n", x*x,g_mycount);
}
int func2(void)
{
    int y = 10;
    printf("%d,%d\n", y+y,g_mycount);
}/<code>

變量g_mycount是全局變量,變量x和y是函數func1和func2的局部變量,因此func1不能訪問y,func2也不能訪問x,但它們都能訪問g_mycount。這就是所謂的變量的作用域。

1)全局變量:所有程序中的代碼都能訪問。當然,如果全局變量在工程中一個源文件中定義,而要在另一個源文件中訪問,需要在另一個源文件裡使用extern關鍵字來導入。但是在多個線程裡訪問全局變量的時候,需要注意多線程安全問題。

2)靜態全局變量:在同一個源文件中可以訪問,在不同的源文件中,不能訪問,也不能使用extern來導入。靜態全局變量主要是為了防止全局變量帶來的命名衝突。

3)局部變量:只能函數內部訪問。

4)靜態局部變量:只能函數內部訪問。

5)寄存器變量:和局部變量一致。

4 變量的生命週期

變量的生命週期是指變量在程序運行期間的有效時間。

1)全局變量:整個程序運行期間有效。

2)靜態全局變量:整個程序運行期間有效。

3)局部變量:函數範圍內或者代碼塊範圍內({}括來的部分)有效,函數退出無效。

4)靜態局部變量:整個程序運行期間有效。

5)寄存器變量:和局部變量一致。

下面是一個綜合各類變量存儲類型、作用域、生命週期的實例:

<code>#include  // malloc()

int count_g = 0;         // 全局初始化變量
char *pFile_g;           // 全局未初始化變量
static int num_gs = 10;  // 全局初始化靜態變量
void func(void)
{
    int k = 0;             // 局部變量
    char arrc[]  = "123";  // 局部變量,字符數組,用"123"字符串常量進行初始化
    char *str    = "hello";// 局部變量,字符指針,指向"hello"字符串常量
    pFile_g       = (char *)malloc(128); // 全局變量指向堆內存
    char *content = (char *)malloc(256); // 局部指針變量指向堆內存
	static int is  = 0; // 靜態局部變量,已初始化
	register int r = 0; // 寄存器變量
    free(pFile_g);      // 顯示釋放p1指向的堆內存
    free(content);      // 顯示釋放p2指向的堆內存
}
int main(void)
{
    func();
	return 0;
}/<code>

各類變量存儲位置、作用域、生命週期:

C|一個小實例瞭解變量存儲屬性、作用域、生命週期

-End-


分享到:


相關文章: