访问非法内存为什么不会出错?
Posted 嵌入式大杂烩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了访问非法内存为什么不会出错?相关的知识,希望对你有一定的参考价值。
大家好,我是杂烩君。
上篇文章分享几个实用的代码片段(第二弹)我们分享了一段代码:
有位读者在朋友圈评论我的文章:(type * )0不是指向空地址吗?(type*)0->member不是访问非法内存了吗?为什么不会出错?
这篇文章我们就来解释这个问题。
GET_MEMBER_SIZE分析
首先,先来解释 获取结构体成员大小
这个宏定义:
// 获取结构体成员大小
#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
这个汇编文件我们可能不全看懂所有指令,但大概知道如下三个指令的意思我们就大概可以知道这段汇编代码的意思了。
leaq:加载有效地址指令,即将有效地址复制到寄存器中。
movl:数据传送指令。
call指令:将当前的 IP 或 CS和IP 压入栈中, 转移(jmp)。
可以看到,从上到下,依次会把立即数1、1、2、4、3、12放到esi寄存器中。
为什么是这些立即数?
我们编译运行一下我们的程序:
可以看到,正好就是我们需要求的结构体各成员的大小及结构体的大小,所以GET_MEMBER_SIZE(type, member)是在编译阶段起作用的。
其实,GET_MEMBER_SIZE宏定义中的0只是看做一个随意给的地址,方便求成员的大小,如果写为0容易引起误解,不妨可以写为一个任意值,比如修改为100,也是可以计算出各结构体成员的大小的。
最后,如果 ((type*)0)->member
在其它地方使用,会出现什么问题呢?自然就是这位读者所理解的:操作非法地址。会引起段错误。比如添加如下一行代码:
编译运行:
段错误的定位方法可查阅往期文章:分享一种你可能不知道的bug定位方法、嵌入式段错误的3种调试方法汇总!
GET_MEMBER_OFFSET分析
// 获取结构体成员偏移量
#define GET_MEMBER_OFFSET(type, member) ((size_t)(&(((type*)0)->member)))
该宏返回的是以0地址为基准的地址,也不涉及访问0地址的操作。
以上就是本次的分享。
期待你的三连支持!
注意
由于微信公众号近期改变了推送规则,为了防止找不到,可以星标置顶,这样每次推送的文章才会出现在您的订阅列表里。
猜你喜欢:
在公众号聊天界面回复1024,可获取嵌入式资源;回复 m ,可查看文章汇总
以上是关于访问非法内存为什么不会出错?的主要内容,如果未能解决你的问题,请参考以下文章
C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针