變數名儲存在那裡,變數的儲存機制是什麼?

初學編程,但我是個比較愛追究根本,一定要知其所以然。

比如指針,確實是很有意思,指針的值存的是另外一個值的地址。

但對變數理解不夠透徹,請從底層解釋一下,謝謝了。

比如 int a = 5

我知道有一塊內存,存了這個值5,a代表了這塊內存。就好像給一個盒子起名叫a,這個盒子里裝了5,現實中這個名字,也就是a只是存在人腦里的,不是實話的。

另外a,b等字元在計算機里用很小的整數儲存,也就是a和65對應。

我的問題是 a = 5 的時候,有一塊內存存了5,也就是這塊內存是00000101,但計算機怎麼知道這塊內存叫a?這個名字a是在哪裡儲存的?

我的理解是否有一塊專門存儲變數名的內存,比如根據順序第x塊存了65,這個65又存了00000101的地址?但這樣豈不是每個變數都是指針?


對於 C/C++ 這種編譯成機器代碼的語言:

變數名不需要儲存,所有的局部變數讀寫都會變成(棧地址 + 偏移)的形式。

允許子程序嵌套的語言比如 Pascal/ML,讀寫非局部變數會經由訪問鏈(Access link)去讀其他的棧幀,但是變數名也會變成一個索引(整數),不會在內存中出現的。

對於帶 eval 的動態語言來說,變數名是儲存的,比如 JS 的每個 Environment Record 就存了所有的局部變數,以及訪問鏈。


變數名不需要儲存,變數名是給人看的,編譯器編譯的時候知道每個變數所儲存的地址,直接使用地址來讀寫便是。


變數名會變成 ebp+n、esp+n等一類的東西了。。。


Belleve等菊苣的回答已經很清楚了。

我只是來推翻題主的想法的。

如果對於變數a經過處理之後保存成65,那麼按這種方式,aMockingExtensibleMarkupLanguageHyperTextTransferProtocolRequest應該怎麼表示呢?


編譯器會統計所有局部變數的數量,然後統計最小的佔用空間(由於一些變數作用域不重疊,可以共用空間,以及一些變數會被優化為寄存器變數),在函數調用的開始在棧上分配。分配之後偏移就是固定的了,用ebp減去偏移來訪問。

如果你看彙編代碼的話,以x86為例,函數開頭先保存esp,再保存caller寄存器,之後會有個sub esp的動作,這個就是了。

其他不懂的可以看《csapp》。再不懂寫個小程序放od裡面看,你會看到動態的調用過程。

至於託管語言,是分配在託管棧上,沒有那麼優化了,嚴格為一個局部變數佔一塊區域。


轉載來的,基本解決了我的疑惑,希望幫到有同樣疑惑的人

C 裡面 ---變數名-- 和 --地址-- 的關係探討

變數名不佔空間

變數:用來標識(identify)一塊內存區域,這塊區域的值一般是可以更改的,這就是它「變」的由來,但是我們可以通過使用如const等一些修飾符號來限定這一內存區域的操作特性(characteristic),即變數的操作特性。用const修飾的使變數不能更改的就和常量一樣的變數叫做常變數。

變數名:是一個標識符(identifier),用來指代一塊內存區域,即變數,使用變數使我們操作內存以區域(area),以塊(block)為單位,提高了方便性。

你的機器代碼中,是不會出現變數名的;變數名是給我們程序員操作內存來使用的。

想想在彙編年代,沒有變數名,我們操作內存,都是用地址來直接操作的,還要控制區域大小;當然彙編語言已經有了簡單的變數。

對於編譯器,它會搜集我們的變數名,比如我們定義了一個全局的int a;那麼編譯器都為我們做了什麼呢?

它會為程序預留4個位元組的空間(假設在32位平台),並把我們的變數名「a」保存進符號表,並用這個符號表的索引對應實際的空間。

如果下面出現b = a;那麼它就會根據符號表找到變數的真正的物理位置,取得它的值,賦給b。

這是寫編譯器需要做的,我們需要建立符號表。

但是實際在彙編層次上,操作的都是地址而已,不存在任何名稱了。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

除了變數名不是內存地址,其他名都是地址。對么?

所謂的其他名無非是函數名、標識符常量名、指針名、數組名、結構名、類名等等。

樓主的話有部分是對的,

比如指針名、數組名、函數名就是地址,它們分別表示指針所指向元素的地址、數組的首地址和函數的入口地址。

變數名雖然不直接表示地址,但可用取地址符號來獲得它所代表的變數的存放地址。因為在定義變數的同時會分配給它相應的空間。

但類和結構只有事例化時才為它分配空間,從而不能用取地址符號來獲得類名或結構名的地址。

變數名是用來標識某個內存塊的

地址就是地址啦,如是變數名的話,用取地址運算符就可以得到它標識的內存塊的地址,

而指針變數呢,它本身也是一個變數名,只不過它標識的那塊內存存放的是一個地址值

變數是地址的別名..就像剛生的小孩,你只知道他在地球上的某個位置,而不能叫出他名字,給你取個名

~~~~~~~~~~~~~~~~~~~~~~~

定義int a;時,編譯器分配4個位元組內存,並命名該4個位元組的空間名字為a(即變數名),當用到變數名a時,就是在使用那4個位元組的內存空間.

5是一個常數,在程序編譯時存放在代碼的常量區存放著它的值(就是5),當執行a=5時,程序將5這個常量拷貝到a所在的4個位元組空間中,就完成了賦值操作.

a是我們對那個整形變數的4個位元組取的"名字",

是我們人為給的,實際上計算機並不存儲a這個名字,只是我們編程時給那4個位元組內存取個名字好用.實際上程序在編譯時,所有的a都轉換為了那個地址空間了.編譯成機器代碼後,沒有a這個說法了.a這個名字只存在於我們編寫的代碼中.

5不是被隨機分配的,而總是位於程序的數據段中,可能在不同的機器上在數據段中的位置可能不一致,它的地址其實不能以我們常用到的內存地址來理解,因為牽扯到一個叫"計算機定址方式"的問題,所以寫很多都解釋不清楚,你自己找本彙編語言的書來學一下吧.........

~~~~~~~~~~~~~~~~~~~~~~~~~~~~

C語言中變數只是標識對應存儲單元內的存儲內容。與地址的對應關係

int a=3;

a---a

一一對應啊,變數名只是一個便於記憶識別的名稱,編譯器會將他編譯成相應的內存地址的.

變數都要佔據一定的內存。

通過定義該變數的指針, [類型]* 指針名=你要指向的變數名

那麼該指針中存儲的就是你的變數的內存地址。

你的變數名 這樣就可以直接獲取到你的變數地址

或者定義引用 [類型] 引用名=變數名

該引用可通過變數的地址來對變數進行修改

~~~~~~~~~~~~~~~~~~~~~~~~~

變數名是給編譯器看的,編譯器根據變數是局部還是全局分配內存地址或棧空間,所謂的變數名在內存中不存在,操作時轉換成地址數存放在寄存器中了。

編譯器會將合法的變數名放到一個叫「符號表」的一個表中。

每個符號對應一個地址。當你調用此變數時,就會根據此符號表找到對應的地址,然後進行操作。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~

還沒有運行怎麼會佔用內存呢?!(這一點還要懷疑嗎!?)

所謂在編譯期間分配空間指的是靜態分配空間(相對於用new動態申請空間),如全局變數或靜態變數(包括一些複雜類型的常量),它們所需要的空間大小可以明確計算出來,並且不會再改變,因此它們可以直接存放在可執行文件的特定的節里(而且包含初始化的值),程序運行時也是直接將這個節載入到特定的段中,不必在程序運行期間用額外的代碼來產生這些變數。

其實在運行期間再看「變數」這個概念就不再具備編譯期間那麼多的屬性了(諸如名稱,類型,作用域,生存期等等),對應的只是一塊內存(只有首址和大小),所以在運行期間動態申請的空間,是需要額外的代碼維護,以確保不同變數不會混用內存。比如寫new表示有一塊內存已經被佔用了,其它變數就不能再用它了; 寫delete表示這塊內存自由了,可以被其它變數使用了。(通常我們都是通過變數來使用內存的,就編碼而言變數是給內存塊起了個名字,用以區分彼此)

內存申請和釋放時機很重要,過早會丟失數據,過遲會耗費內存。特定情況下編譯器可以幫我們完成這項複雜的工作(增加額外的代碼維護內存空間,實現申請和釋放)。從這個意義上講,局部自動變數也是由編譯器負責分配空間的。進一步講,內存管理用到了我們常常掛在嘴邊的堆和棧這兩種數據結構。

最後對於「編譯器分配空間」這種不嚴謹的說法,你可以理解成編譯期間它為你規劃好了這些變數的內存使用方案,這個方案寫到可執行文件裡面了(該文件中包含若干並非出自你大腦衍生的代碼),直到程序運行時才真正拿出來執行!


我嘗試從由底向上來解釋題主的疑問,順便推薦王爽的《彙編語言》,看了前幾章題主應該就明白了,了解一點彙編,CPU工作原理和編譯知識還是有必要的

1.機器語言

機器語言是機器指令的集合。電子計算機的機器指令是-列二進位數字。計算機將之轉變為一列高低電平,以使計算機的電子器件受到驅動,進行運算。

如應用8086CPU 完成運算s=768+12288-1280,機器碼如下:

101100000000000000000011

000001010000000000110000

001011010000000000000101

每行代表一個指令,我只是搬運工,反正看不懂,現在估計也找不到幾個不靠工具看得懂這玩意的人了~

[關於變數名]:機器語言中沒有變數名的概念,一切操作都是直接對地址進行

2.彙編語言

早期的程序員們很快就發現了使用機器語言帶來的麻煩,它是如此難於辨別和記憶,給整個產業的發展帶來了障礙。於是彙編語言產生了。

彙編語言與機器語言是每個指令是一一對應的,最終由彙編器把寫有彙編語言的文本文件編譯成可執行程序。

例如:將寄存器BX內的數據到AX中(寄存器是CPU內部的一組存儲器,大多數指令都需要先將內存里的數據讀入寄存器後才能開始運算。AX,BX是其中兩個寄存器的代號)

機器指令:1000100111011000

彙編指令:mov ax,bx

[關於變數名]:彙編語言中就有變數的概念。在編譯時由彙編器計算相關變數的偏移或實際地址,在編譯出的二進位機器語言中直接使用該地址操作內存。

3.C語言

程序員們後來發現,用彙編語言寫程序還是麻煩,因為它和機器指令意義對應,更加接近CPU的思維,而不是人的思維。於是發明了很多高級語言,C就是其中的一種。

C語言編譯的過程,實際上首先通過 「編譯器」將C語言翻譯成彙編語言,再通過「彙編器」將彙編語言轉化成機器代碼,對於編譯器來講,將C轉化成彙編的時候,不是一一對應的關係,也就是說幾行C代碼,可能翻譯成幾十行彙編。而彙編語言指令和機器代碼指令,從某種意義來講,是一個東西,兩者是一一對應的關係。

[關於變數名]:C語言中處處是變數,即使是個指針,它自身也是個4位元組的變數才能儲存一個地址(32位程序)。編譯後,有輸出文件包括以下兩種:

  1. 可執行文件(xxxx.exe),一段二進位文件,其中代碼段(代碼段,數據段啥的也可以百度了解)的機器指令CPU可以直接識別執行。
  2. 符號文件(xxxx.pdb),記錄了變數和地址的對應信息。僅供調試使用,程序運行時不需要該文件。

4.有了前面的介紹,我再來說說題主的問題

題主問變數名儲存在哪兒,變數名儲存的機制是什麼?回答如下

  1. 程序運行時不需要知道某塊內存對應的變數名,它只是按地址直接操作那塊內存。
  2. C/C++編譯器在編譯時會計算出變數名對應的地址,在底層所有操作該變數的地方,都使用變數對應的地址參與運算。
  3. C/C++編譯器還把變數名和地址對應的關係存在一個文件中的,這類文件叫符號文件。但是程序運行時不需要這些文件,沒有這些文件程序一樣可以正常工作。這些文件只是用來在調試時起作用的。

    看這份反彙編,也就是利用機器語言和彙編語言指令一一對應的關係,把機器語言還原成彙編語言的結果。可以看到,定址都是通過直接用地址,或則寄存器+偏移量等操作完成的,關於變數名的信息在二進位可執行文件里已經完全沒有了。如果在調試時想要恢復這些信息,就需要用到符號文件了。再看下面這份反彙編

    上圖裡有move eax,dword ptr [ebp-3C] 這樣的指令, 而下圖裡有move eax,dword ptr [b]。也就是經過符號文件的幫助,在調試時可以把[ebp-3C]這樣人看不懂的東西,還原成b這種人看得懂的變數名。在Windows下,符號文件的拓展名為pdb,一般xxxx.pdb這樣的文件都是符號文件。如果要說計算機儲存了變數名,在彙編/C/C++下應該指的就是這種了。再次強調程序運行時不需要知道某塊內存對應的變數名,編譯器已經將C語言翻譯成二進位的機器語言,機器語言中沒有變數名的概念,一切操作都是直接對地址進行,變數名只是保存在另一個與運行無關的文件里供人調試時使用的。

  4. 當然上面是針對彙編/C/C++等編譯語言說的。如果是Python等解釋語言的話,其變數名和內存地址之類的信息解釋器(解釋器,編譯器分不清楚自行百度)里是有相關記錄的,具體是什麼方式每研究過,Python可以參考《Python源碼剖析》一書。
  5. 如果題主是想知道編譯時a的地址是怎麼確定的,變數a及它的地址編譯器是儲存在一個什麼數據結構里之類的話,可以好好看看編譯原理,我沒研究過


變數名是為了方面人閱讀滴,定義一個變數類型就意味著編譯器為它準備多少位元組內存,比如int類型是4個位元組,int a=5;那麼編譯器會把5存在相應滴棧里,並會記住它第一個位元組滴地址。需要a參加運算時候,就到相應滴地址把5這個值取出即可。(這是局部變數滴情形)如何是全局變數那麼int a=5;是存在堆中,但也是不需要有a這個變數名的存儲滴。變數名是為了編程人員容易閱讀和編寫程序而制定滴語法形式而已。你可以直接用說明符%p來跟蹤下a滴內存地址就明白了。


請學習彙編語言,這能讓你了解很多高級語言的底層實現

推薦教材:王爽《彙編語言》


請學計算機體系結構和編譯原理


少年,去看csapp吧


推薦閱讀:

編程中你們都習慣怎麼使用大括弧?
C++中char是如何在地址中存儲的?
C++中為什麼說在構造器或析構器中使用異常處理會可能產生嚴重的問題?
如何理解 struct 的內存對齊?
C++內存劃分類型?

TAG:程序員 | 編程語言 | C編程語言 | C |