static变量 static函数
首先要明白c語言的存儲空間
轉自:http://www.52rd.com/blog/Detail_RD.Blog_imjacob_5297.html
一、c程序存儲空間布局
C程序一直由下列部分組成:
????? 1)正文段——CPU執行的機器指令部分;一個程序只有一個副本;只讀,防止程序由于意外事故而修改自身指令;
????? 2)初始化數據段(數據段)——在程序中所有賦了初值的全局變量,存放在這里。
????? 3)非初始化數據段(bss段)——在程序中沒有初始化的全局變量;內核將此段初始化為0。
????? 4)棧——增長方向:自頂向下增長;自動變量以及每次函數調用時所需要保存的信息(返回地址;環境信息)。
????? 5)堆——動態存儲分。自下而上增長
|-----------|
|???????????????? |
|-----------|
|??? 棧???????? | ?高地址位
|-----------|
|??? |??????????? |?
|?? |/?????????? |
|???????????????? |
|???????????????? |
|?? /|?????????? |
|??? |??????????? |?
|-----------|
|??? 堆???????? |
|-----------|
| 未初始化? |
|-----------|
|?? 初始化? |
|-----------|
|? 正文段?? |低地址位
|-----------|
從而c語言變量有三種生命空間:程序的局部變量存在于(堆棧)中,全局變量存在于(靜態區?)中,動態申請數據存在于(?堆)中。
如果在utility.h中定義了一個全局變量 如 int n = 20;
則這個頭文件只能被include一次,否則會出錯;例如在utility.cpp 中include "utility.h",然后又在main.cpp中include "utility.h";include的意思就是把頭文件的內容全部輸出到include之處,這樣的話,就相當于在utility.cpp中定義一個全局變量n,在main.cpp中又定義一個全局變量n,正如上文提及到的,整個工程的全局變量都放在一個地方,這樣就會出現重復定義。
如何在utility.h中定義一個全局變量,從而在工程的任何一個源文件,只要include一下這個頭文件,就可以直接用這個全局變量呢?有三種方法:http://hi.baidu.com/maydaygmail/blog/item/52fc1c0b6413d13e6159f3d7.html
1. (推薦)在utility.h中聲明為extern int n(意思是指這只是一個聲明,定義在外部);然后,只要在任意一個.cpp?文件中再定義一次,只能定義一次,int n =10;在工程的任意地方就可以使用全局變量n,還可以修改n的值
2.?用static修飾的全局變量
??? 首先,我要告訴你static與extern是一對“水火不容”的家伙,也就是說extern和static不能同時修飾一個變量;其次,static修飾的全局變量聲明與定義同時進行,也就是說當你在頭文件中使用static聲明了全局變量后,它也同時被定義了;最后,static修飾全局變量的作用域只能是本身的編譯單元,也就是說它的“全局”只對本編譯單元有效,其他編譯單元則看不到它,如:
??? test1.h:
??? #ifndef TEST1H
??? #define TEST1H
??? static char g_str[] = "123456";?
??? void fun1();
??? #endif
??? test1.cpp:
??? #include "test1.h"
????
??? void fun1()
??? {
??????? cout << g_str << endl;
??? }
????
??? test2.cpp
??? #include "test1.h"
????
??? void fun2()
??? {
??????? cout << g_str << endl;
??? }
????
??? 以上兩個編譯單元可以連接成功, 當你打開test1.obj時,你可以在它里面找到字符串"123456", 同時你也可以在test2.obj中找到它們,它們之所以可以連接成功而沒有報重復定義的錯誤是因為雖然它們有相同的內容,但是存儲的物理地址并不一樣,就像是兩個不同變量賦了相同的值一樣,而這兩個變量分別作用于它們各自的編譯單元。
??? 也許你比較較真,自己偷偷的跟蹤調試上面的代碼,結果你發現兩個編譯單元(test1, test2)的g_str的內存地址相同,于是你下結論static修飾的變量也可以作用于其他模塊,但是我要告訴你,那是你的編譯器在欺騙你,大多數編譯器都對代碼都有優化功能,以達到生成的目標程序更節省內存,執行效率更高,當編譯器在連接各個編譯單元的時候,它會把相同內容的內存只拷貝一份,比如上面的"123456", 位于兩個編譯單元中的變量都是同樣的內容,那么在連接的時候它在內存中就只會存在一份了,如果你把上面的代碼改成下面的樣子,你馬上就可以拆穿編譯器的謊言:
??? test1.cpp:
??? #include "test1.h"
????
??? void fun1()
??? {
??????? g_str[0] = 'a';
??????? cout << g_str << endl;
??? }
??? test2.cpp
??? #include "test1.h"
????
??? void fun2()
??? {
??????? cout << g_str << endl;
??? }
????
??? void main()
??? {
??????? fun1(); // a23456
??????? fun2(); // 123456
??? }
????
??? 這個時候你在跟蹤代碼時,就會發現兩個編譯單元中的g_str地址并不相同,因為你在一處修改了它,所以編譯器被強行的恢復內存的原貌,在內存中存在了兩份拷貝給兩個模塊中的變量使用。
??? 正是因為static有以上的特性,所以一般定義static全局變量時,都把它放在原文件中而不是頭文件,這樣就不會給其他模塊造成不必要的信息污染,同樣記住這個原則吧!
3?const修飾的全局常量
??? const修飾的全局常量用途很廣,比如軟件中的錯誤信息字符串都是用全局常量來定義的。const修飾的全局常量據有跟static相同的特性,即它們只能作用于本編譯模塊中,但是const可以與extern連用來聲明該常量可以作用于其他編譯模塊中,?如
??? extern const char g_str[];
??? 然后在原文件中別忘了定義:
??? const char g_str[] = "123456";
??? 所以當const單獨使用時它就與static相同,而當與extern一起合作的時候,它的特性就跟extern的一樣了!所以對const我沒有什么可以過多的描述,我只是想提醒你,const char* g_str = "123456" 與 const char g_str[] = "123465"是不同的, 前面那個const 修飾的是char * 而不是g_str,它的g_str并不是常量,它被看做是一個定義了的全局變量(可以被其他編譯單元使用), 所以如果你像讓char *g_str遵守const的全局常量的規則,最好這么定義const char* const g_str="123456".
也可以直接在頭文件中定義const char*g_str="123456"
工程任何地方都可以使用g_str,但是不能修改值
在舉一個例子:
//utiltiy.h
.......
//utility.cpp
#include "utility.h"
? ? ? ?static int m = 6;
//main.cpp
#include"utility.h"
int main()
{
cout<<m;
}
首先,雖然在main.cpp中include"utility.h",但是頭文件里沒有定義m所以,必須extern int m,表明這個m是外部的。如果在int main()之前聲明了extern int m;依然有錯,這是因為m因為有static現在,所以m只能用于utility.cpp中;
所以要改為:
//utility.cpp
#include "utility.h"
? ?int m = 6;
//main.cpp
#include"utility.h"
extern int m;
int main()
{
cout<<m;
}
這樣就對了。
如果這樣寫,對不對呢:
//utiltiy.h
?static int m = 6;
//utility.cpp
#include "utility.h"
? ? ??
//main.cpp
#include"utility.h"
int main()
{
cout<<m;
}
這樣是對的,雖然在utility.cpp和main.cpp中都有m的定義,但是此時m被static現在,就是說這兩個m只能在這兩個cpp文件中有效,所以不會沖突;
如果把static去掉,則就有兩個全局變量m,所以出錯
4. 如果在.h文件中定義的全局變量,用于只讀的,還可以在頭文件中這樣定義:
? ? #define G_VAL ?5
? 或者
? enum {G_VAL = 5};
? 在其他文件中,只要include這個頭文件,就可以直接讀取G_VAL
下面講一下static函數
主要參考:http://hi.baidu.com/pope123/blog/item/344407d5512953d450da4b6c.html
在平常的?C/C++?開發中,幾乎所有的人都已經習慣了把類和函數分離放置,一個?.h?的頭文件里放聲明,對應的?.c?或者?.cpp?中放實現。從開始接觸,到熟練使用,幾乎已經形成了下意識的流程。在?Symbian OS?下編程,則更是如此,再小的類也會分成兩個文件,幾乎沒有人想去改變。
盡管這樣的做法無可厚非,而且在不少情況下是相對合理甚至必須的,但我還是要給大家介紹一下把實現全部放置到頭文件中的方式,給出可供大家使用的另一個選擇。同時針對這一做法,也順便說一下其優缺點以及需要注意的情況。
我是一個很喜歡簡潔的人,多年以來甚至養成了這樣的癖好,如果一個功能是能夠用一條語句實現的,那就不要用兩條語句。在我看來,如果給別人提供一份可以復用的代碼的話,最優雅的狀態莫過于僅僅提供一個頭文件就全部搞定。
之所以不太喜歡引入源文件,最重要的原因是源文件往往會帶來工程文件的變化;而且,在使用過程中也會增加一些額外的操作,例如,在一個組織良好的工程里,頭文件和源文件很有可能是位于不同的目錄,這樣就會多帶來一次文件復制操作。
2?、正文
2.1?顧慮
我遇到有不少人不使用頭文件來包含實現,往往是出于以下幾種顧慮:
1、??????????????暴露了實現細節
2、??????????????頭文件被包含到不同的源文件中,會導致鏈接沖突
3、??????????????頭文件被包含到不同的源文件中,會導致有多份實現被編譯出來,增大可執行體的體積
如果有顧慮?1?,那很顯然應該在第一時間拋棄完全在頭文件中實現的念頭。不過我遇到的情形里,通常后兩種顧慮占據了絕對的比例。而這種顧慮,通常是由于對?C/C++?沒有足夠的了解導致的。
有顧慮?2?的,經常會是一些有?C?語言開發經驗的程序員。他們所擔心的也往往是出現的全局函數的情況。例如有以下頭文件?c_function.h?(清晰起見,防衛宏之類的代碼沒有列出):
int integer_add(const int a, const int b)
{
?????????return a + b;
}
如果在同一工程中,有?a.c?(或者是?.cpp?)和?b.c?兩個(或兩個以上)源文件包含了此頭文件,則在鏈接時期就會發生沖突,因為在兩個源文件編譯得到的目標文件中都有一份?integer_add?的函數實現,導致鏈接器不知道對于調用了此函數的調用者,應該使用哪一個副本。(此時有兩個全局函數int integer_add(const int a, const int b))
2.2?著手
解決的辦法有兩個,各自為兩個關鍵字,一個是?inline?,另一個是?static?。使用這兩個關鍵字的任意一個來修飾?integer_add?函數,都會消除上述的沖突問題,然而本質卻大不相同。
如果使用?inline?,則意味著編譯器會在調用此函數的地方把函數的目標代碼直接插入,而不是放置一個真正的函數調用,實際作用就是這個函數事實上已經不再存在,而是像宏一樣被就地展開了。使用?inline?的副作用,首先在于毋庸置疑地,代碼的體積變大了;其次則是,這個關鍵字嚴格算起來并不是?C?語言的關鍵字,使用它多少會帶來一些移植性方面的風險,盡管主流的?C?語言編譯器都可以支持?inline?。對于?GCC?,?inline?功能關鍵字就是?inline?本身,而對于微軟的編譯器,應該是?__inline?(注意有兩個前導下劃線)。
而且,根據慣例,?inline?通常都是對編譯器的某種暗示而非強制要求,編譯器有權力在你不知情的情況下把它實現為非?inline?的狀態(可能的原因有,函數太大或者復雜度過高)。這樣的后果是什么,不好意思,我沒有測試過。
如果是使用?static?,那么至少結果是可預料的。所有包含此頭文件的源文件中都會存在此函數的一份副本。雖然代碼也有一定程度的膨脹,但好就好在互相不沖突,因為?static?關鍵字保證了該函數的可見度為單個源文件之內。
static全局變量與普通的全局變量有什么區別?static局部變量和普通局部變量有什么區別?static函數與普通函數有什么區別?
全局變量(外部變量)的說明之前再冠以static?就構成了靜態的全局變量。全局變量本身就是靜態存儲方式,靜態全局變量當然也是靜態存儲方式。 這兩者在存儲方式上并無不同。這兩者的區別雖在于非靜態全局變量的作用域是整個源程序,當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。而靜態全局變量則限制了其作用域, 即只在定義該變量的源文件內有效, 在同一源程序的其它源文件中不能使用它。由于靜態全局變量的作用域局限于一個源文件內,只能為該源文件內的函數公用,因此可以避免在其它源文件中引起錯誤。
從以上分析可以看出, 把局部變量改變為靜態變量后是改變了它的存儲方式即改變了它的生存期。把全局變量改變為靜態變量后是改變了它的作用域,限制了它的使用范圍。
static函數與普通函數作用域不同,僅在本文件。只在當前源文件中使用的函數應該說明為內部函數(static),內部函數應該在當前源文件中說明和定義。對于可在當前源文件以外使用的函數,應該在一個頭文件中說明,要使用這些函數的源文件要包含這個頭文件
static全局變量與普通的全局變量有什么區別:static全局變量只初使化一次,防止在其他文件單元中被引用;
static局部變量和普通局部變量有什么區別:static局部變量只被初始化一次,下一次依據上一次結果值;
static函數與普通函數有什么區別:static函數在內存中只有一份,普通函數在每個被調用中維持一份拷貝
總結,static限定了全局變量或者函數的作用域,使其只能在本編譯單元
static變量只被定義一次,如下題:
unsigned int sum_int( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是static類型的。?
for (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
答案與分析:
所謂的函數是可重入的(也可以說是可預測的),即:只要輸入數據相同就應產生相同的輸出。
這個函數之所以是不可預測的,就是因為函數中使用了static變量,因為static變量的特征,這樣的函數被稱為:帶“內部存儲器”功能的的函數。因此如果我們需要一個可重入的函數,那么,我們一定要避免函數中使用static變量,這種函數中的static變量,使用原則是,能不用盡量不用。
將上面的函數修改為可重入的函數很簡單,只要將聲明sum變量中的static關鍵字去掉,變量sum即變為一個auto 類型的變量,函數即變為一個可重入的函數。
當然,有些時候,在函數中是必須要使用static變量的,比如當某函數的返回值為指針類型時,則必須是static的局部變量的地址作為返回值,若為auto類型,則返回為錯指針。
總結
以上是生活随笔為你收集整理的static变量 static函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux通信--信号量
- 下一篇: 出栈顺序