淺析VS編譯開關: /RTCc、/RTCu、/RTCs
在vs2008的編譯器中有如下3個編譯選項,設定的地方如下圖
Smaller Type check (/RTCc):資料截斷的檢測
我們看如下程式碼
#include "stdafx.h"
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
int nSize = 256;
unsigned char cData = 0;
cData = nSize;
return 0;
}
如果我們打開了/RTCc的開關,我們將會收到如下錯誤。
Alt+8檢視彙編程式碼,我們發現裡面有個_RTC_Check_4_to_1的函式,這就是類型範圍檢測的函式。在VS中有下面一組來檢測資料截斷:
_RTC_Check_2_to_1
_RTC_Check_2_to_1
_RTC_Check_8_to_1
_RTC_Check_8_to_2
_RTC_Check_8_to_4
_RTC_Check_4_to_1
_RTC_Check_4_to_2
比如,_RTC_Check_4_to_1的內部實現,其實就是用0xffffff00h,來判斷是否超過了值域範圍。其它幾個的原理也是一樣的。所以這個開關是不能用來檢測出unsined char、char的範圍的。
對於cData = 0xffffff;這樣的程式碼,我們只要將編譯警告開關到第2級就會收到警告資訊,但是在編譯後代碼中,將直接被截斷。為cData= 0xff;
Uninitialized Variables (/RTCu):未初始話變數
我們看下簡單的程式碼如下:
#include "stdafx.h"
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char cData;
cData++;
return 0;
}
如果我們開啟/RTCu,那麼就會出現如下錯誤,告訴你沒有被初始化過。
OK,那麼為什麼會這樣呢?編譯器是怎麼做的?我們alt+8看2種情況的彙編程式碼。如下:
那麼是否沒有初始化過的都可以校驗出來呢?那我們來看下面一段程式碼。
#include "stdafx.h"
#include <iostream>
void funSetValue(unsigned char &cData)
{
cData++;
}
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char cData;
funSetValue(cData);
cData++;
return 0;
}
程式沒有出現任何的錯誤提示。同樣我們可以用alt+8檢視彙編程式碼,確實我們沒能發現__RTC_UninitUse這個函式。很抱歉,VS現在無法幫忙我們解決這樣的隱藏問題,良好的編碼習慣才是王道,定義變數一定要初始化。
Stack Frames (/RTCs):棧檢測
例子程式碼如下:
#include "stdafx.h"
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char cData[1] = {0};
cData[1] = 0; //非常明顯訪問越界。
return 0;
}
如果我們開啟編譯開關,將得到如下的錯誤。
在開啟編譯選項的基礎上,我們看下cData記憶體單元,如下
我們看到,在申請的記憶體前後均有0xcch的保護區域(跟堆越界檢測原理一致),前面4位元組,後面4個位元組。如果我們修改了,都將會報錯。大家可以試下,將“cData[1]=0;”改為“cData[8]=0;”,程式是否報錯。當然如果我們跨越了這個範圍,系統是無法檢測處理的,所以我們還是要養成自己的習慣,在vs2005後,採用sprintf_s這種帶_s結尾的函式,用來函式自身來檢測字串越界問題。
下面我們看下彙編程式碼如下:
#include "stdafx.h"
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
00412FC0 push ebp
00412FC1 mov ebp,esp
00412FC3 sub esp,0CCh
00412FC9 push ebx
00412FCA push esi
00412FCB push edi
00412FCC lea edi,[ebp-0CCh]
00412FD2 mov ecx,33h
00412FD7 mov eax,0CCCCCCCCh
00412FDC rep stos dword ptr es:[edi]
unsigned char cData[1] = {0};
00412FDE mov byte ptr [cData],0
cData[1] = 0;
00412FE2 mov byte ptr [ebp-4],0
return 0;
00412FE6 xor eax,eax
}
00412FE8 push edx
00412FE9 mov ecx,ebp
00412FEB push eax
00412FEC lea edx,[ (413000h)]
00412FF2 call @ILT+135(@[email protected]) (41108Ch)
00412FF7 pop eax
00412FF8 pop edx
00412FF9 pop edi
00412FFA pop esi
00412FFB pop ebx
00412FFC mov esp,ebp
00412FFE pop ebp
00412FFF ret
/*以下程式碼是呼叫_RTC_CheckStackVars時用來計算的記憶體區域,如果有多個需要變數需要計算,則
會增加*/
00413000 db 01h
00413001 db 00h
00413002 db 00h
00413003 db 00h
00413004 db 08h
00413005 db 30h
00413006 db 41h
00413007 db 00h
00413008 db fbh
00413009 db ffh
0041300A db ffh
0041300B db ffh
0041300C db 01h
0041300D db 00h
0041300E db 00h
0041300F db 00h
00413010 db 14h
00413011 db 30h
00413012 db 41h
00413013 db 00h
00413014 db 63h
00413015 db 44h
00413016 db 61h
好了,文章到這裡結束,我們只要知道每個編譯的開關的原理、知道它它是如何工作的、可以在什麼範圍幫助我們發現解決問題。