靜態變量

本頁使用了標題或全文手工轉換
維基百科,自由的百科全書

靜態變量(英語:Static Variable)在計算機編程領域指在程序執行前系統就為之靜態分配英語Static memory allocation(也即在運行時中不再改變分配情況)存儲空間的一類變量。與之相對應的是在運行時只暫時存在的自動變量(即局部變量)與以動態分配方式獲取存儲空間的一些對象,其中自動變量的存儲空間在調用棧上分配與釋放。

概念與定義[編輯]

「靜態變量」這一術語有兩個容易混淆的定義:

  1. 語言無關的通用定義:與程序有着相同生命周期英語Object lifetime的變量;
  2. C族語言特有的定義:以static存儲類聲明的變量。

而在以Pascal為代表的許多程序語言中,所有局部變量都由系統自動分配存儲空間,而所有全局變量的存儲空間則以靜態分配的方式獲取(對應「靜態變量」),因此由於實際上「局部變量」和「全局變量」這兩個術語已足以涵蓋所有的情況,在這些程序語言中通常不使用「靜態變量」這一術語,而直接以「全局變量」代之。一般來說,在這些程序語言中,靜態變量就是全局變量,而即使在有明確區分全局和靜態變量的程序語言中,在編譯後的代碼里二者也以相同的方式獲取存儲空間。而今術語「靜態變量」的概念則主要基於C族語言的「static」的定義(即定義2)。

作常量使用[編輯]

靜態變量也可以用於存儲常數。具體來說,靜態變量(全局變量及匯編語言里定義的符號亦同)可用const,constant或final(根據語言決定)等關鍵字標識,這時其值就會在編譯時設定,並且無法在運行時改變。編譯器通常將靜態常量與文本一起置於目標文件的文本區域,而非常量初始化數據則置於數據區;而如若有需要,有些編譯器還可選擇為其開闢專用區;為防止常數變量被錯誤的指針寫入覆蓋,亦可在這塊區域啟用內存保護機制。

C族語言中的實現[編輯]

C語言及由其衍生出的C++Objective-C等程序語言中,「static」是用於控制變量的生命周期和連接方式(即其作用域,亦即可見性)的保留字。確切來說,正如C族語言中的extern,auto與register這些保留字一樣,static也是一種存儲類(此處的「類」與面向對象語言的「」的定義不同)標識。每個變量與函數都有以上的一種存儲類標識,如果在聲明中沒有明確標識其存儲類,編譯時就會根據上下文來選擇其默認存儲類,如在源文件里的所有文件級變量對應的默認存儲類是extern,而在函數體內的變量對應的則是auto,各存儲類的屬性如下表所列。

存儲類名 生命周期 作用域
extern 靜態(程序結束後釋放) 外部(整個程序)
static 靜態(程序結束後釋放) 內部(僅翻譯單元,一般指單個源文件)
auto,register 函數調用(調用結束後釋放)

易見存儲類為extern的變量(包括上面提到的未明確聲明存儲類的文件級變量)符合前段所述靜態變量的定義1,但不符合定義2。

不同情況下的作用[編輯]

除明確標識出變量的生命周期英語Object lifetime外,將變量聲明為static存儲類還會根據變量屬性不同而有一些特殊的作用:

  • 對於靜態全局變量來說,針對某一源文件的以static聲明的文件級變量與函數的作用域只限於文件內(只在文件內可見),也即「內部連接」,因而可以用來限定變量的作用域
  • 對於靜態局部變量來說,在函數內以static聲明的變量雖然與自動局部變量的作用域相同(即作用域都只限於函數內),但存儲空間是以靜態分配而非默認的自動分配方式獲取的,因而存儲空間所在區域不同(一般來說,靜態分配時存儲空間於編譯時在程序數據段分配,一次分配全程有效;而自動分配時存儲空間則是於調用棧上分配,只在調用時分配與釋放),且兩次調用間變量值始終保持一致;必須注意,靜態局部變量只能初始化一次,這是由編譯器來保證實現。[1]

C示例[編輯]

在C語言中,帶有靜態變量的程序如下所示:

#include <stdio.h>

void func() {
	static int x = 0; // 在对func的三次调用中,x只进行一次初始化
	printf("%d\n", x); // 输出x的值
	x = x + 1;
}

int main(int argc, char * const argv[]) {
	func(); // 输出0
	func(); // 输出1
	func(); // 输出2
	return 0;
}

C++示例[編輯]

在C++中,帶有含私有靜態內部變量的類的程序如下所示:

class Request
{
	private:
		static int count; // 不能为外部调用
		string url; // 只能被成员函数调用
	
	public:
		Request() { count++; }
		string getUrl() const { return url; }
		void setUrl(string value) { url = value; }
		static int getCount() { return count; }
};
int Request::count = 0; // count 可以在类声明外进行初始化

PHP示例[編輯]

<?php
function test(){
static $a = 0;//变量$a在第一调用test()时被初始化,每次调用 test() 函数都会输出 $a 的值并加 1
echo $a;
$a++;//,每次调用 test() 函数都会输出 $a 的值并加 1
}
?>

參見[編輯]

參考[編輯]

  1. ^ 例如,gcc編譯器對靜態局部變量,首先獲取guard變量,判斷低字節是否為 0,若非零,表示已經初始化,可以直接使用。否則,將 guard 作為參數調用 __cxa_guard_acquire,如果鎖成功,執行初始化靜態變量的語句,然後釋放鎖。如果鎖失敗,說明產生競態條件,則會阻塞當前線程。利用該機制,可以很好的實現所謂 Singleton 模式。對於單線程程序,靜態變量初始化的互斥保護是沒有必要的,gcc的-fno-threadsafe-statics 選項可以禁掉該機制。