将空指针写入 std::cout 可防止以后的写入
Writing null pointer to std::cout prevents later writes
#include <iostream>
#include <string>
using namespace std;
template<class T> class Sample {
private:
T val;
public:
Sample(T InitialVal=T()) : val(InitialVal)
{
// do nothing
}
~Sample()
{
// do nothing
}
void PrintVal(void)
{
try {
cout << "[" << val << "]" << endl;
} catch(...) {
cout << "exception thrown" << endl;
}
}
};
int main() {
// your code goes here
Sample<int> ints(20), intd;
Sample<char *> chars(const_cast<char*>("Neelakantan")), charsd;
Sample<string> s, ss("neel");
ints.PrintVal();
intd.PrintVal();
chars.PrintVal();
charsd.PrintVal(); // <<- Culprit line. Commenting out this line works as expected.
s.PrintVal();
ss.PrintVal();
return 0;
}
当我运行上面的代码时,我得到以下输出:
sh-4.4$ g++ -o main *.cpp
sh-4.4$ main
[20]
[0]
[Neelakantan]
[sh-4.4$
当我注释掉"charsd.PrintVal();
"行时,我得到以下输出:
[sh-4.4$ g++ -o main *.cpp
sh-4.4$ main
[20]
[0]
[Neelakantan]
[]
[neel]
sh-4.4$
Sample
编译器版本:
sh-4.4$ g++ --version
g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
sh-4.4$
问题是charsd
对象的字段val;
是用空指针初始化的。因此,尝试将其传递给operator <<
违反了运算符前提条件并导致未定义的行为。
charsd
包含一个空指针,将空const char*
写入ostream
是未定义的行为。
GCC 的标准库实现会检查该条件(请参阅此处的代码(,并在流状态下设置badbit
标志。badbit
指示流状态已损坏,在本例中由标准所说的意外空指针损坏,这是未定义的行为。注:注:未记录ostream
的空指针的这种处理,将来可能会更改。
程序继续运行直到完成,但由于badbit
是在流上设置的,因此所有后续写入都将失败,从而不再在std::cout
上产生输出。
当空指针写入流时,程序不会以静默方式退出。它设置错误标志,并继续运行。
如果要检测是否可以这样做,例如,通过检查main
末尾的流状态并将消息打印为标准错误:
int main() {
// ...
if (!std::cout)
std::cerr << "An error happened while writing to coutn";
return 0;
}
或者告诉cout
流您希望将错误转换为异常:
int main() {
std::cout.exceptions(std::ios::badbit | std::ios::failbit);
// ...
return 0;
}
现在,一旦发生错误,程序将立即抛出异常:
terminate called after throwing an instance of 'std::ios_base::failure[abi:cxx11]'
what(): basic_ios::clear: iostream error
Aborted (core dumped)
[根据新信息/知识修订答案。 另请参阅Jonathan Wakely的答案(我在看到之前更新了这个(,他是gcc/libstdc ++的贡献者,并且接受了我提交的错误报告。 非常感谢他让我直截了当,最真诚地道歉,因为我做出了荒谬的假设,即ostream
会以我之前描述的方式行事。
在失败点,你实际正在做的事情实际上是这样的:
std::cout << (const char *) nullptr << std::endl;
大多数人期望得到的(尽管严格来说你在那里所做的事情的行为是不确定的(将是一个SEGFAULT。
但是,包含gcc
使用的ostream
的实现的libstdc++,
执行了一些不同操作。 它在流上设置badbit
,并且仅当您启用了badbit
例外(默认情况下禁用(时,它才会(也(引发异常。感谢乔纳森·韦克利(Jonathan Wakely(在我提交有关此的错误报告时向我指出这一点,当我发布此答案的第一个版本时,我对此一无所知(显然,其他人也没有发布到此线程(。
但是您的代码尚未启用上述异常,因此发生的所有情况都是badbit
被设置为cout
,并且所有后续写入随后都静默失败。 我以前将此误解为程序在传入 nullptr 时悄悄退出,但我错了,我为做出如此无根据的假设向开发人员道歉。 评论中有更多关于这一点的信息。
因此,要在发生这种情况时引发异常,您必须在流上启用badbit
异常,您可以这样做:
std::cout.exceptions (std::ostream::badbit | std::ios::failbit);
然后你得到你所希望的例外。 就个人而言,我不太喜欢这种行为,我宁愿有SEGFAULT(事实上,对于叮当声,你这样做(,但Jonathan告诉我,自2002年以来一直如此,开发人员有充分的理由现在不改变它。
有一个新的现场演示显示了添加了上述行的gcc
的行为,输出现在为:
[20]
[0]
[Neelakantan]
terminate called after throwing an instance of 'std::__ios_failure'
what(): basic_ios::clear: iostream error
[
Aborted
所以,请注意,粗心的人的陷阱就在那里等待,"为什么我的日志记录语句没有突然出现?"或类似的东西。
- 防止主数据类型C++的隐式转换
- 是否可以为 std::cout 创建别名?
- 以编程方式防止重命名或删除文件,但仍使其可写
- 附加作为空字符串的 ostream 可防止进一步追加
- CMake:防止在子目录库项目中生成测试可执行目标
- 将空指针写入 std::cout 可防止以后的写入
- 防止 cin、cout、cerr 被实例化
- C++移动分配可防止复制交换习惯用法
- 防止作用域枚举可复制/可移动
- 在修改项目列表时,请防止可编辑的Qcombobox选择更改
- 大括号初始化可防止非常量使用临时
- 全球安装的键盘钩可防止键盘输入其他应用程序
- C++ 类模板可防止成员函数中的隐式转换
- OpenGLES 标头(包括 Availability.h)可防止 CPP 文件编译
- std::cout 可防止功能中的段错误
- 可视化 如何在 C++ 自动化中启动 ms Word 时防止运行自动宏
- 窗口可防止多个实例代码不起作用
- STDIN 可防止激活其他文件描述符
- 防止 CMake 为仅可选标头库生成的生成文件在仅标头模式下编译源文件
- 在可视化cpp控制台应用程序中使用std::cout出错