c++杂谈-2(栈堆的内存布局)


一、动态连接库的原理

.data 数据段;.code 代码段
在这里插入图片描述

全局偏移表(GOT,Global Offset Table)引入.data数据段。

查看GOT
readelf -S ./math.so

在这里插入图片描述
在动态链接库加载的时候GOT表项会被修改为真正的代码段地址:
在这里插入图片描述
动态链接库的.code代码段到GOT可以相对寻址(上图中的黄色箭头——查表过程)。结果动态链接库的.code代码段可以在内存中被所有进程共享,通过代码段的地址相对寻址GOT表项,查表后再确定其他动态库的代码段地址。

在这里插入图片描述
.data数据段中的GOT表在每个进程中保留一份副本。
在这里插入图片描述

地址无关代码(Position Independent Code):
动态链接库.so加载内存后.code代码段为所有进程共享,但.data数据段中开辟一个GOT,GOT及.data在每个进程中保留一份独立的副本。通过共享的.code相对于.data寻址查找GOT表项,从而定位(重定位)其他.code的方式。

以PIC方式进行的动态连接,动态库不需要做任何的修改,被加载到任意内存地址都能够正常运行。

对代码段函数的重定位,与其在程序一开始就对所有函数重定位,不如将这个过程推迟到函数第一次被调用的时候。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
于是我们再次调用函数的时候就会直接跳转到动态库中真正的函数实现。

二、编译和链接

Linux下的目标文件是ELF(Executable and Linkable Format)格式——可执行文件的通用格式。Windows下的目标文件是PE(Portable Executable)格式。

readelf -h main.o
readelf -S main.o
objdump -s -d main.o
objdump -r main.o

其中.text就是前边的.code代码区,.data就是数据区。
在这里插入图片描述

三、栈堆的增长方向

在这里插入图片描述

四、extern和static的区别

编译的时候,头文件.h是不编译的(一般里边只有声明),编译的基本单元是.c或者.cpp文件(.c和.cpp都是编译单元)。编译预处理#include其实就是把.h中的声明拷贝到编译单元中。

  • extern
    默认外部连接。定义的变量可以被其他文件使用。当编译单元中使用的变量在另一个文件中定义时,为了使得编译通过需要使用extern告诉编译器变量在另外的文件中定义,并需要启用对变量的外部链接。
    外部函数也类似。
  • static
    默认内部连接。定义的变量只能在当前文件内访问。静态函数也类似。

五、内存字节对齐

内存地址的寻址存在字节对齐,即寻址的地址值是某个数的整数倍(比如 2 n 2^n 2n),如果不足的就补齐。

可以使用预编译指令
#pragma pack(show)
以warning的形式显示出#pragma pack(n)中的n。

内存对齐的规则:

  • 对于基本数据类型,它的地址只要求是它长度的整数倍。
  • 对于自定义数据类型,比如结构体,对齐规则如下
    1. 数组成员。第一个数组成员应该放在offset为0的地方,以后每个数组成员应该放在offset为min(当前成员的大小,#pragma pack(n)中的n)整数倍的地方开始。比如int 在32位机器上为4字节,#pragma pack(2),那么从2的倍数的地方开始存储。
    2. 结构体总大小(sizeof的值),必须是min(结构体内部最大成员,#pragma pack(n)中的n)的整数倍,不足要补齐。
    3. 如果一个结构体B中嵌套一个结构体A,还是以最大成员类型的大小对齐,但是结构体A的起点为A内部最大成员的整数倍的地方。struct B里边嵌套struct A,A里边有char,int,double,则A应该从8的整数倍开始存储。结构体A中的成员的对齐规则仍满足规则1和2。

计算参考:https://blog.csdn.net/x2528238270/article/details/120798606
算法参考:https://blog.csdn.net/u012342808/article/details/124361370
理解参考:https://blog.csdn.net/u014545085/article/details/107891707

字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:

  1. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
  2. 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;例如上面第二个结构体变量的地址空间。
  3. 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值