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