C++常見的三種內(nèi)存破壞的場(chǎng)景和分析

比如某個(gè)變量整形,在程序中只可能初始化或者賦值為 1或者2, 但是在使用的時(shí)候卻發(fā)現(xiàn)其為0或者其他的情況。對(duì)于其他類型,比如字符串等,可能出現(xiàn)了一種出乎意料的值!程序在堆上申請(qǐng)內(nèi)存或者釋放內(nèi)存的時(shí)候,在內(nèi)存充足的情況下,居然出現(xiàn)了堆錯(cuò)誤。
1. 內(nèi)存破壞之強(qiáng)制類型轉(zhuǎn)換
#include#includeclass DemoClass{public:DemoClass() : m_bInit(true), m_tRecordTime(0){time((time_t *)(&m_tRecordTime));};void DoSomething(){if (m_bInit)std::cout << "Do Task!" << std::endl;}private:int m_tRecordTime;bool m_bInit;};int main(){DemoClass testObj;testObj.DoSomething();return 0;}
time_t time( time_t *destTime );,在VC6中time_t默認(rèn)是32位,而在VS2017中默認(rèn)是64位。早期程序以為32位中表達(dá)最大的時(shí)間是2038年,那時(shí)候完全夠用,但隨著計(jì)算機(jī)本身的發(fā)展64位逐漸成為主流time_t在最新的編譯器中也默認(rèn)采用64位,這樣時(shí)間完全夠用以億年為單位了,那時(shí)候計(jì)算機(jī)發(fā)展超出我們想象了。程序的問題所在m_tRecordTime采用的是int類型,默認(rèn)為32位,那么其地址作為time_t time( time_t *destTime );函數(shù)實(shí)參后,在VC6中time_t本身為32位自然也不會(huì)出錯(cuò),但是在VS2017中因?yàn)?code style="box-sizing: border-box;font-family: "Source Code Pro", "DejaVu Sans Mono", "Ubuntu Mono", "Anonymous Pro", "Droid Sans Mono", Menlo, Monaco, Consolas, Inconsolata, Courier, monospace, "PingFang SC", "Microsoft YaHei", sans-serif;font-size: 14px;background-color: rgb(249, 242, 244);border-radius: 2px;padding: 2px 4px;line-height: 22px;color: rgb(199, 37, 78);">time_t為64位,則time((time_t *)(&m_tRecordTime));后寫入了一個(gè)64位的值。結(jié)合下圖,看下這個(gè)對(duì)象的內(nèi)存布局,m_bInit的值將會(huì)被覆蓋,而這里原先的m_bInit的值為1,被覆蓋為0,從而導(dǎo)致內(nèi)存破壞,導(dǎo)致程序執(zhí)行意想不到的結(jié)果。這里只是不輸出,那在真實(shí)程序中,可能會(huì)導(dǎo)致某個(gè)邏輯錯(cuò)亂,發(fā)生嚴(yán)重的問題。
m_tRecordTime定義為time_t類型就可以了。在定義類型的時(shí)候,盡量和原始類型一致,比如這里的 time_t有些程序員可能慣性的認(rèn)為就是32位,那就定義一個(gè)時(shí)間戳的時(shí)候就定義為int了,而我們要做的應(yīng)該是和原始類型匹配(也就是函數(shù)的輸入類型),將其定義為time_t,于此類似的還有size_t等,這樣可以避免未來在數(shù)據(jù)集變化或者做平臺(tái)遷移的時(shí)候造成不必要的麻煩。在有一些復(fù)雜的場(chǎng)景的下,也許你不得不做類型轉(zhuǎn)換,而這個(gè)時(shí)候就格外的需要注意或者了解清楚,轉(zhuǎn)換帶來的情況和后果,保持警惕,否則就可能是一個(gè)潛在的 bug。這和開車一樣,當(dāng)你開車的時(shí)候如果看到前方車輛忽然產(chǎn)生一個(gè)不合常理的變道行為,首先要做的不是噴那輛車,而是集中注意力,看看是否更前方有障礙物或者事故放生,做出相應(yīng)的反應(yīng)。
2. 字符串拷貝溢出
#include#define BUFER_SIZE_STR_1 5#define BUFER_SIZE_STR_2 8class DemoClass{public:void DoSomething(){strcpy(m_str1, "Hi Coder!");std::cout << m_str1 << std::endl;std::cout << m_str2 << std::endl;}private:char m_str1[BUFER_SIZE_STR_1] = { 0 };char m_str2[BUFER_SIZE_STR_2] = { 0 };};int main(){DemoClass testObj;testObj.DoSomething();return 0;}
errno_t strcpy_s(
char *dest,
rsize_t dest_size,
const char *src
);
3. 隨機(jī)性的內(nèi)存被修改
#include#define BUFER_SIZE_STR_1 5#define BUFER_SIZE_STR_2 8class DemoClass{public:void DoSomething(){strcpy_s(m_str2, BUFER_SIZE_STR_2, "Coder");strcpy_s(m_str1, BUFER_SIZE_STR_1, "Test");//Notice this line:m_str1[BUFER_SIZE_STR_2 - 1] = '\0';std::cout << m_str1 << std::endl;std::cout << m_str2 << std::endl;}private:char m_str1[BUFER_SIZE_STR_1] = { 0 };char m_str2[BUFER_SIZE_STR_2] = { 0 };};int main(){DemoClass testObj;testObj.DoSomething();return 0;}
#include#define BUFER_SIZE_STR_1 5#define BUFER_SIZE_STR_2 8#define BUFFER_SIZE_UNUSED 100class DemoClass{public:void DoSomething(){strcpy_s(m_str2, BUFER_SIZE_STR_2, "Coder");strcpy_s(m_str1, BUFER_SIZE_STR_1, "Test");//Notice this line:m_str1[BUFER_SIZE_STR_2 - 1] = '\0';std::cout << m_str1 << std::endl;std::cout << m_str2 << std::endl;}private:char m_str1[BUFER_SIZE_STR_1] = { 0 };char m_strUnused[BUFFER_SIZE_UNUSED] = { 0 };char m_str2[BUFER_SIZE_STR_2] = { 0 };};int main(){DemoClass testObj;testObj.DoSomething();return 0;}
0:000> bp ObjectMemberBufferOverFllow!main*** WARNING: Unable to verify checksum for ObjectMemberBufferOverFllow.exe0:000> gBreakpoint 0 hiteax=010964c0 ebx=00e66000 ecx=00000000 edx=00000000 esi=75aae0b0 edi=0109b390eip=003a1700 esp=00defa00 ebp=00defa44 iopl=0 nv up ei pl nz na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206ObjectMemberBufferOverFllow!main:003a1700 55 push ebp
0:000> dv /t /v00def984 class DemoClass testObj = class DemoClass
0:000> ba w1 00def984+70:000> g
0:000> k# ChildEBP RetAddr00 00def97c 003a1720 ObjectMemberBufferOverFllow!DemoClass::DoSomething+0x41 [......\strcpybufferoverflow.cpp @ 16]01 00def9fc 003a1906 ObjectMemberBufferOverFllow!main+0x20 [......\strcpybufferoverflow.cpp @ 30]02 (Inline) -------- ObjectMemberBufferOverFllow!invoke_main+0x1c [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]03 00defa44 75818494 ObjectMemberBufferOverFllow!__scrt_common_main_seh+0xfa [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]04 00defa58 770a40e8 KERNEL32!BaseThreadInitThunk+0x2405 00defaa0 770a40b8 ntdll!__RtlUserThreadStart+0x2f06 00defab0 00000000 ntdll!_RtlUserThreadStart+0x1b


