深入理解靜態變量

2021-02-20 看雪學院

#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 原創,轉載請註明來自看雪社區。

好書推薦

相關焦點

  • 什麼是靜態變量?它與臨時變量有什麼區別?(深入解讀)
    前幾天有網友詢問關於問靜態變量的問題,考慮到該問題的普遍性,今天這篇文章我們就來介紹下什麼是靜態變量?什麼是臨時變量?二者之間有什麼區別?
  • Java中的static關鍵字和靜態變量、靜態方法
    作者: Java進階者 來源:Java進階學習交流一、static關鍵字使用static修飾的變量和方法分別稱為類變量(或稱靜態變量)和類方法(或稱靜態方法),沒有使用static修飾的變量和方法分別稱為實例變量和實例方法
  • PHP static局部靜態變量和全局靜態變量總結
    靜態局部變量的特點:1.不會隨著函數的調用和退出而發生變化,不過,儘管該變量還繼續存在,但不能使用它。
  • C語言 | static靜態變量
    例87:學習C語言static定義靜態變量的用法。 解題思路:在C語言中,static 不僅可以用來修飾變量,還可以用來修飾函數,使用 static 修飾的變量,稱為靜態變量。靜態變量的存儲方式與全局變量一樣,都是靜態存儲方式。
  • C語言初學靜態變量的使用
    #C語言初學#在敲代碼時候在定義變量前面有時候會有static修飾,有什麼用呢?我就假假的自問自答一波,畢竟也看過書。在C語言中內存是分了五個區的代碼區,常量區,全局數據區,堆區,棧區。靜態變量就存放在全局數據區。
  • 面試官:為什麼java中靜態方法不能調用非靜態方法或變量?
    main中調用非靜態變量或者是方法都會報錯。我們反過來看看:反過來沒有一點問題,接下來我們解釋一下原因:二、原因解釋我們需要首先知道的是靜態方法和靜態變量是屬於某一個類,而不屬於類的對象。我們不直接講原因,先從jvm說起:這是一張類加載的生命周期圖。
  • 在嵌入式軟體編程中深入理解關鍵字
    引 言 計算機程式語言的關鍵字就好比是它的靈魂,只有深入理解了它們的含義才能編寫出優秀的代碼。根據const使用的不同場合,大致可以分為三種情況,其一限定普通變量,其二限定函數參數,其三限定指針變量。 第一和第二種情況最為簡單,語句①和語句②分別展示了它的用法。語句①定義了一個值為10的整型常量。語句②中的const表示在函數體中不能修改src指向的區域中的數據,這與函數的拷貝功能相對應,只做它應該做的事情而不應該有其他副作用,編譯器可以利用這些信息進行適當的優化。
  • 深入理解 Dart Function & Closure
    (本文中可能會出現 函數 / 方法 二者僅叫法不同)而本文將帶你深入理解 Dart 的函數 (Function)&閉包(Closure)。什麼是 Closure(閉包)如果你從未聽說過閉包,沒關係,本節將會從零開始引入閉包這個概念。
  • 一文理解 Python 中的變量
    在很多程式語言中,變量是靜態類型。其含義是,變量在初始化時需要聲明為一個特定的類型,在後續的使用中也只能將相同類型的值賦給這個變量。靜態類型的變量,意味著在其上只能執行類型專有的操作。而 Python 中的變量不受此限制。變量可以被賦予一個類型的值,之後再被賦予另一個類型的值。
  • 手把手帶你深入解析靜態分派 & 動態分派原理|原力計劃
    man的靜態類型 = 引用類型 = Human:不會被改變、在編譯器可知// 變量man的動態類型 = 實例對象類型 = Man:會變化、在運行期才可知 } }即:變量的靜態類型 = 引用類型根據變量的靜態類型,進行方法分派的行為,即根據變量的靜態類型,確定執行哪個方法。
  • 深入分析Java中的關鍵字static
    這篇文章就把java中static關鍵字的使用方法的原理進行一個深入的分析。父類靜態變量父類靜態代碼塊子類靜態變量子類靜態代碼塊父類普通變量父類普通代碼塊父類構造函數>子類普通變量子類普通代碼塊子類構造函數代碼驗證一下:首先我們定義一個父類然後定義一個子類看個結果二、深入分析static
  • 都說變量有七八種,到底誰是 Java 的親兒子
    網上羅列了很多關於變量的理解,良莠不齊,不知道哪些是對的,哪些是錯的。所以筆者索性就這些博客和自己的理解寫出這篇文章,如果有不對的地方,希望讀者能夠指正,感謝。變量是我們經常用到的一種,我在剛學 Java 的時候,也經常被各種變量的概念折磨,當時並沒有細摳,但是我在寫一篇類似的文章中,想把變量作為一種小標題來簡述一下,但是發現,變量這個概念還是比較繁瑣的,本篇文章就來深入認識一下 Java 中這些變量的概念。
  • 深入理解Java虛擬機:類加載機制
    一、類的生命周期二、類加載時機必須對類進行"初始化"的情況:使用new關鍵字實例化對象的時候讀取或設置一個類型的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候。調用一個類型的靜態方法的時候使用java.lang.reflect包的方法對類型進行反射調用的時候。當初始化類的時候,發現其父類還沒有進行過初始化的時候。
  • C語言全局變量存放在哪裡?
    ,佔用靜態的存儲單元。說到靜態的存儲單元,這裡還要提一下全局變量分為:全局變量和靜態全局變量。全局變量的定義請看示例代碼CH07_3_4,而靜態全局變量,只是在int i = 2;前加static關鍵字。書寫形式:static int i =2;全局變量與靜態全局變量有什麼區別?
  • 深入理解void類型
    儘管如此,但還是有極少數系統設定RAM區從0地址開始,但指向有效變量的指針不會指向0地址。即使「代碼區」從0地址開始,但在任何情況下,0地址都不是C語言中任何函數的起始地址,因此指向有效函數地址的指針也不會指向0地址。
  • php變量是什麼?php變量的數據類型、命名規則等詳細介紹
    php變量雖然很簡單,大家都會使用,但是很多人並沒有真正的了解php變量。今天小編講php變量專題,就是希望能幫助大家更徹底的了解php變量,在使用中更得心應手。下邊對php變量的介紹來源於小編的理解和學習筆記整理所得,如有不對的地方,望批評指出,謝謝!二、php變量1、什麼是php變量?
  • C語言局部變量和全局變量的區別
    局變量是使用相同的內存塊在整個類中存儲一個值。 全局變量的存在主要有以下一些原因: 2,使用全局變量程序運行時速度更快一些(因為內存不需要再分配),同樣現在也快不了多少。 3,對於局部變量的名字空間汙染,這個在不使用太多變量時是可以避免的。
  • 怎麼理解php中的變量?php變量命名注意什麼?
    怎麼理解php中的變量?程序中的變量源於數學,在程序語言中能夠儲存結果或者表示抽象概念。簡單理解變量是臨時存儲值的容器,它可以儲存數字、文本、和一些複雜的數據(比如說字符串、複雜的排列組合等),變量在php語言中居於核心地位,是使用php的關鍵所在,變量的值在程序運行中會隨時發生變化,能夠把程序中準備使用的一段數據起一個簡短容易記得名字,另外它還可以保存用戶輸入數據和特點運算的結果,總結變量是變量是用於跟蹤幾乎所有類型信息的簡單工具。
  • C語言中的變量詳解
    當在main函數中用extern對x,y進行了「外部變量聲明」,就可以從「聲明」處起,合理的使用該外部變量x和y。變量的存儲類別動態存儲方式和靜態存儲方式變量從作用域(空間)上分為全局變量和局部變量。從存在的時間(生存期)的角度可分為靜態存儲方式和動態存儲方式。靜態存儲方式:在程序運行期間分配固定的存儲空間的方式。
  • 在.NET中,C#靜態類使用static定義,什麼是靜態?如何使用它?
    基本概念在.NET應用程式中,使用C#開發,少不了使用靜態類。首先理解清楚什麼是靜態,靜態是相對於實例的,前面我們講解的類,就是實例的類。實例的類,只需要使用class關鍵字定義即可,實例的類必須使用new關鍵字生成對象才可以使用。那什麼是C#靜態的類呢?