bug诞生记——const_cast引发只读数据区域写违例
? ? ? ? 對于C++這種強類型的語言,明確的類型既帶來了執行的高效,又讓錯誤的發生提前到編譯期。所以像const這類體現設計者意圖的關鍵字,可以隱性的透露給我們它描述的對象的使用邊界。它是我們的朋友,我們要學會和它相處,而不是改變它。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 我們來看一個試圖改變這個好朋友的案例
class Base {
public:Base() : _name("Base") {}const char* get_name() const {return _name;}
private:const char _name[32];
};
? ? ? ? Base類有個get_name方法返回了一個const char*。它返回的是該類的成員變量_name——即類名"base"。
? ? ? ? 現在有個同學試圖繼承該類,于是他增加了如下代碼
class Derive : public Base {
public:Derive() {const char* base_name = Base::get_name();strcpy(const_cast<char*>(base_name), "Derive");}
};
? ? ? ? Derive的構造函數調用了Base的get_name方法獲取了一個const char*變量base_name。他試圖去修改這個地址空間的數據,讓其填充自己類的名稱"Derive"。由于strcpy需要第一個參數是char*型的,于是他使用const_cast關鍵字“剝奪”了base_name的const屬性,讓編譯通過。
? ? ? ? 目前這段代碼還可以正確執行,但是之后一個同學對Base類的優化,將徹底觸發修改const屬性引發的災難。
class Base {
public:const char* get_name() const {return _name;}
private:const char* _name = "Base";
}
? ? ? ? 這樣編譯出來的代碼,最終在執行期會崩潰。
? ? ? ? 先分析Base的get_name方法的反匯編代碼
push ebp
mov ebp, esp
sub esp, 0CCh
push ebx
push esi
push edi
push ecx
lea edi, [ebp+var_CC]
mov ecx, 33h
mov eax, 0CCCCCCCCh
rep stosd
pop ecx
mov [ebp+var_8], ecx
mov eax, [ebp+var_8]
mov dword ptr [eax], offset aBase ; "Base"
mov eax, [ebp+var_8]
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
retn
? ? ? ? 第15行將"Base"字符串的地址保存到寄存器eax指向的地址空間中。之后該方法返回
push offset Source ; "Derive"
mov eax, [ebp+Dest]
push eax ; Dest
call j_strcpy_0
? ? ? ? 調用了strcpy方法,其中eax還是"Base"字符串的首地址。
? ? ? ? 崩潰出現在strcpy方法中,出錯的地址也是“Base"字符串的首地址。
? ? ? ? 為什么寫這個地址會出錯,我們看下get_name中aBase的地址
.rdata:0041DB30 aBase db 'Base',0 ; DATA XREF: sub_412460+26↑o
.rdata:0041DB35 db 0
.rdata:0041DB36 db 0
.rdata:0041DB37 db 0
.rdata:0041DB38 db 0
? ? ? ? 可以見到這個地址保存在rdata區域。rdata是readonly-data的意思,即這塊地址空間是只讀的,所以往其中寫數據會報錯。
? ? ? ? 由于我們在修改后的Base類中,讓成員變量_name指向了一個字面量。這個字面量作為常量,它會保存在PE/ELF文件的只讀數據區域。關于什么信息會保存在只讀區域,以及還有什么其他區域,大家可以在網上搜索PE/ELF文件格式的說明。
? ? ? ? 最后從語義角度來說,可以認為Derive的作者違背了編譯器對關鍵字const的約束,即違背了一種約定導致的。所以我們盡量別用const_cast這種試圖繞過編譯器的“小聰明”手段。
總結
以上是生活随笔為你收集整理的bug诞生记——const_cast引发只读数据区域写违例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bug诞生记——临时变量、栈变量导致的双
- 下一篇: ffmpeg api的应用——提取视频图