ifstream 打开失败时如何获取错误消息

Posted

技术标签:

【中文标题】ifstream 打开失败时如何获取错误消息【英文标题】:How to get error message when ifstream open fails 【发布时间】: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++ ifstream Error Checking的可能重复 Can you get a specific error condition when a C++ stream open fails?的可能重复 @Alex Farber:当然。 cerr << "Error code: " << strerror(errno); // Get some info as to why 似乎与问题有关。 @MatthieuRouget:检查我发布的可能重复项——这似乎是仅由 gcc 实现的非标准行为。 @MatthieuRouget:strerror(errno) 有效。将此作为答案发布,我会接受。 【参考方案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);

【讨论】:

以上是关于ifstream 打开失败时如何获取错误消息的主要内容,如果未能解决你的问题,请参考以下文章

当 mkdir 从 PHP 失败时如何找到原因?

ifstream:如何判断指定文件是不是不存在

如何从 DB2 获取描述性错误消息?

如何获取文件的地图视图只有ifstream?

Django,自定义身份验证登录。身份验证失败时如何显示错误消息?

okhttp 获取失败响应