结构体和指针
结构体内包含多个成员,这些成员之间在内存中是如何存放的呢?
比如:
<p>struct fraction {</p><p> int num; // 整数部分</p><p> int denom; // 小数部分</p><p>};</p><p>
</p><p>struct fraction fp;</p><p>fp.num = 10;</p><p>fp.denom = 2;</p>
这是一个定点小数结构体,它在内存占 8 个字节(这里不考虑内存对齐),两个成员域是这样存储的:
我们把 10 放在了结构体中基地址偏移为 0 的域,2 放在了偏移为 4 的域。
接下来我们做一个这样的操作:
<p>((fraction*)(&fp.denom))->num = 5; </p><p>((fraction*)(&fp.denom))->denom = 12; </p><p>printf("%d\n", fp.denom); // 输出多少?</p>
上面这个究竟会输出多少呢?自己先思考下噢~
接下来我分析下这个过程发生了什么:
首先,&fp.denom表示取结构体 fp 中 denom 域的首地址,然后以这个地址为起始地址取 8 个字节,并且将它们看做一个 fraction 结构体。
在这个新结构体中,最上面四个字节变成了 denom 域,而 fp 的 denom 域相当于新结构体的 num 域。
因此:
((fraction*)(&fp.denom))->num = 5
实际上改变的是 fp.denom,而
((fraction*)(&fp.denom))->denom = 12
则是将最上面四个字节赋值为 12。
当然,往那四字节内存写入值,结果是无法预测的,可能会造成程序崩溃,因为也许那里恰好存储着函数调用栈帧的关键信息,也可能那里没有写入权限。
大家初学 C 语言的很多 coredump 错误都是类似原因造成的。
所以最后输出的是 5。
为什么要讲这种看起来莫名其妙的代码?
就是为了说明结构体的本质其实就是一堆的变量打包放在一起,而访问结构体中的域,就是通过结构体的起始地址,也叫基地址,然后加上域的偏移。
其实,C++、Java 中的对象也是这样存储的,无非是他们为了实现某些面向对象的特性,会在数据成员以外,添加一些 Head 信息,比如C++ 的虚函数表。
实际上,我们是完全可以用 C 语言去模仿的。
这就是为什么一直说 C 语言是基础,你真正懂了 C 指针和内存,对于其它语言你也会很快的理解其对象模型以及内存布局。
页:
[1]