访问非法内存为什么不会出错?

举报
嵌入式大杂烩 发表于 2022/08/22 23:53:53 2022/08/22
【摘要】 大家好,我是杂烩君。 上篇文章分享几个实用的代码片段(第二弹)我们分享了一段代码: 有位读者在朋友圈评论我的文章:(type * )0不是指向空地址吗?(type*)0->member不是访问非法内存了吗?为什么不会出错? 这篇文章我们就来解释这个问题。 GET_MEMBER_SIZE分析 首先,先来解释 ...

大家好,我是杂烩君。

上篇文章分享几个实用的代码片段(第二弹)我们分享了一段代码:

321666bfe111b49caa0807e3e71246c7.png

有位读者在朋友圈评论我的文章:(type * )0不是指向空地址吗?(type*)0->member不是访问非法内存了吗?为什么不会出错?

83d076a7cba6967061bfb75d2f6f3045.png

这篇文章我们就来解释这个问题。

GET_MEMBER_SIZE分析

首先,先来解释 获取结构体成员大小 这个宏定义:


   
  1. // 获取结构体成员大小
  2. #define  GET_MEMBER_SIZE(type, member)   sizeof(((type*)0)->member)

虽然说这里用了 ((type*)0)->member ,看起来似乎有问题?访问非法地址0地址?

其实不是的,注意这里用到了 sizeof操作符 。在C语言中,sizeof() 是一种内存容量度量函数,其字节数的计算是在 编译阶段 进行的。

C语言源程序经过编译器进行词法分析、语法分析等过程生成中间语言(object后缀的文件)编译期间会生成一个字符表和静态分配空间(如new static 全局变量)它们所需的内存空间可以计算出来放在链接库后的可执行文件中(虚拟内存即磁盘),在运行时将放在可执行文件中的偏移量加载到内存的堆中同时将局部变量加载到栈中。

所有内存的开辟只有程序运行的时候才会在物理内存中开辟,即sizeof(((type*)0)->member)的操作不是等到程序运行期间计算的,而是在编译阶段就计算了,所以GET_MEMBER_SIZE宏定义并没有访问非法内存的操作。

进一步的,我们看看上面那个代码实例中,结构体成员的字节数是不是在编译阶段计算出的,编译出汇编文件:

gcc -S member_size.c -o member_size.s
  
6dd59a1f855f9315ac895d22e6867764.png

这个汇编文件我们可能不全看懂所有指令,但大概知道如下三个指令的意思我们就大概可以知道这段汇编代码的意思了。

  • leaq:加载有效地址指令,即将有效地址复制到寄存器中。

  • movl:数据传送指令。

  • call指令:将当前的 IP 或 CS和IP 压入栈中, 转移(jmp)。

可以看到,从上到下,依次会把立即数1、1、2、4、3、12放到esi寄存器中。

为什么是这些立即数?

我们编译运行一下我们的程序:

9c5c02b0ff6997326e48d054b945a117.png

可以看到,正好就是我们需要求的结构体各成员的大小及结构体的大小,所以GET_MEMBER_SIZE(type, member)是在编译阶段起作用的。

其实,GET_MEMBER_SIZE宏定义中的0只是看做一个随意给的地址,方便求成员的大小,如果写为0容易引起误解,不妨可以写为一个任意值,比如修改为100,也是可以计算出各结构体成员的大小的。

63642b5da9d65756d18681b39af443ef.png cf7fbf34959bff79a4c08692291692a4.png

最后,如果 ((type*)0)->member 在其它地方使用,会出现什么问题呢?自然就是这位读者所理解的:操作非法地址。会引起段错误。比如添加如下一行代码:

451d2261e92e4b64c866d699e0da8820.png

编译运行:

7798180460eef81411c73e61afe08bf2.png

段错误的定位方法可查阅往期文章:分享一种你可能不知道的bug定位方法嵌入式段错误的3种调试方法汇总!

GET_MEMBER_OFFSET分析


   
  1. // 获取结构体成员偏移量
  2. #define  GET_MEMBER_OFFSET(type, member)  ((size_t)(&(((type*)0)->member)))

该宏返回的是以0地址为基准的地址,也不涉及访问0地址的操作。

88ea3acab7cb209946a3f33134aa0e09.png

以上就是本次的分享。

期待你的三连支持!

注意

由于微信公众号近期改变了推送规则,为了防止找不到,可以星标置顶,这样每次推送的文章才会出现在您的订阅列表里。

猜你喜欢:

嵌入式大杂烩周记 | 第 14 期

分享几个实用的代码片段(第二弹)

分享一种你可能不知道的bug定位方法

分享一种修改配置文件的方法

《嵌入式大杂烩周记第 13 期:lz4》

《嵌入式并行多线程处理器,了解一下!》

《分享一种修改配置文件的方法》

《分享几个实用的代码片段(附代码例子)》

《废旧板子再利用:搭建无线调试环境!》

《嵌入式段错误的3种调试方法汇总!》

《简说TCP通信非阻塞接收(附代码例子)》

《TCP server如何与多个client通信?》

《TCP通信常用接口的使用封装》

《写国际化的嵌入式代码,时间问题如何处理?》

《Linux命令行万能解压命令》

《嵌入式软件中,总线错误的坑?替大家先踩一步》

《分享嵌入式软件调试方法及几个有用的工具!》

《分享两点提高编程能力的建议!》

在公众号聊天界面回复1024,可获取嵌入式资源;回复 m ,可查看文章汇总

文章来源: blog.csdn.net,作者:嵌入式大杂烩,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/zhengnianli/article/details/126446627

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。