在对Stack Overflow进行了扩展的争论之后(现在已经由Powers-that-Be清理了(,出现了一个问题,即何时应该真正调用GetLastError
函数。
注意:这不是一个关于风格的问题,只是关于: (a( 在"非纯粹主义"守则的情况下,标准保证(或不保证(什么; (b( 就安全winapi
规划而言,什么是"最佳做法"。
这里有一个例子(改编自那里发布的原始问题(:
#include <windows.h>
#include <stdio.h>
#include <iostream>
//#define PURIST 1
using namespace std;
int main()
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SMALL_RECT wSize = { 0,0,60,20 }; // Works on my screen!
if (hConsole == nullptr) {
cout << "Console Handle is Null" << endl;
return 1;
}
else {
char message[256];
if (!SetConsoleWindowInfo(hConsole, TRUE, &wSize)) {
#ifdef PURIST
// 'Purist' code ...
DWORD eCode = GetLastError();
sprintf(message, "SetConsoleWindowInfo failed; code = %d!", eCode);
#else
// More normal code ...
sprintf(message, "SetConsoleWindowInfo failed; code = %d!", GetLastError());
#endif
}
else {
strcpy(message, "SetConsoleWindowInfo call succeeded!");
}
cout << message << endl;
}
getchar(); // Just to stop console closing!
return 0;
}
显然,"纯粹主义"方法将始终正常工作!但是,c++
语言标准是否保证"正常"方法也有效?(也就是说,是否可以确定GetLastError()
作为sprintf
参数将是测试SetConsoleWindowInfo()
返回值后执行的第一个代码?
PS:请不要对代码的质量过于苛刻地评判我!正如我所说,这是对原始问题的改编。
编辑:一个更典型的情况(我在Windows应用程序中经常使用(如下所示:
if (<WinApi call failed>) {
TCHAR eText[256];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, eText, 255, nullptr);
// Do something with eText, etc. ...
return <error code>;
}
但同样,GetLastError()
是唯一的非常量论点。
我将这个问题分为两个独立的问题:
-
"普通代码"示例是否按预期工作?
是的,此确切代码将按预期工作。其他函数参数是内置类型,即使未定义顺序函数参数的计算,它们都不会干扰GetLastError
调用。 -
"普通代码"示例是调用
GetLastError
的正确方法吗?
事实并非如此,即使运行此代码会产生预期的结果。这样做的原因是,使用GetLastError
作为函数参数引入了对其他函数参数的隐式约束,即它们必须没有最后错误更改的副作用。这使得代码更容易出错,更难维护。
因此,经验法则是在调用其他函数或创建/销毁对象之前将最后一个错误值存储在某个变量中。
非纯粹主义的方式在这里很好
调用函数时(无论函数是否内联,以及是否使用显式函数调用语法(,与任何参数表达式或指定被调用函数的后缀表达式关联的每个值计算和副作用都在执行被调用函数主体中的每个表达式或语句之前进行排序。
规则 3,我认为这对应于 C++17 标准草案版本中的 §8.2.2.5。
在这种情况下,没有任何其他有副作用的论点。
但请注意,它是脆弱的,如果有人做了一些顽皮的事情,比如
#define sprintf (log_call_to_sprintf(), sprintf)
你运气不好。
让我们有下一个代码片段
if (<WinApi call failed>) {
SomeApiCall(arg<1>, .., GetLastError(), .., arg<n>);
}
并假设以下条件:
SomeApiCall
不是扩展到其他内容的宏 - 这是 正是一些函数调用- 函数的所有参数 (
arg<1>
...arg<n>
(,除GetLastError()
外,仅使用内部C/C++语言表达式,计算时没有任何 外部函数调用(包括语言支持库( 参数中不存在任何东西,什么会导致硬件异常, 如除以零、参考内存除以指针等
这不是关于编码风格的问题
例如此调用
SomeApiCall( "some text", GetLastError(), 8);
符合 2 和 3
在这种情况下,我们可以说GetLastError()
将在线程的最后一个错误代码值可以更改之前调用,在<WinApi call failed>
之后。 因为:
- 线程的最后一个错误代码只能作为某些结果进行更改 视窗 API 调用
- 函数(
SomeApiCall
(的所有参数在执行被调用体中的每个表达式或语句之前计算 功能 - 所以在
<WinApi call failed
>和SomeApiCall
之间只存在arg<1>
, ..,GetLastError()
, ..,arg<n>
- 到期 2 和 3 - 没有任何直接或间接(在异常处理程序中( Windows API 调用,因为C/C++语言不了解 Windows API并且不能通过自调用它(没有一些外部的 语言调用,包括语言支持库(
- 所以在
<WinApi call failed
>和GetLastError()
之间打电话 - 没有任何 视窗 API 调用 - 由于最后一个错误代码的结果值不会在
<WinApi call failed
>和GetLastError()
通话