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


99
ifstream f;
f.open(fileName);

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

如何获取错误信息作为字符串?




3
@Alex Farber:当然可以。cerr << "Error code: " << strerror(errno); // Get some info as to why似乎与这个问题有关。
Matthieu Rouget

@MatthieuRouget:检查我发布的可能重复项-看来这是仅由gcc实现的非标准行为。
Arne

1
@MatthieuRouget:strerror(errno)有效。将其发布为答案,我会接受。
Alex F

Answers:


72

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

因此,您可以ifstream通过使用类似以下内容的信息来进一步了解打开失败时发生的情况:

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

但是,由于每个系统调用都会更新全局errno值,因此如果另一个系统调用在的执行f.open和使用之间触发了错误,则在多线程应用程序中可能会出现问题errno

在具有POSIX标准的系统上:

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


编辑(感谢Arne Mertz和其他人的评论):

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


1
e.what()似乎没有提供太多信息,请参阅我的答案的更新。
Arne Mertz 2013年

17
errno在现代操作系统上使用线程本地存储。但是,不能保证在出现错误后fstream函数不会崩溃errno。基本功能可能根本没有设置errno(Linux或Win32上的直接系统调用)。这不适用于许多现实世界的实现。
strcat

1
在MSVC中,e.what()总是打印相同的消息“ iostream stream error
rustyx

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> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\string.h(168) : see declaration of 'strerror'
sergiol

1
@sergiol这些都是谎言。忽略它们或禁用警告。
SS安妮

29

您可以尝试让流在失败时引发异常:

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(),但是似乎没有什么帮助:

  • 我在Embarcadero RAD Studio 2010 Win7上尝试过,它给出“ 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";
}

谢谢。我没有对此进行测试,因为strerror(errno)在评论中发布了作品并且使用起来非常简单。我认为这e.what会起作用,因为会errno起作用。
Alex F

然后在Matthieus答案中看到有关多线程的注释-我的猜测是,e.what()它将以strerror线程安全的方式返回。两者都可能取决于平台。
Arne Mertz 2013年

1
@AlexFarber:我认为Arne的答案比我的要好。我的解决方案不是解决问题的C ++方法。但是,我没有找到有关C ++库如何将系统调用错误映射到的官方信息exception.what()。可能是深入学习libstdc ++源代码的好机会:-)
Matthieu Rouget 2013年

我尝试了一下:尝试打开一个不存在的文件,并且读取异常消息basic_ios::clear,除此之外没有其他。这真的没有帮助。这就是为什么我没有发布;)
Arne

@arne wich平台,编译器,操作系统?
Arne Mertz 2013年

22

在@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;
}

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


8
对我来说,在MSVC 2015中仅打印出来iostream stream error
rustyx

2
对我来说,GCC 6.3也可以打印iostream error。您在哪个编译器上对此进行了测试?是否有任何编译器实际上提供了用户可读的失败原因?
Ruslan

2
在macOS上的libc ++上的Clang 6 unspecified iostream_category error
阿基姆

MacOS 10.14.x上的Xcode 10.2.1(Clang)/ libc ++(C ++ 17):也是“未指定iostream_category错误”。strerror(errno)SEEMS是获得此权利的唯一方法。我想我可以通过询问std :: filesystem是否path.exists()并检查返回的std :: error_code来捕获它。
SMGreenfield

7

您还可以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;
}

示例输出(带c声的Ubuntu):

$ ./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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.