【问题标题】:How to get error message when ifstream open failsifstream 打开失败时如何获取错误消息
【发布时间】:2013-06-24 15:02:58
【问题描述】:
ifstream f;
f.open(fileName);

if ( f.fail() )
{
    // I need error message here, like "File not found" etc. -
    // the reason of the failure
}

如何获取字符串形式的错误信息?

【问题讨论】:

标签: c++ error-handling stream std


【解决方案1】:

每个失败的系统调用都会更新errno 值。

因此,您可以通过以下方式获得更多关于ifstream 打开失败时会发生什么的信息:

cerr << "Error: " << strerror(errno);

但是,由于 每个 系统调用都会更新全局 errno 值,如果另一个系统调用在执行 @987654325 之间触发错误,您可能会在多线程应用程序中遇到问题@ 和使用errno

在符合 POSIX 标准的系统上:

errno 是线程本地的;将其设置在一个线程中不会影响其 任何其他线程中的值。


编辑(感谢 Arne Mertz 和 cmets 中的其他人):

e.what() 起初似乎是一种更 C++ 惯用的正确实现方式,但是此函数返回的字符串依赖于实现,并且(至少在 G++ 的 libstdc++ 中)此字符串没有关于原因的有用信息错误的背后...

【讨论】:

  • e.what() 似乎没有提供太多信息,请参阅我的答案的更新。
  • errno 在现代操作系统上使用线程本地存储。但是,不能保证 fstream 函数在 errno 发生后不会破坏 errno。底层函数可能根本没有设置errno(Linux 或 Win32 上的直接系统调用)。这不适用于许多现实世界的实现。
  • 在 MSVC 中,e.what() 总是打印相同的消息“iostream stream error
  • warning C4996: 'strerror': This function or variable may be unsafe. Consider using strerror_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. 1&gt; C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\string.h(168) : see declaration of 'strerror'
  • @sergiol 这些都是谎言。忽略它们或禁用警告。
【解决方案2】:

您可以尝试让流在失败时抛出异常:

std::ifstream f;
//prepare f to throw if failbit gets set
std::ios_base::iostate exceptionMask = f.exceptions() | std::ios::failbit;
f.exceptions(exceptionMask);

try {
  f.open(fileName);
}
catch (std::ios_base::failure& e) {
  std::cerr << e.what() << '\n';
}

e.what(),然而,似乎没有多大帮助:

  • 我在 Win7、Embarcadero RAD Studio 2010 上尝试过,它给出“ios_base::failbit set”,而strerror(errno) 给出“没有这样的文件或目录”。
  • 在 Ubuntu 13.04、gcc 4.7.3 上,异常显示“basic_ios::clear”(感谢arne

如果e.what() 对您不起作用(我不知道它会告诉您什么错误,因为这不是标准化的),请尝试使用std::make_error_condition(仅限C++11):

catch (std::ios_base::failure& e) {
  if ( e.code() == std::make_error_condition(std::io_errc::stream) )
    std::cerr << "Stream error!\n"; 
  else
    std::cerr << "Unknown failure opening file.\n";
}

【讨论】:

  • 谢谢。我没有对此进行测试,因为在 cmets 中发布的 strerror(errno) 可以工作并且使用起来非常简单。我认为e.what 会起作用,因为errno 起作用。
  • 然后在 Matthieus 的答案中查看关于多线程的注释 - 我的猜测是 e.what() 将以线程安全的方式返回 strerror。两者都可能依赖于平台。
  • @AlexFarber:我认为 Arne 的回答比我的要好。我的解决方案不是解决您的问题的 C++ 方式。但是,我没有找到有关 C++ 库如何将系统调用错误映射到exception.what() 的官方信息。可能是深入了解 libstdc++ 源代码的好机会 :-)
  • 我试过这个:试图打开一个不存在的文件,异常消息读取basic_ios::clear,没有别的。这真的没有帮助。这就是我没有发帖的原因;)
  • @arne 平台、编译器、操作系统?
【解决方案3】:

根据 @Arne Mertz 的回答,从 C++11 开始,std::ios_base::failure 继承自 system_error(请参阅 http://www.cplusplus.com/reference/ios/ios_base/failure/),其中包含 strerror(errno) 将返回的错误代码和消息。

std::ifstream f;

// Set exceptions to be thrown on failure
f.exceptions(std::ifstream::failbit | std::ifstream::badbit);

try {
    f.open(fileName);
} catch (std::system_error& e) {
    std::cerr << e.code().message() << std::endl;
}

如果fileName 不存在,则打印No such file or directory.

【讨论】:

  • 对我来说,在 MSVC 2015 中只打印 iostream stream error
  • 对我来说,GCC 6.3 也会打印出iostream error。你在哪个编译器上测试过这个?是否有任何编译器实际上提供了用户可读的失败原因?
  • macOS 上 libc++ 上的 Clang 6:unspecified iostream_category error.
  • MacOS 10.14.x 上的 Xcode 10.2.1 (Clang) / libc++ (C++17):还有“未指定的 iostream_category 错误”。 strerror(errno) 似乎是解决此问题的唯一方法。我想我可以先通过询问 std::filesystem 是否 path.exists() 来捕获它,然后检查它返回的 std::error_code。
  • 在示例程序中,语句f.open(fileName) 抛出了std::ios_base::failure 类型的异常,该异常是从std::system_error 派生的。异常被 catch 块捕获。在 catch 块中,e.code() 调用 std::ios_base::failure::code(),它返回一个类型为 std::error_code 的对象。 std::error_code 类定义的错误代码是平台相关的——即e.code().message()e.code().value() 都返回平台相关的值。
【解决方案4】:

您也可以抛出std::system_error,如下面的测试代码所示。这种方法似乎比f.exception(...) 产生更具可读性的输出。

#include <exception> // <-- requires this
#include <fstream>
#include <iostream>

void process(const std::string& fileName) {
    std::ifstream f;
    f.open(fileName);

    // after open, check f and throw std::system_error with the errno
    if (!f)
        throw std::system_error(errno, std::system_category(), "failed to open "+fileName);

    std::clog << "opened " << fileName << std::endl;
}

int main(int argc, char* argv[]) {
    try {
        process(argv[1]);
    } catch (const std::system_error& e) {
        std::clog << e.what() << " (" << e.code() << ")" << std::endl;
    }
    return 0;
}

示例输出(Ubuntu w/clang):

$ ./test /root/.profile
failed to open /root/.profile: Permission denied (system:13)
$ ./test missing.txt
failed to open missing.txt: No such file or directory (system:2)
$ ./test ./test
opened ./test
$ ./test $(printf '%0999x')
failed to open 000...000: File name too long (system:36)

【讨论】:

    【解决方案5】:

    上面的std::system_error 示例有点不正确。 std::system_category() 将映射来自系统本机错误代码工具的错误代码。对于 *nix,这是 errno。对于 Win32,它是GetLastError()。即,在Windows上,上面的例子会打印出来

    failed to open C:\path\to\forbidden: The data is invalid
    

    因为 EACCES 是 13,即 Win32 错误代码 ERROR_INVALID_DATA

    要修复它,请使用系统的本机错误代码工具,例如在 Win32 上

    throw new std::system_error(GetLastError(), std::system_category(), "failed to open"+ filename);
    

    或者使用errno和std::generic_category(),例如

    throw new std::system_error(errno, std::generic_category(), "failed to open"+ filename);
    

    【讨论】:

      猜你喜欢
      • 2022-01-04
      • 1970-01-01
      • 2011-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-14
      • 2015-11-21
      • 1970-01-01
      相关资源
      最近更新 更多