空指针,段错误,这场面试我栽倒在这里了!
作者 | 李肖遥
来源 | 技术让梦想更伟大(ID:gh_f7effb2fbc1c)
面试官:满头的汗的,来面试的路一定很远吧?
还好还好,骑车不到俩小时
面试官:来先喝杯水,咱们面试不急,边喝边聊
哇,谢谢您啦,咕隆咕隆喝下半杯
面试官:那咱们开始吧,看你项目做的还不少啊,不错不错,咱们随便聊聊。
哇,“这面试官还不错,感觉有戏”
面试官:说说空指针NULL是本质什么,与0一样吗
嗯嗯,这个,NULL不就是0吗?
面试官:你确定是吗,面带微笑,淡定的眼神......
嗯嗯,应该是吧,,,不是吧,汗又开始冒起了来了......
面试官:嗯嗯,了解了,不急慢慢喝水,我先上个厕所。
水的滋润与口水相互交织,骑车两小时,面试3分钟,这次我栽在这里了。
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<stdio.h>
int main()
{ int *p=NULL; printf("%s",p);
}
结果如下:
输出 (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<stdio.h>
int main()
{ int *node = NULL;int p = 0;p = *node;printf("%d\n",p);return 0;
}
编译结果如下:
由于指针 node 执行的是 NULL,也就是逻辑地址 0x00000000,而这个地址不能访问,所以编译器提示段错误。
那么看到这里你觉得 NULL 还是 0 吗?根据宏定义我们知道:(void *)0 表示把数值 0 强制转换为 void * 类型,所以最后运行结果为 0。
变量在定义时,系统会给他分配内存空间,指针变量也是一样,如果指针没有指向的话,那么地址就是随机值,如果不小心用的话就会导致数据错误,从而使程序退出。
NULL 使指针 p 指向地址 0x00000000,在大多数系统中都将 0x00000000 作为不被使用的地址,所以运用 p 也不会毁坏数据。
但也有系统会使用地址 0x00000000,而将 NULL 定义为其他值,所以不要把 NULL 和 0 等同起来。
我们使用值传递的方式来看,在网上有一个面试题,这里我参考一下代码,能够帮助大家更好的理解,其代码如下:
//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include <stdio.h>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;
}
输出结果如下:
vPassByFun 函数是值传递,node 指针变量的值并不受影响,所以这个程序的效果和上一个程序运行结果都是段错误。
如果要让结果为 1024,应该怎样写代码呢?我们来这样写代码:
//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include <stdio.h>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;}
运行结果如下:
传递一个二级指针也就是传递 node 指针变量的指针给 vPassByFun 函数,这样的话结果就对了。
最后
编码过程中,我们需要对自己的指针负责,往往导致 bug 出现或者找不到问题所在地的就是这种细节。最后,原创不易,希望能够改正文章的错误,多提意见留言,谢谢。
更多精彩推荐
☞连按 5 次 Shift 重改 CMD 和密码并重启电脑,这个漏洞你不能不知道!
☞如何判断领导是在培养你,还是压榨你?看这4点就够了
☞微信iOS版朋友圈可删除评论;华为回应安卓系统断供;银河麒麟操作系统V10发布| 极客头条
☞万字长文总结机器学习的模型评估与调参 | 附代码下载
☞什么?一个核同时执行两个线程?
☞交易平台搅局者“Uniswap之父”,不会编程的“失业青年”,出手即巅峰
点分享点点赞点在看
空指针,段错误,这场面试我栽倒在这里了!相关推荐
- xxl-job 执行结果是空_空指针,段错误,这场面试我栽倒在这里了!
空指针,段错误,这场面试我栽倒在这里了! NULL在C/C++中的标准定义 NULL的标准定义 #if !defined(NULL) && defined(__NEEDS_NULL) ...
- Rust语言——无虚拟机、无垃圾收集器、无运行时、无空指针/野指针/内存越界/缓冲区溢出/段错误、无数据竞争...
2006年,编程语言工程师Graydon Hoare利用业余时间启动了Rust语言项目.该项目充分借鉴了C/C++/Java/Python等语言的经验,试图在保持良好性能的同时,克服以往编程语言所存在 ...
- 宋宝华:让Linux的段错误(segmentation fault)不再是一个错误
今天周末,娃儿们配合不闹事,写一篇短小精悍的文章吧,反正文章长了大家也没时间看.今天文章的目标是,如何在进程访问空指针等情况下,产生段错误后,不再退出而是继续运行. 这件事情,对于熔断(meltdow ...
- 如何系统有效地准备一场面试
如何系统有效地准备一场面试 1. 澄清阶段 澄清阶段的任务,是理清我们有什么.想要什么,明确自己的求职目标.这个阶段的工作,可以分为下面 6 部分: 明确自己的职业价值观 盘点自我价值 筛选公司 分析 ...
- 9个offer,12家公司,35场面试,从微软到谷歌,应届计算机毕业生的2012求职之路[对书籍加了注释]
9个offer,12家公司,35场面试,从微软到谷歌,应届计算机毕业生的2012求职之路[对书籍加了注释] 引言:同样是求职,和前面一篇blog一样,看出了很大的差距,多花了些时间,把作者列举的书籍通 ...
- (转载)职场面试如何自我评价,面试回答问题技巧
转载:http://www.201980.com/lizhi/zhichang/1541.html 职场里,职场面试是很平常不过的事了,不一定是职场新人为了找工作的面试,参加工作以后,竞聘岗位有竞聘演 ...
- java 主动抛出 段错误_段错误产生的原因~
原文:https://blog.csdn.net/qq_29350001/article/details/53780697 一.什么是段错误? 一旦一个程序发生了越界访问,cpu 就会产生相应的保护, ...
- linux 下 C 编程和make的方法 (十、C版的try catch 捕捉段错误和异常处理)
2019独角兽企业重金招聘Python工程师标准>>> 哇塞,C语言有try catch吗?当然没有.倒..可能有人说了,那你野鬼说没有的东西做什么. 这里需要重申一下,所谓正向设计 ...
- linux下gdb使用core文件调试程序,解决“段错误核心已转储“的问题
一.core文件介绍以及用途: 1.core文件是什么? core文件包含了程序运行时的内存状态.寄存器状态.堆栈指针.内存管理信息以及各个函数使用堆栈信息等等: 当程序运行过程中出现段错误(Segm ...
最新文章
- 博客 rss 如何使用_如何使用RSS从您的GatsbyJS博客自动交叉发布
- python接口自动化测试框架链接数据库_python接口自动化测试框架实现之操作mysq数据库...
- 艾伟也谈项目管理,谈谈如何说“不”
- react取消捕获_react 异常捕获
- 【HDU - 2570】迷瘴 (贪心,水题,排序,卡精度有坑)
- springcloud项目的启动顺序_Spring Cloud微服务项目完整示例,含注册中心,网关,断路器等等...
- Trie树(c++实现)
- python函数分为哪几类_Python 69个内置函数分8类总结
- php面试编程题_一位资深php程序员在北京的面试30个题目
- OGG迁移大库注意要点之impdp导入产生大量归档
- AI(4)---AI+IOT=?
- c语言程序设计中&是什么,C语言程序设计是什么
- java导入hbase_如何用java导入hbase.dat文件
- VBScript基础
- 【气动学】基于龙格库塔算法实现外弹道仿真含Matlab源码
- prometheus中step或resolution的含义
- iptables限速 iptables限制流量
- 计算机应用物联网应用技术论文,物联网的关键技术及计算机物联网的应用研究...
- Unity中控制物体运动
- 如何把PPT连背景一起复制?