c++ 判断 错误的指针_空指针,段错误,这场面试我栽倒在这里了!

本文探讨了C/C++中NULL的定义和本质,区分了NULL与0的区别,强调了在面试中理解NULL作为空指针常量的特性的重要性。通过实例解释了NULL作为0在不同情况下的表现,提醒程序员避免将NULL简单视为0,以防止潜在的段错误。
摘要由CSDN通过智能技术生成

360d88ba4d1459a91d2d9131c0f4ac10.gif 关注“脚本之家”,与百万开发者在一起

00ee5a097f4fa9664d0d819e08b10935.png 导读: 编码过程中,我们需要对自己的指针负责,往往导致bug出现或者找不到问题所在地的就是这种细节。

作者 | 李肖遥

来源 | 技术让梦想更伟大 ( ID:gh_f7effb2fbc1c )

714aa764fdc4c28336e8492214a11d35.png 126f58d2fb9caf964b850e69197b6dcb.png

面试官:满头的汗的,来面试的路一定很远吧?

还好还好,骑车不到俩小时

ffc65ebcf39529b212188d55290e3159.png 126f58d2fb9caf964b850e69197b6dcb.png dd671ef745e2bc883540c4b16e186379.png 714aa764fdc4c28336e8492214a11d35.png 126f58d2fb9caf964b850e69197b6dcb.png

面试官:来先喝杯水,咱们面试不急,边喝边聊

哇,谢谢您啦,咕隆咕隆喝下半杯

ffc65ebcf39529b212188d55290e3159.png 126f58d2fb9caf964b850e69197b6dcb.png dd671ef745e2bc883540c4b16e186379.png 714aa764fdc4c28336e8492214a11d35.png 126f58d2fb9caf964b850e69197b6dcb.png

面试官:那咱们开始吧,看你项目做的还不少啊,不错不错,咱们随便聊聊。

哇,“这面试官还不错,感觉有戏”

ffc65ebcf39529b212188d55290e3159.png 126f58d2fb9caf964b850e69197b6dcb.png dd671ef745e2bc883540c4b16e186379.png 714aa764fdc4c28336e8492214a11d35.png 126f58d2fb9caf964b850e69197b6dcb.png

面试官:说说空指针NULL是本质什么,与0一样吗

嗯嗯,这个,NULL不就是0吗?

  126f58d2fb9caf964b850e69197b6dcb.png dd671ef745e2bc883540c4b16e186379.png 714aa764fdc4c28336e8492214a11d35.png 126f58d2fb9caf964b850e69197b6dcb.png

面试官:你确定是吗,面带微笑,淡定的眼神......

嗯嗯,应该是吧,,,不是吧,汗又开始冒起了来了......

126f58d2fb9caf964b850e69197b6dcb.png dd671ef745e2bc883540c4b16e186379.png 714aa764fdc4c28336e8492214a11d35.png 126f58d2fb9caf964b850e69197b6dcb.png

面试官:嗯嗯,了解了,不急慢慢喝水,我先上个厕所。

水的滋润与口水相互交织,骑车两小时,面试3分钟,这次我哉在这里了。

  126f58d2fb9caf964b850e69197b6dcb.png dd671ef745e2bc883540c4b16e186379.png

NULL在C/C++中的标准定义

NULL的标准定义

#if !defined(NULL) && defined(__NEEDS_NULL)
#ifdef _cplusplus      
#define NULL 0         // 这里对应C++的情况
#else
#define NULL (void *)0 // 这里对应C语言的情况
#endif

编译器预先定义了一个宏_cplusplus,来判断当前的编译环境是C++的还是C语言的,在C++定义为0,在C语言中定义为(void *)0

在C/C++中的区别

在C语言中,C中的“标准”写法,NULL被替换为一个void* 类型的指针右值,值等于0;由于是void* 类型,可以隐式转化为其它类型的指针。

在C++中,void* 无法自由隐式转换为其它类型的指针,而字面量0可以隐式转换为指针类型。

NULL 的本质是什么

我们从指针,空指针,空指针常量以及指向的内存说起

从指针角度来看

我们看以下定义,p是一个函数内的局部变量,则p的值是随机的,也就是说p是一个野指针。

int func()
{
  int *p; 
  ...
}

再看以下函数,p是一个局部变量,分配在栈上的地址,p的值是(void *)0,实际就是0x00000000,意思就是指针p指向内存的0x00000000地址处。这时候p就不是野指针了。

int func()
{
  int *p = NULL; 
  ...
}

什么是空指针(null pointer)?

如果将空指针常量转换为指针类型,则保证生成的指针(称为空指针)将不相等的值与指向任何对象或函数的指针进行比较。

定义char *p=0后,在之后p的任何一种赋值操作之后,p 都成为一个空指针,即p不指向任何实际的对象或者函数。反过来说,任何对象或者函数的地址都不可能是空指针。

什么是空指针常量(null pointer constant)?

值为0的整数常量表达式,或强制转换为void *类型的表达式,称为空指针常量

空指针(null pointer)指向了哪里的内存

这里标准没有定义,取决于系统的实现。我们常见的空指针一般指向0x00000000地址,即空指针的内部用全0x00000000来表示,也有一些系统用一些特殊的地址值或者特殊的方式表示空指针。

在我们实际写代码时,关键点在于判断哪个是空指针。

NULL 有什么作用

在大部分的CPU中,内存的0x00000000地址处都不是可以随便访问的,所以野指针指向了这个区域可以保证野指针有个安家之所,否则会发生段错误。

当你尝试访问的时候会阻止你,但是有些地址不是只读的,如果一个指针指向了这个地址,你又在不经意间修改了它,可能会导致一些重要的文件被修改,所以指针初始化成NULL是有必要的。

注意不要混用'\0' 和 '0' 和 0  和 NULL

  • '\0'是一个转义字符,他对应的ASCII编码值是0,本质就是0;常用于表示字符串的结尾标志,以判断字符串有没有到头。
  • '0'是一个字符,他对应的ASCII编码值是48,本质是48。
  • 0是一个int类型的数字,本质就是0。
  • NULL是一个表达式,是强制类型转换为void *类型的0,一般用来比较指针是否是一个野指针。

NULL是不是0呢

NULL 就是0?

我们先来看以下代码:

//https://tool.lu/coderunner/
#include
int main()
{    
  int *p=NULL;    
  printf("%s",p); 
}

结果如下:

b3e2b070632b83202c634ce9ed6c3930.png

输出(null) ,在执行int *p=NULL,打印出来空白,实际上p的值为0x00000000,在C语言中,NULL的本质是0,但是这个0不是当一个整形数据来解析,而是当一个内存地址来解析的,代表的是内存的0地址。

(void *)0这个整体表达式表示一个指针,地址在哪里取决于指针变量本身,这个指针变量指向0地址(实际是0地址开始的一段内存)。

NULL 不是0?

如果一个指针被赋予NULL,相当于这个指针执行了0x00000000这个逻辑地址,但是在C语言中0x0000这个逻辑地址用户是不能使用的,所以当你试图取一个指向了NULL的指针的内容时,就会提示段错误,测试一下代码如下:

//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include
int main()
{    
 
 int *node = NULL;
 int p = 0;
 p = *node;
 printf("%d\n",p);
 
 return 0;
}

编译结果如下:

8cc430942706eee1bc618123f59029d5.png

由于指针node执行的是NULL,也就是逻辑地址0x00000000,而这个地址不能访问,所以编译器提示段错误。

那么看到这里你觉得NULL还是0吗?根据宏定义我们知道:(void *)0表示把数值0强制转换为void *类型,所以最后运行结果为0。

变量在定义时,系统会给他分配内存空间,指针变量也是一样,如果指针没有指向的话,那么地址就是随机值,如果不小心用的话就会导致数据错误,从而使程序退出。

NULL 使指针p指向地址0x00000000,在大多数系统中都将0x00000000作为不被使用的地址,所以运用p也不会毁坏数据。

但也有系统会使用地址0x00000000,而将 NULL 定义为其他值,所以不要把 NULL 和 0 等同起来。

我们使用值传递的方式来看,在网上有一个面试题,这里我参考一下代码,能够帮助大家更好的理解,其代码如下:

//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include 

void vPassByFun(int *node)
{
    static int N = 1024;
    node = &N;
}

int main()
{
 
    int *node = NULL;
    int p = 0;
    vPassByFun(node);
    p = *node;
 
    printf("%d\n",p);
    
    return 0;
}

输出结果如下:

a352c1b3f874cd5357116e423f9087e3.png

vPassByFun函数是值传递,node指针变量的值并不受影响,所以这个程序的效果和上一个程序运行结果都是段错误。

如果要让结果为1024,应该怎样写代码呢?我们来这样写代码:

//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include 

void vPassByFun(int ** node)
{
    static int N = 1024;
    *node=&N;
}

int main()
{
 
    int *node = NULL;
    int p = 0;

    vPassByFun(&node);
    p = *node;

    printf("%d\n",p);
 
    return 0;

}

运行结果如下:

d9a5d02a315e224ecc99fb20b0580dec.png

传递一个二级指针也就是传递node指针变量的指针给vPassByFun函数,这样的话结果就对了。

最后

编码过程中,我们需要对自己的指针负责,往往导致bug出现或者找不到问题所在地的就是这种细节。最后,原创不易,希望能够改正文章的错误,多提意见留言,谢谢。

推荐阅读:万字长文系统梳理C++函数指针积分兑换,来就“兑”了

1d7a15debe0caa625b89b7e05994992e.png

d101d34dc6598356b2ed524654390cc7.png

Today's Interaction

今日互动

聊聊面试时你的故事吧,有没有栽在哪里呐?

在留言区参与互动,我们将随机选择精彩留言赠送小程序积分

d101d34dc6598356b2ed524654390cc7.png 94126bb6b7c49cd86403080670ed8057.gif 0ee110ffc323056288288b566455870f.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值