C語言內存中是否存在一個區域,存儲著變數的符號,變數的類型和變數的首地址?

學習C語言時,在想當我們要訪問一個變數時,程序員只輸入了變數的標識符,然後系統,準確的找到了在內存中找到了這個變數的存放位置,並且讀取了合適的位元組長度。那麼,是否類似於在內存中存在著,一個空間,記錄著所有標識符,和首地址和類型的關係啊?謝謝(程序小白知乎首問)


C語言標準應該沒有說編譯後的東西是怎樣的,或可以說,即使編譯結果含有問題中的信息,也沒有符合C語言標準的方式去獲取這些信息。

而正常的編譯器在一般情況下不會儲存這些信息,但是為了調試或性能剖析,常會把類似的信息(如debug symbol)儲存在可執行文件或其他調試文件中,所以在調試器里你是能看到標識符與其值的對應關係。


通常沒有,而是在編譯的時候,把那塊內存當作某種類型使用。

指針類型的變數,其內容就是存儲某一個東西的「首地址」,用於運行時可以變化的效果。C++簡陋的RTTI系統,才提供了運行時的類型標籤。

腳本語言才會真的存一個變數名和函數名的表,以及類型信息,而且它們的變數通常不是基本類型,函數也不是native函數。


在C/C++的源碼編譯後,是不會存在「變數名」/「變數符號」這種東西了,一切都轉換為實際的地址(或者和棧頂/EBP的偏移)操作了。而這個轉換的步驟,就是在編譯過程中完成的。

當然,有些時候為了調試方便,編譯器會在編譯時,把變數符號對應的地址關係寫到可執行文件中。這樣,如果出了問題,debug會比較方便。例如說gcc編譯時,加上-g參數就ok了。不過即使是這樣,也未必是全部符號都會記錄,有些變數會在編譯過程中被優化掉的,那就真的徹底找不到了。


題主不要想太多,你學了彙編,組成原理,操作系統就慢慢懂了。現在的情況是說了你聽不懂,你著急答主們也著急。


查找的和被查找的都是地址或偏移,不存在符號。除非是debug會有給調試器的符號信息。


題主問這問題,表示題主並不理解計算機的運行過程,對高級語言的編譯過程也不清楚。這是一個常見問題,現在教授編程語言更偏重於教授大家如何表達邏輯,對基礎原理卻不願說明白。

對計算機來說,它的世界裡沒有變數,常量。他生活在一個簡單的世界,它認知的是各類有地址或可定址(可以找到)的存儲空間,寄存器,內存等等,它通過執行指令操作數據。每天做的事情就是按照順序依次執行指令,指令通常會告訴它到某個地址把數據搬來搬去,或者把數據加來減去,或者改變當前執行的指令地址。

高級語言中定義了各類變數,常量,結構體,乃至對象。其實計算機並不認識他們,在計算機看來這些概念不存在,但是對應他們的可能是一塊內存空間,也可能是指令的一部分。為了讓計算機懂得高級語言里的概念,編譯器這個兄弟被發明來幫助進行翻譯工作。

比如編譯器碰到了int a;這樣一條語句,通常他做一件事情,找一個還沒用的地址,在他自己的小本上記下這個地址給了a,a是int型的。當遇到這樣的語句時a++;編譯器找到小本上記的a的地址,還有類型等等信息。形成一條指令,告訴計算機到該地址上取數,放到累加器上加一,結果放到原地址表示的空間裡面。很多條這樣的指令和一堆地址組合起來就是一個應用程序了。

編譯器就完成了高級語言代碼到機器指令之間的翻譯工作,通常情況下,你單擊編譯就是乾的這個事。在運行的時候就沒有變數啦,只有指令,寄存器,內存單元,外設空間等等放數據的地方。


佔個坑,晚上細答。先簡單說下。 很多答案說的結論都是對的,不過我估計剛接觸編程的新人看不懂。

你的想法是這樣:

程序員只輸入了變數的標識符,然後系統,準確的找到了在內存中找到了這個變數的存放位置,並且讀取了合適的位元組長度

簡答:編譯器直接把所有變數翻譯成了地址,執行的時候cpu直接去對應地址取值。

觀眾:廢話么。

那麼,為了清晰地展示這個過程,我碼了一大段字,發現還是不如上圖簡單。首先,我們選取一個非常普通沒有特別意義的數字1024,做個實驗。

按照題主的猜測,計算機會先給a、b各分配一個地址,然後把這兩個地址保存在一個表中。

接下來我要把變數a的值賦給變數b,所以計算機從地址表查找到符號a對應的地址,取值,然後查找符號b對應的地址,把值寫入。

那麼,這段程序編譯完,計算機到底怎麼處理的呢?廣告之後。。。咳咳,見圖,這是調試時顯示的彙編信息,我們造彙編就是方便human理解的CPU指令:

警察蜀黍快看!1024就在這裡,披上16進位的馬甲也沒用!

a=1024是怎麼執行的?程序進入函數時,會被分配一段內存,我們叫他『棧』,這個棧是從頂部開始使用的,棧頂地址保存在CPU的ebp寄存器中。

第11行這裡,-0x10(%ebp)在彙編中我們叫做相對定址,它表示ebp寄存器保存的地址前面0x10個位元組,1024被保存在了棧頂地址的前面0x10個位元組處;

第12行,2048被保存在了棧頂地址的前面0x14個位元組處;

b=a怎麼執行呢?

第13行,把ebp前面第16個位元組(也就是0x10)的內存中的值,保存到eax寄存器中;

第14行,把eax寄存器中的值,寫入-0x14(%ebp),也就是變數b的地址。

你看,計算機壓根不造什麼a啊b啊,他只管把內存中的數據讀出來,處理。至於讀取哪個內存地址的數據,怎麼處理,這些都是由編譯器完成的。

答完睡午覺嘍。


一般來說不需要你說的那種「映射表」。C源代碼裡面你看到的那些「符號」,在編譯連接時基本上都被直接就地替換成了對應的內存的首地址。


題主應該看深入理解計算機系統和程序員的自我修養這兩本書來解答這個問題。

編譯後,可重定位文件會有全局/靜態變數的符號表記錄一些信息。。

靜態鏈接後,可執行文件中的全局/靜態變數在進程虛擬內存地址中會固定位置,代碼也已經正確讀取該位置,函數中的局部自動變數是代碼操作棧的結果。。


C語言的變數在編譯成可執行文件時實際上是寄存器或者一個內存地址。

變數的名字和類型在編譯過程中會放在符號表裡,一旦編譯成機器代碼,這些信息就丟失了。所以要從機器代碼反編譯回C代碼是很難的,經過編譯優化的代碼反編譯回來基本是面目全非了。


  • 存儲著變數的符號

如果使用GCC的-g編譯,會附帶上符號symbol的名字,但是這一般是全局變數和全局函數。

局部或者函數內部的有沒有,我就不太記得了。

  • 變數的類型

程序編譯完成後,在彙編語言或者機器碼裡面,變數已經是處理器能夠處理的類型。

嚴格地說,已經絕大部分地丟失了原始的類型信息。

例如size_t,已經變成了int,除非你能夠從彙編的代碼中猜出來它是size_t。

  • 變數的首地址

不是所有變數都會有「首」地址的吧?

如果是結構體裡面的成員,通過指針訪問的,會用地址的方式表示。

如果是一個簡單的類型變數,那麼可能已經被直接包含在機器碼裡面,不再存在地址。

例如int foo = 99;


沒有,編譯器在編譯的時候就已經完成了變數/函數名到實際地址(或者說是偏移量)的轉換了。

——————————————分割線————————————

順便提一個有趣的事情,vs的編譯器為了能夠讓程序員較快的查出數組下標越界之類的bug,在debug模式下分配內存的時候,每個數組/變數之間都留下一大塊空白,裡面存儲著特定的信息。

這就是有時候初學者的程序輸出詭異的「錕斤拷」,「燙燙燙燙燙」的原因啦╮( ̄▽ ̄")╭


如果是C++的話,有一些途徑。

C++的RTTI可以獲取類型信息,不過要訪問存放這些類型信息的內存的話比較困難。

如果一個對象的類型有虛函數的話,虛函數表的第一個元素的前一個元素會存放一個type_info的類型信息。不過不同的編譯器生成的虛函數表的構成可能不一樣,這個方法也不是總成立,依賴於編譯器的具體實現。


編譯時加了debug info就有。其他有反射機制的語言就一直有


程序在運行的時候才有變數在內存中吧?


推薦閱讀:

c語言為什麼可以通過變數名來訪問變數的值?變數的值是存儲在計算機中,那麼變數名也同時存儲在計算機中嗎?
C 語言有什麼奇技淫巧?
如何快速便捷地打小括弧?
c語言里的char大小到底是4還是1?
為什麼不都用memmove代替memcpy?

TAG:C編程語言 | 程序 |