vs编译器 printf 控制台输出_C/C++输出不小心是NULL的字符串,UB UB UB UB UB UB…

0055cfdc9578cdd716f3e407e0d2f258.png

看网上很多讲一些C/C++的问题回答或者文章,经常会遇到UB、UB、UB、UB……

UB就是undefined behavior。也就是语言的官方标准未定义的地方,在不同系统,编译器上行为可能不一致。没错,我们当然不能把UB的效果,当成语言标准。但我们也不能因为某些语法是UB就拒绝学习和探究,毕竟我们学习编程语言都不是抱着《XX语言官方标准》的手册来学的。另外呢,我们工作之中通常也是在特定的系统和编译器上开发的。

今天要讲的内容就是UB的。言归正传。


C语言printf输出NULL

看下这个:

#include <stdio.h>
int main()
{
    char* a = NULL;
    printf("%s", a);
    return 0;
}

printf用%s输出NULL,会有什么行为,大家可能会说,运行会core啊,不能这样写。没错,去空地址寻址,会core掉,好的编码规范,也要求我们在调用函数之前对变量a判非NULL,不过今天我们不谈编码规范。

这段代码在gcc(Linux上测试)和clang(Mac上测试)上其实都不会core掉。而是会正常输出,当然这是UB的:

(null)

再看这个:

printf("%sn", a);

能正常运行吗?这就有差异了。

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 <iostream>
using namespace std;
int main()
{
    char* a = NULL;
    cout<<NULL<<endl;  // 可以打印出0
    cout<<a<<endl;
    cout<<123<<endl;
}

先看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 <iostream>
using namespace std;
int main()
{
    char* a = NULL;
    cout<<NULL<<endl; 
    cout<<a<<endl; 
    cout.clear(); // 重置输出流
    cout<<123<<endl;
}

这个例子是从我以前工作中踩到的一个坑抽象而来。当时用的不是cout,而是其他的输出流,也是调试了很久,才发现的坑……有时候真不如直接core掉痛快。

好了,让我们好好学学C++编码规范吧。


如果你感兴趣,欢迎点赞、收藏、留言,评论,分享,关注。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值