看网上很多讲一些C/C++的问题回答或者文章,经常会遇到UB、UB、UB、UB……
UB就是undefined behavior。也就是语言的官方标准未定义的地方,在不同系统,编译器上行为可能不一致。没错,我们当然不能把UB的效果,当成语言标准。但我们也不能因为某些语法是UB就拒绝学习和探究,毕竟我们学习编程语言都不是抱着《XX语言官方标准》的手册来学的。另外呢,我们工作之中通常也是在特定的系统和编译器上开发的。
今天要讲的内容就是UB的。言归正传。
C语言printf输出NULL
看下这个:
#include
printf用%s输出NULL,会有什么行为,大家可能会说,运行会core啊,不能这样写。没错,去空地址寻址,会core掉,好的编码规范,也要求我们在调用函数之前对变量a判非NULL,不过今天我们不谈编码规范。
这段代码在gcc(Linux上测试)和clang(Mac上测试)上其实都不会core掉。而是会正常输出,当然这是UB的:
(null)
再看这个:
printf
能正常运行吗?这就有差异了。
gcc运行会段错误。但是clang依旧能正常输出(null)和换行。clang鲁棒性很好,暂且不提了。gcc为什么会因为多了n就有此差异呢?这是因为gcc对printf有优化。当printf的格式化字符串为"%sn"的时候,实际 printf("%sn", a); 会被替换成 puts(a); 而puts 是未针对NULL特殊处理输出成(null),而是直接去寻址导致段错误。
相关阅读:What is the behavior of printing NULL with printf's %s specifier?
C++ IO流输出NULL
看下这段代码会如何输出呢?
#include
先看clang:
0
[1] 40674 segmentation fault ./a.out
不出意外,第二句就段错误了。
工作中,我们开发都是在Linux上开发,g++编译的,我们来看下g++运行效果:
0
没有段错误!?
cout<<NULL<<endl; 在编译时会有警告,但是不会编译失败。在运行时,会输出0(同clang)。
但是为什么不是:
0
0
123
肯定不会是这样,因为cout在直接输出NULL的时候(cout<<NULL),把NULL做整数0来理解,最后输入了0。而通过char *变量来输出的时候(cout<<a)会按字符串来理解,将传入的数据理解成字符串的地址,然后去寻址,然后会访问非法内存地址。很遗憾,在g++上这个行为并不会导致段错误(clang会)。
好,虽然第二句没有core掉,那么为什么第三句123没有输出呢??同样是由于第2句。当C++的输出流访问了非法内存地址(0)之后,虽然没有段错误,但是会导致输出流异常,导致后面的cout失效。
这可能会比在第二句core掉的危害更深的。因为如果段错误,core了一次,你就会立刻发现从而纠正代码了。但是它在g++上不会导致段错误,那么对我们的业务逻辑就可能荼毒颇深了。
那么g++上如何纠正输出流呢?其实很简单:
#include
这个例子是从我以前工作中踩到的一个坑抽象而来。当时用的不是cout,而是其他的输出流,也是调试了很久,才发现的坑……有时候真不如直接core掉痛快。
好了,让我们好好学学C++编码规范吧。
如果你感兴趣,欢迎点赞、收藏、留言,评论,分享,关注。