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

时间:2016-08-26 14:15:29

标签: c++ winapi error-handling

我正在使用C ++代码调用Windows API,我有一个辅助方法来执行FormatMessage内容并为错误处理抛出异常。该功能的签名是

void throw_system_error(const std::string& msg_prefix, DWORD messageid)

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

handle = ...;
if (handle == NULL) {
    throw_system_error("something bad happened", GetLastError());
}

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

这在哪里工作得很好:

handle = ...;
if (handle == NULL) {
    DWORD error = GetLastError();
    throw_system_error("something bad happened", error);
}

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

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

它会查找全世界,好像std::string的构造函数正在重置错误代码。

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

任何人都知道这里到底发生了什么?

Visual C ++ 2015,64位。

3 个答案:

答案 0 :(得分:6)

让我们看一下GetLastError文档:

  

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

     

你应该在a时立即调用GetLastError函数   函数的返回值表示这样的调用将返回有用   数据。那是因为某些函数调用SetLastError为零   当他们成功时,消除最近设置的错误代码   失败的功能。

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

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

  

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

     

在调试模式下(使用visual studio)有一个手写的   free和malloc的版本(新的/ delete被重定向)   线程锁和更多异常检测,以便您可以检测   当你用堆指针做错时更容易   在调试模式下运行代码。

因此在发布模式下,调用的函数是HeapAlloc NOT 调用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;
}

输出以下内容:

23
0

所以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;
}

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

23
0

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

23
23

答案 1 :(得分:4)

根据GetLastError

的文档
  

设置最后错误代码的每个函数的文档的返回值部分说明了函数设置最后错误代码的条件。设置线程的最后错误代码的大多数函数在失败时设置它。 但是,某些函数在成功时也会设置最后一个错误代码。如果没有记录该函数来设置最后一个错误代码,则此函数返回的值只是最近的最后一个错误代码已设置; 某些函数在成功时将最后一个错误代码设置为0,而其他函数则不会。

在构建std::string期间的某个时刻,会调用SetLastError。 Windows上的标准库使用Win32调用作为其实现的一部分。

您的第二种方法(有效)是使用GetLastError

的正确方法
  

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

答案 2 :(得分:0)

这是正常的 - &#34;最后一个错误&#34;可以通过任何函数调用间接设置 有些功能将其设置为&#34;没有错误&#34;如果你想要可靠地使用它,你需要在你做任何其他事情之前立即存储它。

如果您遇到过&#34;发生严重错误:操作成功完成&#34;对话,这可能是原因。