c的程序要手动管理内存的,所有的数据(结构)都可以分为两种存储方式,连续存储,顾名思义申请一片连续的内存以供使用(数组、结构体、共用体、栈、队列);非连续存储,顾名思义断断续续的的存储,那有一点这有一点(链表,链表实现的树)。
对齐填充,这个东西很有意思,看似无用其实很关键,比如有个结构体:
struct xo { int i;char j,int k};
结构体是连续存储的,我们设这个结构体起始地址为0x0,那么 i 的地址 就是 0x0 、j就是0x4、k就是0x5,可是呢OS(操作系统)不允许这样做,为什么呢?因为不好访问,windows就规定 k个字节数据的地址必须是k的整数倍,同理其他的OS也有类似规定,那么xo本来只占9字节,对齐填充之后就变成了12字节,也就是把j的1字节后面硬填了三个空白字节。这种对齐要求,在OS中比比皆是(尤其是在申请内存的时候,往往都是2、4、8的倍数)。
栈帧结构,函数是通过栈来调用的,以栈帧为单位,当前函数就是栈顶帧,栈帧格式大概如下:
本地、临时变量表 |
构造参数表 |
。。。。。一些东西 |
返回地址 +4(偏移地址) |
保存的%ebp (这里是栈顶) |
栈是自高地址向低地址的,通过栈顶指针+偏移地址访问栈帧里的东西,栈帧的大小在调用函数(过程)的时候固定了(同样需要对齐),这也就可以解释为什么数组越界的后果非常严重了,函数中定义的数组一但越界,也就是说栈帧的大小不够了,装不下他了,那么他就会向上覆盖变量表、甚至上一帧的返回地址等等,使得程序不知所措甚至崩溃。
同样栈帧的地址固定极容易被攻击,导致缓冲区泄露(想一想为什么呢?) 当然现在的OS都采取了各种办法随机化栈地址对栈进行保护。
时间: 2024-11-07 07:01:15