Windows Via C/C++ Part Ⅰ Chapter 2: 字符和字符串(3)

来源:互联网 发布:皮卡ct630端口设置 编辑:程序博客网 时间:2024/04/27 17:09

(五) CRT中的安全字符串函数

  涉及修改字符串的函数都面临潜在的危险:当目标缓冲区不足以容纳产生的字符串时会产生内存错误。比如下面的代码:

这些函数的问题在于它们没有定义参数以指示目标缓冲区的大小,因此函数不会知道自己的调用产生了内存错误,这样开发人员也无从知道。这是很危险的,最好的情形是如果发生内存错误则让函数以失败形式返回而不去更改任何内存数据。这种特性过去经常被恶意软件利用,现在微软提供了新的解决方案——一组安全的字符串函数。使用这些新的函数替代代码中旧的CRT字符串函数会使代码更加健壮。 


1 安全字符串函数简介

  每一个CRT字符串函数在tchar.h头文件中都对应着新的安全字符串函数,后者的函数名是原函数名加上_s后缀,比如_tcscpy的安全版本是_tcscpy_s。所有的安全字符串函数都有共同的特征,如下所示:

在安全字符串函数中,如果某参数指向一块可写的内存,那么必须用另外一个参数提供这块内存的大小——以字符数目的形式,通过_countof宏我们可以获取该参数的值。

  大多数CRT函数在执行前会检查参数的有效性:指针是否非NULL、整数值是否在有效范围之内、枚举值是否有效、缓冲区是否足够大等等。如果检查失败,函数将设置本线程的CRT变量errno为相应的值并将该值以errno_t类型返回。但是,一般情况下如果检查失败,这些函数从不返回,在debug模式下它将产生一个断言失败对话框,在realse模式下将产生一个异常对话框并中止,这种并不友好的提示并不是我们期望的。事实上CRT允许我们自定义一个_invalid_parameter_handler类型的回调函数,_invalid_parameter_handler在stdlib中定义,其原型为:

expression表示参数检查失败的具体错误信息,function为产生错误的函数名,file为定义该函数的CRT源文件名,line为file中的行号,最后一个参数为系统保留。这些参数仅在DEBUG模式下有意义,否则均为空值。定义了回调函数之后,调用_set_invalid_parameter_handler注册它。现在,你可以在回调函数中将错误信息打印出来或写入日志,并且即使参数检查失败,它只是返回失败值并继续执行而不是中断程序。但是断言失败(DEBUG)对话框仍然会出现,可以通过调用_CrtSetReportMode(_CRT_ASSERT, 0)阻止程序显示它。通过上面的做法,你可以检查函数返回的errno_t类型值而不用担心程序中断了,下面是一个完整的例子:  

下面是运行结果截图:

图1- _tcscpy_s参数检查失败后各变量的值 

注意rc和szBuffer的值

图2- _tprintf参数检查失败后各变量的值

注意rc值的变化

图3- 整个程序的console输出

console窗口的打印结果


 2 更灵活的字符串处理

除了上面提到的安全字符串函数,CRT另外定义了一些函数对字符串操作提供更加灵活的控制。比如在目标缓冲区太小时进行截断插入或者控制目标缓冲区的填充方式。这些函数包括ANSI和Unicode版本,下面是一些例子:

这些函数名中的Cch表示Count of Characters,作为函数要求的参数可以用_countof宏得到该值。与之相对应有一组名称中含有Cb的同功能函数,如StringCbCat(Ex)等,这些函数要求传递的缓冲区大小参数用字节而不是字符数目来表示,sizeof运算符可以得到该值。这些函数的返回值意义如下:

  •  S_OK:函数调用成功,目标缓冲区以'/0'结尾;
  • STRSAFE_E_INVALID_PARAMETER:失败,检测到无效的空参数;
  • STRSAFE_E_INSUFFICIENT_BUFFER:失败,缓冲区太小无法容纳目标字符串。

如果函数返回STRSAFE_E_INSUFFICIENT_BUFFER,虽然看起来函数调用失败了,但它会进行截断处理——将缓冲区用目标数据或用户指定的数据填充,这与_s版本的安全函数所做的截然不同。比如在上一节的代码中,如果调用StringCchCopy而不是_tcscpy_s,那么szBuffer在调用后会包含“012345678”,但函数依然返回STRSAFE_E_INSUFFICIENT_BUFFER表示调用失败。这种截断插入的特性可能并不是你想要的:如果缓冲区用于接收一个完整的路径那么截断结果是不可用的,但是如果缓冲区用来接收向用户的反馈信息,这似乎是可以接受的——总之,是否使用截断版的函数及如何处理截断后的结果完全取决于你自己。

接下来看看如StringCchCopyEx这样的扩展函数其它参数的意义:

  • size_t *pcchRemaining:输出参数,表示函数操作完成后缓冲区剩余的空间大小,不包括结束符'/0';
  • LPTSTR *ppszDestEnd:输出参数,指向函数返回后缓冲区中结束符所在位置;
  • DWORD dwFlags:可由以下常量组合——STRSAFE_FILL_BEHIND_NULL、STRSAFE_IGNORE_NULLS、STRSAFE_FILL_ON_FAILURE、STRSAFE_NULL_ON_FAILURE、STRSAFE_NO_TRUNCATION,具体含义请查阅MSDN文档。可以使用这些常量控制函数失败时的截断操作处理,比如当你不想花费时间让函数填充一个很大的缓冲区时,可以使用STRSAFE_NULL_ON_FAILURE。


3 Windows 字符串函数

Windows也提供了一些字符串操作函数,但是其中类似lstrcat、lstrcpy等已不再推荐使用——它们并不会检测缓冲区溢出问题。Windows在ShlwApi.h定义了一些用来格式化扣作系统相关数值的函数,如StrFormatKBSize、StrFormatByteSize等,具体用法可查阅MSDN(http://msdn2.microsoft.com/en-us/library/ms538658.aspx )。

[本节介绍的另外两个字符串比较函数较简单,不再翻译] 

原创粉丝点击