浅析VS编译开关: /RTCc、/RTCu、/RTCs

 在vs2008的编译器中有如下3个编译选项,设置的地方如下图


Smaller Type check  (/RTCc):数据截断的检测

   我们看如下代码

         #include "stdafx.h"

#include

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

 

int _tmain(int argc, _TCHAR* argv[])

{

    unsigned char cData;

    cData++;

    return 0;

}

 

如果我们开启/RTCu,那么就会出现如下错误,告诉你没有被初始化过。


OK,那么为什么会这样呢?编译器是怎么做的?我们alt+8看2种情况的汇编代码。如下:

 

    那么是否没有初始化过的都可以校验出来呢?那我们来看下面一段代码。

#include "stdafx.h"

#include

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

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

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(@_RTC_CheckStackVars@8) (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  

好了,文章到这里结束,我们只要知道每个编译的开关的原理、知道它它是如何工作的、可以在什么范围帮助我们发现解决问题。

你可能感兴趣的:(浅析VS编译开关: /RTCc、/RTCu、/RTCs)