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> 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()
,然而,似乎没有多大帮助:
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 打开失败时如何获取错误消息的主要内容,如果未能解决你的问题,请参考以下文章