【问题标题】:Why is std::string constructor resetting GetLastError为什么 std::string 构造函数会重置 GetLastError
【发布时间】:2016-08-26 14:15:28
【问题描述】:

我正在从 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 位。

【问题讨论】:

  • 您的代码已损坏。就那么简单。在调用任何其他 API 函数之前调用 GetLastError。显然,分配内存的调用可能会调用SetLastError。我很确定以前在这里有人问过这个问题。
  • 代码已损坏,但我要求的是根本原因,而不是修复。为了增加谜团,我已经测试了 HeapAlloc 并且它不会修改 last-error。但是 malloc 将其设置为零。作为 GetLastError 要求的文档,msdn 上没有记录。那么...我应该接受哪个答案?
  • malloc 不在 MSDN Win32 文档的范围内。它可以为所欲为。
  • 您可能需要考虑抛出一个仅由错误代码构造的std::system_error。如果您需要在任意子系统边界添加字符串前缀,您可以在接口实现中的适当catch 子句中实现代码来执行此操作。

标签: c++ winapi error-handling


【解决方案1】:

让我们看看GetLastError 文档:

大多数设置线程最后错误代码的函数都会在 失败。然而,一些函数在它们的时候也会设置最后一个错误代码 成功。

您应该立即调用 GetLastError 函数,当 函数的返回值表明这样的调用会返回有用的 数据。这是因为 一些函数调用 SetLastError 时使用零 当他们成功时,清除最近设置的错误代码 功能失败。

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

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

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

在调试模式下(使用 Visual Studio)有一个手写的 free 和 malloc 的版本(将 new/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

【讨论】:

  • 您误解了HeapAlloc 的文档。当它说 “如果函数失败,它不会调用 SetLastError。” 它只是意味着在失败时调用 GetLastError 将返回一个不确定的值。这并不意味着 API 调用将保留调用线程的最后一个错误。
【解决方案2】:

根据GetLastError 的文档

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

std::string 的构建过程中,SetLastError 被调用。 Windows 上的标准库使用 Win32 调用作为其实现的一部分。

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

当函数的返回值表明这样的调用将返回有用的数据时,您应该立即调用 GetLastError 函数。那是因为有些函数调用 SetLastError 时,调用成功时返回 0,从而清除了最近失败函数设置的错误代码。

【讨论】:

  • 是的,我能猜到这么多。但是在我所知道的 std::string 文档中,没有任何地方注意到设置最后一个错误代码的条件,因此根据您引用的文档,它确实不应该。此外,分配内存可能会通过 HeapAlloc 明确说明 not 调用 SetLastError。
  • @Stefan HeapAlloc 的文档不要这么说。您可以绘制的只是“应用程序无法调用GetLastError 获取扩展错误信息。”
  • @Stefan 我同意,关于设置 GetLastError 的文档不是很好。该文档确实考虑到了这一点(第二个引用)。你不应该依赖它被记录,并且应该总是在需要时立即调用GetLastError,因为任何东西都可以设置它。
  • @Stefan: “但在我所知道的 std::string 文档中,没有任何地方注意到设置最后错误代码的条件” - 它没有,因为 C++ 标准库从不记录实现细节。它也不需要,因为GetLastError 的文档非常清晰“当函数返回值时,您应该立即调用 GetLastError 函数表示这样的调用将返回有用的数据。”
【解决方案3】:

这是正常的——“最后一个错误”可以通过任何函数调用间接设置。
某些功能在成功时将其设置为“无错误”,因此如果您想可靠地使用它,您需要在执行其他任何操作之前立即存储它。

如果您曾经遇到过“发生严重错误:操作成功完成”对话框,这可能就是原因。

【讨论】:

  • 没有任何功能。仅记录此行为的函数。请参阅 Ics 的答案。
  • 文档在错误代码方面经常出错。仅仅因为一个函数没有记录它设置它,并不能保证它无论如何都不会。
  • MS 说“如果没有记录该函数以设置最后一个错误代码,则此函数返回的值只是最近设置的最后一个错误代码”,这意味着(在至少对我来说)它可以由另一个函数间接设置。
猜你喜欢
  • 1970-01-01
  • 2015-12-20
  • 2023-03-16
  • 2016-03-26
  • 2022-01-03
  • 1970-01-01
  • 2016-12-15
  • 2018-09-20
相关资源
最近更新 更多