#include <stdio.h>#include <stdlib.h> static int g_nTest = 0x996; int main(){ printf("%p\r\n",&g_nTest); system("pause"); return 0;}在mainCRTStartup()函數起始位置下斷點,然後在內存窗口監測靜態全局變量地址。可以看到其在斷下時,全局靜態變量地址的值就已經有了,因為已初始化的全局變量的值會被寫入到exe文件中,所以其在模塊加載時,就已經有了值,是在mainCRTStartup()函數之前的。我們繼續測試,在C++編譯器環境下,將函數的返回值賦值給全局靜態變量的情況。#include <stdio.h>#include <stdlib.h>int GetInt(){ printf("Hello world!"); return 0x996;} int nTest1 = GetInt(); int main(){ system("pause"); return 0;}該函數在_cinit()中的第二個_initterm調用裡被執行,_cinit()的作用為初始化浮點協處理器和初始化全局變量。此時到了第二個_intitterm按F10(不要按F11跟進去)自動跳轉到在GetInt函數頭部下的斷點的位置。第一個為_initterm官方的全局變量初始化,第二個_initterm才為用戶的全局變量初始化。在main函數return處下斷點,單步步過到進程結束的位置,查看全局靜態變量值的變化。一路F10跟到MainCRTStartup中的exit(mainret);處,全局靜態變量內存的值仍未發生變動,此時單步執行exit時,程序結束。所以,我們可以判定,全局變量的生命周期是從所處模塊裝載到所處模塊卸載。編譯器控制跨文件訪問:限制導出全局靜態變量主要用途就是限制導出,實現其函數和變量的私有化,編譯器通過限制導出機制來控制其跨文件訪問的。static void foo(),只能在本文件中使用,不可以跨文件調用,這樣則有利於開發過程中的私有化,從而摘輕各自開發者的責任。早期編譯器的私有概念是通過static來實現的,後來才完善這個概念,並逐步發展為其他的面向對象語言,比如C++。在沒有面向對象概念的時候,使用static來實現私有化。static char* msg = "Hello";char* GetMsg(){ return msg;}printf("%s\r\n",GetMsg());編譯器編譯階段將全局靜態變量進行處理,在連結階段時候,其他文件便不能夠訪問本文件中的全局靜態變量了,會產生報錯。但是僅僅是編譯器層面做的處理,全局靜態變量的值依舊存在內存中,可以用如下的方法進行訪問。#include <stdio.h>#include <stdlib.h> static int g_nTest = 0x996;int g_nTest2 = 0x123;void printFun(); int main(){ printFun(); system("pause"); return 0;}#include <stdio.h>extern int g_nTest2; void printFun(){ printf("%x\r\n",(&g_nTest2)[-1]);}名稱粉碎(Name-mangling)又名命名粉碎或命名重組,是指在目標文件符號表和連接過程中使用的名字通常與編譯目標文件的源程序中的名字不一樣,編譯器將目標源文件中的名字進行了調整。首先將其聲明成全局變量,然後將其作用域插入到全局變量名稱中去,類似於snTest_fooD通過這種方式將全局變量限制為在某函數裡面才可以訪問。不同編譯器廠商對局部靜態變量的名稱粉碎機制存在差異,有些會將參數和返回值也加入到重組後的名稱中,名稱粉碎和編譯器廠商的習慣相關,不屬於標準,所以,不同的廠商不同的版本,甚至不同的版本規則都不一樣。修改各項函數屬性,編譯後,打開對應的obj文件,搜索局部靜態變量名,查看不同屬性參數的修改對於名稱粉碎後的局部靜態變量名的影響。#include <stdio.h>#include <stdlib.h> void TestLocal(){ static int nTest1 = 0x996; printf("%d\r\n",nTest1);} int main(){ TestLocal(); system("pause"); return 0;}_?nTest1@?1??TestLocal@@9@9將其局部靜態變量放入函數內的代碼塊中,編譯後觀察名稱粉碎的變化:void TestLocal(){ { static int nTest1 = 0x996; printf("%d\r\n",nTest1); }}_?nTest1@?2??TestLocal@@9@9可以看到由?1變成了?2這裡大致可以推測,?x表示層級。全局靜態變量不進行名稱粉碎不影響從標識符到內存地址的識別,局部靜態變量不名稱粉碎會影響。編譯器通過名稱粉碎的方式做語法檢查,關鍵是集成了變量名、作用域名、作用域的層級編號。局部靜態變量只能被賦一次初值的原因
上述代碼是給編譯器看的,告訴編譯器全局變量的snTest的初值為999。靜態局部變量定義處沒有產生賦值的彙編代碼,所以在函數執行時不會被賦值。
局部靜態變量初始化為常量的值靜態局部變量如果賦初值,則會和已初始化的全局變量一樣被寫入到文件中,存儲在數據區中的已初始化的全局變量區。如果未賦初值,則會存儲在未初始化的全局變量區,都不會產生賦值的彙編指令。
局部靜態變量初始化為變量的值
void fooD(int n){ static int nTest = n;}在C編譯器下報錯error C2099: initializer is not a constantc++的語法允許局部靜態變量初始化為變量的值,c語言不允許。調用方式、返回值、函數參數、及函數參數的數量均會影響到其名稱粉碎規則的改變。_?nTest1@?1??TestLocal@@YAXH@Z@4HAVC++6.0 Debug中watch窗口解析名稱粉碎bugwatch窗口用的C編譯器的名稱粉碎規則,所以其無法正常顯示cpp文件中的局部靜態變量信息。當靜態局部變量賦初值為變量時,儲存在未初始化區,會產生代碼。
如何判斷靜態局部變量是否被賦初值當靜態全局變量賦值為變量之後,VC++6.0編譯器會在其存儲位置附近增加一個字節來存儲是否賦初值的狀態。VC++6.0中,一個位存儲一個靜態全局變量是否被賦初值的狀態。其他編譯器存儲狀態的位置和大小可能不一樣,但是思路一樣。
#include <stdio.h>#include <stdlib.h> void TestLocal(int n){ static int nTest2 = n; printf("%p:",&nTest2); printf("%d\r\n",nTest2); (&nTest2)[1] = 0; nTest2++;} int main(){ TestLocal(10); TestLocal(20); TestLocal(30); system("pause"); return 0;}(&nTest2)[1] = 0;將這個標誌位的值給修改掉了,所以導致了靜態變量重複賦初值。在VC++6.0編譯器中,當賦初值為函數參數的局部靜態變量超過8個時,會新增加一個字節來記錄狀態:
致謝
看雪ID:flag0
https://bbs.pediy.com/user-873556.htm
*這裡由看雪論壇 flag0 原創,轉載請註明來自看雪社區。好書推薦