編譯50字節代碼耗費4G內存
想用宏和內聯匯編做些邪惡的事情(僅僅試著做一些怪異的測試,目的無關緊要),我決定寫個程序讓Vistual Studio的C++編譯器分配4GB的內存,然后處于卡死狀態。
寫個50字節的代碼就可以了。
一開始我可能沒注意到我的機子并沒有4GB的空閑內存,瘋狂的數據分頁,需要找到4GB的內存,使得我的筆記本在幾分鐘內都毫無反應。如果你的機子有超過4GB的空閑內存,使用ETW做內存分析倒是個很好的測試,看看你可以復現我的結果嗎?
我簡化了代碼,只留下的最基本的精華部分,只是覺得這樣很好玩:
- void test()
- {
- __asm { add eax
- __asm { add eax
- }
這是編譯器的輸出:
- error C2414: illegal number of operands
- error C2414: illegal number of operands
- error C2400: inline assembler syntax error in ‘opcode’; found ‘end of file’
- fatal error C1060: compiler is out of heap space
我是在Windows 64位上運行的,編譯器是32位的大型地址進程,所以堆空間耗盡意味著分配大約4GB的內存空間。我連續進行多次編譯,可以看出每次4GB的內存使用量都在飆升。
我很好奇,到底是哪部分編譯程序在分配這些內存。我使用cl.exe編譯器,用etwheap.bat記錄了所有的堆空間和 VirtualAlloc 分配 ,并再次編譯了源文件。事實證明我本應該使用wprui’s VAlloc Usage選項去獲取追蹤。僅僅只有幾MB是從堆上分配的,大部分都是使用VirtualAlloc來分配的,如圖所示:
接下來,為了完成調查,我查看了所有的調用棧。我們可以看到內聯匯編程序的語法分析器正在使用它自己的VirtualHeap分配大量的Asm Tokens。VirtualHeap::Create 預留內存空間,VirtualHeap::HeapExtend提交內存。再深入研究下(沒有顯示)發現內存空間預留在512KB的內存塊,被提交在 32KB的內存塊。
還有一些細節,不是很清楚,像為什么VirtualHeap::HeapExtend調用 VirtualHeap::Create,但是卻沒有源代碼,難以得知。
所以我們不再探究了,我像往常一樣將把這個問題提交給VC++團隊。如果他們解決了這個問題,我并不驚訝,這也算不上是一個嚴重的問題。第一次遇到這個問題時,因為我的機子沒有4GB的空閑內存,所以才注意到它。
編譯器是32位進程也是件好事兒,要不然它還會繼續消耗內存,將遠遠超過4GB。條件限制萬歲!
這些測試都是在VC++ 2010 的調試版本上進行的,我沒試過其他版本。
Linux 變體
還有一個很類似的問題(鏈接器在一個很簡單的程序上消耗了大量內存,詳情見 棧溢出)。
Windows 糟透了?
我預料到有人會說Windows太爛了,這就是為什么當遇到這個問題時,我的筆記本幾分鐘內都毫無反應。但是如果在Linux和OSX系統上分配 (或寫入)4GB的內存,并不會引發嚴重的系統延遲問題,但其實這說明不了什么。我的筆記本只有8GB的內存,大部分都在被使用,想獲取到空閑的4GB內 存的唯一可能的辦法就是把大量的數據寫到磁盤上。筆記本的硬盤相當慢,如果我是在工作機子上(32GB的內存,20GB可用)或者當筆記本上只有很少的程 序在運行時(5GB空閑內存),做同樣的測試,4GB內存的分配和釋放不到5秒中就可以完成。
Reddit的討論鏈接在這兒。
額外補充
很奇怪,怎么會有一些博客文章比其他人的更受歡迎…
有人在復現這個問題時遇到了困難,這個bug只能確定在VS2010 SP1 出現,并且test函數放在源文件的最后。
這顯然不是一個嚴重的bug—代碼也有缺陷,編譯器有給出了警告并且指出問題所在,也沒出現什么大問題。但是它的確是一個失敗的詞法分析程序。尤其 是,內存不足會阻止VC++去報告一些括號不匹配的問題—假如你在test函數之后再添加一個函數,詞法分析完成后,額外的警告就會顯示出來了。
編譯錯誤代碼時給出出錯的信息提示是很重要的,這也是Clang的顯式設計目標之一。
在 Visual Studio SP1 中復現這個 Bug 了。見下圖或查看原圖。
原文鏈接:http://randomascii.wordpress.com/2013/08/14/50-bytes-of-code-that-took-4-gb-to-compile/