为什么std :: string构造函数重置GetLastError

Ste*_*fan 4 c++ error-handling winapi

我从C++代码调用Windows API,我有一个辅助方法来完成FormatMessage这些工作,并为错误处理抛出异常.功能的签名是

void throw_system_error(const std::string& msg_prefix, DWORD messageid)
Run Code Online (Sandbox Code Playgroud)

我注意到一些奇怪的事情.此代码无法正常运行:

handle = ...;
if (handle == NULL) {
    throw_system_error("something bad happened", GetLastError());
}
Run Code Online (Sandbox Code Playgroud)

传递给的错误代码 throw_system_error始终为零.

这在哪里工作得很好:

handle = ...;
if (handle == NULL) {
    DWORD error = GetLastError();
    throw_system_error("something bad happened", error);
}
Run Code Online (Sandbox Code Playgroud)

更多调查显示此版本存在同样的问题:

handle = ...;
if (handle == NULL) {
    std::string msg("something bad happened");
    DWORD error = GetLastError();
    throw_system_error(msg, error);
}
Run Code Online (Sandbox Code Playgroud)

它寻找所有的世界,好像构造函数std::string正在重置错误代码.

我的猜测std::string是内部分配内存导致一些系统调用,然后将最后一个错误设置回零.

谁知道这里到底发生了什么?

Visual C++ 2015,64位.

Ped*_*ios 6

我们来看看GetLastError文档:

设置线程的最后错误代码的大多数函数在失败时设置它.但是,某些函数在成功时也会设置最后一个错误代码.

当函数的返回值指示此类调用将返回有用数据时,应立即调用GetLastError函数.这是因为一些函数在成功时调用SetLastError为零,消除了最近失败的函数设置的错误代码.

所以有一个函数调用SetLastError,很可能是一个分配内存的函数:当你构造一个字符串时,new调用它来分配内存.

现在,让我们看看new在vc ++中的实现.Stack Overflow中有一个非常好的答案:https://softwareengineering.stackexchange.com/a/293209

这取决于您是处于调试模式还是发布模式.在发布模式下,HeapAlloc/HeapFree是内核函数,

在调试模式下(使用visual studio),有一个手写版本的free和malloc(新的/ delete被重定向),带有线程锁和更多异常检测,这样当你犯了一些错误时你可以更容易地检测到在调试模式下运行代码时使用堆指针.

因此,在释放模式,调用的函数是HeapAlloc,这确实不是调用SetLastError.从文档:

如果函数失败,则不会调用 SetLastError

因此代码应该在发布模式下正常工作.但是,在调试实现中,调用函数FlsGetValue,该函数在成功时调用SetLastError.

这很容易检查,

#include <iostream>
#include <Windows.h>
int main() {

    DWORD t = FlsAlloc(nullptr);
    SetLastError(23); //Set error to 23
    DWORD error1 = GetLastError(); //store error

    FlsGetValue(t); //If success, it is going to set error to 0

    DWORD error2 = GetLastError(); //store second error code

    std::cout << error1 << std::endl;
    std::cout << error2 << std::endl;
    system("PAUSE");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它输出以下内容:

23
0
Run Code Online (Sandbox Code Playgroud)

所以FlsGetValue调用了SetLastError().为了证明只在调试时调用它,我们可以进行以下测试:

#include <iostream>
#include <Windows.h>
int main() {

    DWORD t = FlsAlloc(nullptr);
    SetLastError(23); //Set error to 23
    DWORD error1 = GetLastError(); //store error

    int* test = new int; //allocate int

    DWORD error2 = GetLastError(); //store second error code

    std::cout << error1 << std::endl; //output errors
    std::cout << error2 << std::endl;

    delete test; //free allocated memory
    system("PAUSE");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果你在调试模式下运行它会给你,因为它调用FlsGetValue:

23
0
Run Code Online (Sandbox Code Playgroud)

但是,如果在发布模式下运行它,它会生成,因为它调用HeapAlloc:

23
23
Run Code Online (Sandbox Code Playgroud)