从Visual Studio 2005开始编译程序时就会遇到很多warning C4996警告。虽然没有错误但是这个警告估计还是让很多人不爽。MSDN上关于此警告有说"为了支持新的、更安全的函数,否决了某些 CRT 和标准 C++ 库函数,如果出于安全原因使用了否决的 MFC 或 ATL 函数,也可能发生 C4996"。以此为切入点来聊聊微软从Visual Studio 2005开始引入的一项针对程序安全性的新技术SAL(Standand Annotations Language),SAL就是更安全的函数的基石。
传统的代码安全性更加注重运行时程序正确性的保证,更有甚至者防御性的代码几乎占了整个代码的一多半,当然这是好的习惯但为此开发人员也浪费了大量精力和时间,而且在编写代码的逻辑时考虑安全性防御代码会或多或少的打断原本的设计思路,对程序开发人员来说这是安全性、稳定性的代价之一。C\C++语言本身对代码的安全性没有多少假设,这完全依赖开发人员的自觉和严格的代码检查。从另一个角度来看代码安全可以从两个层次上来对待"静态和动态“,静态是指代码在编译时发现更多的问题由编译器或者语法检查器这类工具来发现问题;动态是指在代码中由开发人员自己编写检查代码。相对动态方式来说静态方式更易于早些发现问题,从解决Bug的成本来说越早发现这个Bug就越廉价。SAL应运而生用来以静态方式解决代码安全性问题。
微软官方文档里并没有把SAL放到更加重要的位置,以至于我去找相关信息的时候难以轻松的理出头绪。目前看来SAL只是问题的解决方案和手段并没有独立为一种标准。微软不仅为CRT库函数提供了安全版本也为Window API提供了安全版本。CRT库函数的安全版命名规则为原有函数名加上"_s"后缀,如strcpy的安全版本为strcpy_s,返回值类型、参数个数个类型都未做变更只是加上了SAL修饰。从语法角度上来看SAL其实就是用于被编译器等工具识别的元语言。接下来看看strcpy在源文件中的定义
_Check_return_wat_ _CRTIMP_ALTERNATIVE errno_t __cdecl _strset_s(_Inout_z_cap_(_DstSize) char * _Dst, _In_ size_t _DstSize, _In_ int _Value); __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_1(errno_t, _strset_s, _Deref_prepost_z_ char, _Dest, _In_ int, _Value) __DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(char *, __RETURN_POLICY_DST, __EMPTY_DECLSPEC, _strset, _Inout_z_, char, _Dest, _In_ int, _Value) #if __STDC_WANT_SECURE_LIB__ _Check_return_wat_ _CRTIMP_ALTERNATIVE errno_t __cdecl strcpy_s(_Out_z_cap_(_SizeInBytes) char * _Dst, _In_ rsize_t _SizeInBytes, _In_z_ const char * _Src); #endif __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_1(errno_t, strcpy_s, _Deref_post_z_ char, _Dest, _In_z_ const char *, _Source) __DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(char *, __RETURN_POLICY_DST, __EMPTY_DECLSPEC, strcpy, _Pre_cap_for_(_Source) _Post_z_, char, _Dest, _In_z_ const char *, _Source)
能写出让人眼花缭乱的一堆宏的是何其的伟大:( 此乃吾辈孜孜不倦追求之目标。虽然各位C++大佬反对过多的使用宏并在各种巨著数说C++可替代特性的好处,但是理想与实现有太大的差距。微软用事实告诉我们(CRT,MFC,ATL)宏是C/C++的第二生命要不离不弃并放扬光大。
把上面用到的宏展开
#define _Check_return_wat_ _Check_return_ //在dll版本 #define _CRTIMP_ALTERNATIVE _CRTIMP #define _CRTIMP __declspec(dllimport) //在exe版本只简单的定义存在 #define _CRTIMP_ALTERNATIVE #define _Inout_z_cap_(size) _Pre_z_cap_(size) _Post_z_ #define _In_ _Pre1_impl_(_$notnull) _Deref_pre2_impl_(_$valid, _$readaccess) #define _Deref_prepost_z_ _Deref_pre_z_ _Deref_post_z_ #define _Inout_z_ _Prepost_z_ #define _Out_z_cap_(size) _Pre_cap_(size) _Pre_invalid_ _Post_z_ #define _In_z_ _Pre_z_ _Deref_pre_readonly_ #define _Pre_cap_for_(param) _Pre2_impl_(_$notnull, _$cap_for(param)) #define _Post_z_ _Post1_impl_(_$zterm) _Deref_post1_impl_(_$valid) #define _Pre_z_cap_(size) _Pre3_impl_(_$notnull, _$zterm,_$cap(size)) _Deref_pre1_impl_(_$valid)
#define _Pre3_impl_(p1,p2,p3) [SA_Pre(p1,p2,p3)]
#define _Deref_pre1_impl_(p1) [SA_Pre(Deref=1,p1)]
#define _Post_z_ _Post1_impl_(_$zterm) _Deref_post1_impl_(_$valid)
#define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_1(_ReturnType, _FuncName, _DstType, _Dst, _TType1, _TArg1) \ extern "C++" \ { \ template <size_t _Size> \ inline \ _ReturnType __CRTDECL _FuncName(_DstType (&_Dst)[_Size], _TType1 _TArg1) \ { \ return _FuncName(_Dst, _Size, _TArg1); \ } \ } #define __DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(_ReturnType, _ReturnPolicy, _DeclSpec, _FuncName, _SalAttributeDst, _DstType, _Dst, _TType1, _TArg1) \ __DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1_EX(_ReturnType, _ReturnPolicy, _DeclSpec, _FuncName, _FuncName##_s, _DstType, _SalAttributeDst, _DstType, _Dst, _TType1, _TArg1) #define __DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1(_ReturnType, _ReturnPolicy, _DeclSpec, _FuncName, _SalAttributeDst, _DstType, _Dst, _TType1, _TArg1) \ __DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1_EX(_ReturnType, _ReturnPolicy, _DeclSpec, _FuncName, _FuncName##_s, _DstType, _SalAttributeDst, _DstType, _Dst, _TType1, _TArg1) #define __DEFINE_CPP_OVERLOAD_STANDARD_FUNC_0_1_EX(_ReturnType, _ReturnPolicy, _DeclSpec, _FuncName, _SecureFuncName, _SecureDstType, _SalAttributeDst, _DstType, _Dst, _TType1, _TArg1) \ _CRT_INSECURE_DEPRECATE(_SecureFuncName) _DeclSpec _ReturnType __cdecl _FuncName(_SalAttributeDst _DstType *_Dst, _TType1 _TArg1); //输出的提示信息在这儿,没错就是它 #define _CRT_INSECURE_DEPRECATE(_Replacement) _CRT_DEPRECATE_TEXT("This function or variable may be unsafe. Consider using " #_Replacement " instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.") #define _CRT_DEPRECATE_TEXT(_Text) __declspec(deprecated(_Text)) #define __RETURN_POLICY_DST(_FunctionCall, _Dst) return ((_FunctionCall) == 0 ? _Dst : 0) #define __EMPTY_DECLSPEC
看着让人叹为观止的宏我们来说说其重点,[SA_Pre(p1)]这种元语言语法和C#的元语言语法类似,没找到任何关于它的说明(有谁知道麻烦告诉我一声),别一种用法是 __declspec("SAL_pre"),这个容易理解就是一个标准的编译器扩展。类似的扩展支持多少?同样也没有看到文档说,不过不必担心,使用SAL的时候永远不会直接用到的它的。关于SAL的使用微软官方有两篇文档进行了介绍,这个是介绍vs2010的http://msdn.microsoft.com/en-us/library/ms235402.aspx,这个是介绍vs2005的http://msdn.microsoft.com/en-us/library/aa383701(VS.85).aspx。下面简单介绍一下SAL语法。
1、__in
用__in注解的函数将只从单元素的缓冲区中读取数据,而且这个缓冲区必须被初始化(不能为NULL);__in_ecount(n)与其相似但可以指定元素个数。
2、__out
用__out注解的函数表示数据缓冲区必须是有效的并且可以被调用代码废弃。
3、__in_opt
表示可选缓冲区当然缓冲区可以为NULL。
4、__inout
表示可以读/写的缓冲区。缓冲区必须被调用者初始化。
5、__bcount(n)
限定缓冲区大小为n字节。
6、_ecount(n)
限定元素个数为n。
7、__checkReturn
调用者必须检查返回值,不允许忽略。
SAL的关健字很多就不一一介绍了,希望看这个贴子的读者能对SAL有个感性的认识,虽然不一定要用它但了解一下总归是有帮助的。个人理解不同难免会有疏漏,如果你要是准备使用SAL建议还是多看看MSDN吧。