在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(@_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
好了,文章到这里结束,我们只要知道每个编译的开关的原理、知道它它是如何工作的、可以在什么范围帮助我们发现解决问题。
本文深入解析了Visual Studio 2008中的三个编译选项:数据截断检测、未初始化变量检测与栈检测,详细介绍了它们的工作原理及应用场景,帮助开发者提升代码质量和安全性。
522

被折叠的 条评论
为什么被折叠?



