1. 程式人生 > >淺析VS編譯開關: /RTCc、/RTCu、/RTCs

淺析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  

好了,文章到這裡結束,我們只要知道每個編譯的開關的原理、知道它它是如何工作的、可以在什麼範圍幫助我們發現解決問題。