将空指针写入 std::cout 可防止以后的写入

Writing null pointer to std::cout prevents later writes

本文关键字:可防止 cout std 空指针      更新时间:2023-10-16
#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类型的模板实例的"charsd"对象有什么问题?没有抛出任何例外。

编译器版本:

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

所以,请注意,粗心的人的陷阱就在那里等待,"为什么我的日志记录语句没有突然出现?"或类似的东西。