變數儲存機制
本文轉載自https://www.cnblogs.com/vipchenwei/p/8686472.html
他也是轉載的,但是他裡面的部落格有些指的一看
本文轉載於:https://www.zhihu.com/question/34266997?from=profile_question_card
問題:
比如 int a = 5
我知道有一塊記憶體,存了這個值5,a代表了這塊記憶體。就好像給一個盒子起名叫a,這個盒子裡裝了5,現實中這個名字,也就是a只是存在人腦裡的,不是實話的。
另外a,b等字元在計算機裡用很小的整數儲存,也就是a和65對應。
我的問題是 a = 5 的時候,有一塊記憶體存了5,也就是這塊記憶體是00000101,但計算機怎麼知道這塊記憶體叫a?這個名字a是在哪裡儲存的?
我的理解是否有一塊專門儲存變數名的記憶體,比如根據順序第x塊存了65,這個65又存了00000101的地址?但這樣豈不是每個變數都是指標?
解答:
變數名不佔空間
變數:用來標識(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
[關於變數名]:組合語言中就有變數的概念。在編譯時由彙編器計算相關變數的偏移或實際地址,在編譯出的二進位制機器語言中直接使用該地址操作記憶體。
程式設計師們後來發現,用匯編語言寫程式還是麻煩,因為它和機器指令意義對應,更加接近CPU的思維,而不是人的思維。於是發明了很多高階語言,C就是其中的一種。
C語言編譯的過程,實際上首先通過 “編譯器”將C語言翻譯成組合語言,再通過“彙編器”將組合語言轉化成機器程式碼,對於編譯器來講,將C轉化成彙編的時候,不是一一對應的關係,也就是說幾行C程式碼,可能翻譯成幾十行彙編。而組合語言指令和機器程式碼指令,從某種意義來講,是一個東西,兩者是一一對應的關係。
[關於變數名]:C語言中處處是變數,即使是個指標,它自身也是個4位元組的變數才能儲存一個地址(32位程式)。編譯後,有輸出檔案包括以下兩種:
- 可執行檔案(xxxx.exe),一段二進位制檔案,其中程式碼段(程式碼段,資料段啥的也可以百度瞭解)的機器指令CPU可以直接識別執行。
- 符號檔案(xxxx.pdb),記錄了變數和地址的對應資訊。僅供除錯使用,程式執行時不需要該檔案。
題主問變數名儲存在哪兒,變數名儲存的機制是什麼?回答如下
- 程式執行時不需要知道某塊記憶體對應的變數名,它只是按地址直接操作那塊記憶體。
- C/C++編譯器在編譯時會計算出變數名對應的地址,在底層所有操作該變數的地方,都使用變數對應的地址參與運算。
- C/C++編譯器還把變數名和地址對應的關係存在一個檔案中的,這類檔案叫符號檔案。但是程式執行時不需要這些檔案,沒有這些檔案程式一樣可以正常工作。這些檔案只是用來在除錯時起作用的。看這份反彙編也就是利用機器語言和組合語言指令一一對應的關係,把機器語言還原成組合語言的結果。可以看到,定址都是通過直接用地址,或則暫存器+偏移量等操作完成的,關於變數名的資訊在二進位制可執行檔案裡已經完全沒有了。如果在除錯時想要恢復這些資訊,就需要用到符號檔案了。再看下面這份反彙編上圖裡有move eax,dword ptr [ebp-3C] 這樣的指令, 而下圖裡有move eax,dword ptr [b]。也就是經過符號檔案的幫助,在除錯時可以把[ebp-3C]這樣人看不懂的東西,還原成b這種人看得懂的變數名。在Windows下,符號檔案的拓展名為pdb,一般xxxx.pdb這樣的檔案都是符號檔案。如果要說計算機儲存了變數名,在彙編/C/C++下應該指的就是這種了。再次強調程式執行時不需要知道某塊記憶體對應的變數名,編譯器已經將C語言翻譯成二進位制的機器語言,機器語言中沒有變數名的概念,一切操作都是直接對地址進行,變數名只是儲存在另一個與執行無關的檔案裡供人除錯時使用的。
- 當然上面是針對彙編/C/C++等編譯語言說的。如果是Python等解釋語言的話,其變數名和記憶體地址之類的資訊直譯器(直譯器,編譯器分不清楚自行百度)裡是有相關記錄的,具體是什麼方式每研究過,Python可以參考《Python原始碼剖析》一書。
- 如果題主是想知道編譯時a的地址是怎麼確定的,變數a及它的地址編譯器是儲存在一個什麼資料結構裡之類的話,可以好好看看編譯原理,我沒研究過