此诊断结果是代码分析过于狭隘而忽略了现成的提示的不幸结果。C26485警告数组到指针的衰减,这是 C++ 最危险的特性之一。当将数组的名称传递给需要指针的函数时,编译器会默默地将数组转换为指向其第一个元素的指针,从而删除作为数组类型一部分的大小信息。
调用接受单个参数(指针和大小)来描述数组的接口的客户端必须确保大小实际上与数组的大小匹配。这导致了无数的CVE,没有理由相信情况会好转。数组到指针的衰减是危险的,有工具防范它是很好的。理论上。
然而,这里的情况有所不同。GetErrorMessage
具有SAL 注释的声明(和定义)允许编译器在编译时验证指针和大小是否匹配。签名如下:
virtual BOOL GetErrorMessage(_Out_writes_z_(nMaxError) LPTSTR lpszError,
_In_ UINT nMaxError,
_Out_opt_ PUINT pnHelpContext = NULL) const;
注释在指针与其对应数组的大小_Out_writes_z_(s)
之间建立了编译时可验证的关系。这是有用的信息,应尽可能加以利用。lpszError
nMaxError
不过,首先,让我们按照文档中的建议尝试解决眼前的问题:
对衰减指针类型的显式强制转换可以防止警告,但不能防止错误代码。
将数组转换为指向其第一个元素的指针的最紧凑的方法是直接这样做:
catch (CDBException* e)
{
TCHAR szError[_MAX_PATH];
e->GetErrorMessage(&szError[0], _MAX_PATH);
AfxMessageBox(&szError[0]);
}
这解决了眼前的问题(顺便说一句,在两个函数调用上,即使出于不同的原因)。不再发出 C26485,并且——作为额外的奖励——传递一个不正确的值作为第二个参数(例如_MAX_PATH + 1
)确实获得了所需的C6386诊断(“缓冲区溢出”)。
作为验证正确性的一种方式,这也至关重要。如果您要使用更间接的方式(例如,CString
按照此处的建议使用 a ),您将立即放弃该编译时验证。使用 aCString
在计算上更昂贵,而且更不安全。
作为上述方法的替代方法,您还可以暂时抑制两个调用的 C26485 诊断,例如
catch (CDBException* e)
{
TCHAR szError[_MAX_PATH];
// Decay is safe due to the _Out_writes_z_ annotation
#pragma warning(suppress : 26485)
e->GetErrorMessage(szError, _MAX_PATH);
// Decay is safe; the previous call guarantees zero-termination
#pragma warning(suppress : 26485)
AfxMessageBox(szError);
}
您选择哪种实现最终取决于个人喜好。任何一个都解决了这个问题,就代码分析而言,后者可能更具保留性。
关于为什么最终调用AfxMessageBox
是安全的一句话:它需要一个以零结尾的字符串,因此不需要显式的大小参数。签名上的_Out_writes_z_(s)
注释GetErrorMessage
承诺在返回时始终以零终止输出字符串。这也在编译时在合约双方进行验证:调用者可以依靠接收一个以零结尾的字符串,并且编译器确保实现没有违反此后置条件的返回路径。