brk(); sbrk()使用

1. brk(); sbrk()

#include <iostream>
#include <cstring>
#include <unistd.h>

using namespace std;


void brk_test(){
    printf("堆结束地址:%p\n", sbrk(0));//sbrk(size); 以偏移方式设置堆的结束地址
    
    char *p = (char *)sbrk(0);        //偏移值为0,表示当前堆的结束地址
    brk(p + 4);                     //将堆的结束设置为 p + 4;相当于给p分配了4字节大小的空间
    printf("堆结束地址:%p\n", sbrk(0));
    
    
    p[0] = 0;
    p[3] = 3;
    p[6] = 1;       // 尽管只申请了4个char大小的位置,但是操作p[6]并不会发生错误
    p[4095] = 1;    // 正常
    // p[4096] = 1;    // Segmentation fault
    printf("%x, %x\n", p[0], p[3]);
    
    
    brk(p + 1);                     //收回3个char大小的空间
    printf("堆结束地址:%p\n", sbrk(0));
    p[3] = 1; // 正常


    brk(p);                         //恢复成最初的结束地址
    printf("堆结束地址:%p\n", sbrk(0));
    // p[3] = 1; // Segmentation fault
   
}

输出:

堆结束地址:0x7fffcb9f8000    // 以0x1000(4K, 页)对齐
堆结束地址:0x7fffcb9f8004
0, 3
堆结束地址:0x7fffcb9f8001
堆结束地址:0x7fffcb9f8000

在Linux中,页与页框的大小一般为4KB,页是虚拟内存中若干个大小相等的储存分区,页框则是每页对应的物理存储区;

所以可以看到上面输出的,堆的结束地址应该是4K对齐的(个人理解)。结束地址0x7fffcb9f8000,0x7fffcb9f8000指向的内存并不属于堆。调用 brk(p + 4) 后,相当于在堆上新申请了4字节的空间,由于页对齐的关系,虚拟内存分配到实际的物理内存时只能以4K为最小单位来分配,0x7fffcb9f8000 ~ 0x7fffcb9f8ffff为1页,所以这一页的剩余空余都属于该程序,所以操作p[6]时不会发生段错误,但这还是属于非法操作。而操作p[4096]时,p[4096]的地址是0x7fffcb9f9000,又是新的一页,而这一页并不属于该程序,没有读写权限,所以会报 'Segmentation fault' 错误。最后调用 brk(p) 恢复成初始状态后,系统收回起始地址为0x7fffcb9f8000的页,所以接着操作p[3]报 'Segmentation fault' 错误。

2. malloc(size_t size)

先看一段程序。

int malloc_test(){
    char *m1 = (char *)malloc(0x10);
    printf("m1: %p \n", m1);

    char *m2 = (char *)malloc(0x10);
    printf("m2: %p \n", m2);

    char *m3 = (char *)malloc(0x20);
    printf("m3: %p \n", m3);

    char *m4 = (char *)malloc(0x30);
    printf("m4: %p \n", m4);

    char *m5 = (char *)malloc(0x60);
    printf("m5: %p \n", m5);


    printf("m1: %08xh %08xh\n", *((int *)m1 - 1), *((int *)m1 - 2));
    printf("m2: %08x %08x\n", *((int *)m2 - 1), *((int *)m2 - 2));
    printf("m3: %08x %08x\n", *((int *)m3 - 1), *((int *)m3 - 2));
    printf("m4: %08x %08x\n", *((int *)m4 - 1), *((int *)m4 - 2));
    printf("m5: %08x %08x\n", *((int *)m5 - 1), *((int *)m5 - 2));
 
}

输出:

m1: 0x7fffc71aee70
m2: 0x7fffc71af0a0
m3: 0x7fffc71af0c0
m4: 0x7fffc71af0f0
m5: 0x7fffc71af130
m1: 00000000h 00000021h
m2: 00000000 00000021
m3: 00000000 00000031
m4: 00000000 00000041
m5: 00000000 00000071

 

使用malloc申请内存时也有地址对齐的情况,0x10对齐。malloc所分配内存的前8字节,其值与申请内存的大小有关,存放的是这一块动态申请的内存的起始地址与下一块动态申请的内存的起始地址的距离(个人理解),再加一。可以约等于系统给你分配的内存大小。(64位测试结果,32位不确定)

3. free(void *ptr)

为什么释放动态内存时只提供首地址就行了,而不用提供具体的大小?

由每块申请的内存的前8个字节可以算出这块内存的大小。

 

例1.

int malloc_test(){
    char *m1 = (char *)malloc(0x10);
    printf("m1: %p \n", m1);

    char *m2 = (char *)malloc(0x10);
    printf("m2: %p \n", m2);

    char *m3 = (char *)malloc(0x20);
    printf("m3: %p \n", m3);

    char *m4 = (char *)malloc(0x30);
    printf("m4: %p \n", m4);

    char *m5 = (char *)malloc(0x60);
    printf("m5: %p \n", m5);


    printf("m1: %08x %08x\n", *((int *)m1 - 1), *((int *)m1 - 2));
    printf("m2: %08x %08x\n", *((int *)m2 - 1), *((int *)m2 - 2));
    printf("m3: %08x %08x\n", *((int *)m3 - 1), *((int *)m3 - 2));
    printf("m4: %08x %08x\n", *((int *)m4 - 1), *((int *)m4 - 2));
    printf("m5: %08x %08x\n", *((int *)m5 - 1), *((int *)m5 - 2));
 

    // *(m5 - 2) = 0x31;
    free(m3);   // 释放m3
    printf("\n释放m3后,m3:%08x %08x\n", *((int *)m3 - 1), *((int *)m3 - 2));  // 并没有变化

    char *m6 = (char *)malloc(0x20);    // 申请和m3相同大小的内存
    printf("m6: %p \n", m6);
    printf("m6: %08x %08x\n", *((int *)m6 - 1), *((int *)m6 - 2));

}

输出:

m1: 0x7fffbd602e70
m2: 0x7fffbd6030a0
m3: 0x7fffbd6030c0
m4: 0x7fffbd6030f0
m5: 0x7fffbd603130
m1: 00000000 00000021
m2: 00000000 00000021
m3: 00000000 00000031
m4: 00000000 00000041
m5: 00000000 00000071

释放m3后,m3:00000000 00000031
m6: 0x7fffbd6030c0
m6: 00000000 00000031

可以看到,free掉m3后,m3前8个字节存的值并没有变化。

再次申请和m3大小相同的m6时,系统把原来m3的内存分配给了m6。

所以malloc并不一定是从堆的尾部开始分配新的内存,优先分配堆内的、大小合适的废弃内存。

 

例2.

int malloc_test(){
    char *m1 = (char *)malloc(0x10);
    printf("m1: %p \n", m1);

    char *m2 = (char *)malloc(0x10);
    printf("m2: %p \n", m2);

    char *m3 = (char *)malloc(0x20);
    printf("m3: %p \n", m3);

    char *m4 = (char *)malloc(0x30);
    printf("m4: %p \n", m4);

    char *m5 = (char *)malloc(0x60);
    printf("m5: %p \n", m5);


    printf("m1: %08x %08x\n", *((int *)m1 - 1), *((int *)m1 - 2));
    printf("m2: %08x %08x\n", *((int *)m2 - 1), *((int *)m2 - 2));
    printf("m3: %08x %08x\n", *((int *)m3 - 1), *((int *)m3 - 2));
    printf("m4: %08x %08x\n", *((int *)m4 - 1), *((int *)m4 - 2));
    printf("m5: %08x %08x\n", *((int *)m5 - 1), *((int *)m5 - 2));
 

    free(m3);   // 释放m3
    *((int *)m3 - 2) = 0xFF;    // 修改大小标志
    printf("\n释放m3后,m3:%08x %08x\n", *((int *)m3 - 1), *((int *)m3 - 2));  // 并没有变化

    char *m6 = (char *)malloc(0x20);    // 申请和m3相同大小的内存
    printf("m6: %p \n", m6);
    printf("m6: %08x %08x\n", *((int *)m6 - 1), *((int *)m6 - 2));

}

输出:

 

m1: 0x7fffd5e62e70
m2: 0x7fffd5e630a0
m3: 0x7fffd5e630c0
m4: 0x7fffd5e630f0
m5: 0x7fffd5e63130
m1: 00000000 00000021
m2: 00000000 00000021
m3: 00000000 00000031
m4: 00000000 00000041
m5: 00000000 00000071

释放m3后,m3:00000000 000000ff
m6: 0x7fffd5e630c0
m6: 00000000 000000ff

 在m3被释放后,修改m3的大小标志,再申请大小相同的m6,系统还是把原来m3的内存分配给了m6。

说明申请内存时,前8个字节并不是用来确定内存块大小的,系统的其他地方还记录着每一块内存的使用情况。

 

例3.

int malloc_test(){
    char *m1 = (char *)malloc(0x10);
    printf("m1: %p \n", m1);

    char *m2 = (char *)malloc(0x10);
    printf("m2: %p \n", m2);

    char *m3 = (char *)malloc(0x20);
    printf("m3: %p \n", m3);

    char *m4 = (char *)malloc(0x30);
    printf("m4: %p \n", m4);

    char *m5 = (char *)malloc(0x60);
    printf("m5: %p \n", m5);


    printf("m1: %08x %08x\n", *((int *)m1 - 1), *((int *)m1 - 2));
    printf("m2: %08x %08x\n", *((int *)m2 - 1), *((int *)m2 - 2));
    printf("m3: %08x %08x\n", *((int *)m3 - 1), *((int *)m3 - 2));
    printf("m4: %08x %08x\n", *((int *)m4 - 1), *((int *)m4 - 2));
    printf("m5: %08x %08x\n", *((int *)m5 - 1), *((int *)m5 - 2));
 

    free(m3);   // 释放m3
    *((int *)m3 - 2) = 0xff;    // 修改大小标志
    printf("\n释放m3后,m3:%08x %08x\n", *((int *)m3 - 1), *((int *)m3 - 2));  // 并没有变化

    char *m6 = (char *)malloc(0x20);    // 申请和m3相同大小的内存
    printf("m6: %p \n", m6);
    printf("m6: %08x %08x\n", *((int *)m6 - 1), *((int *)m6 - 2));

    // *((int *)m3 - 2) = 0x31;
    free(m6);
    m6 = (char *)malloc(0x20);    // 申请和m3相同大小的内存
    printf("m6: %p \n", m6);
    printf("m6: %08x %08x\n", *((int *)m6 - 1), *((int *)m6 - 2));

}

输出:

m1: 0x7ffff5673e70
m2: 0x7ffff56740a0
m3: 0x7ffff56740c0
m4: 0x7ffff56740f0
m5: 0x7ffff5674130
m1: 00000000 00000021
m2: 00000000 00000021
m3: 00000000 00000031
m4: 00000000 00000041
m5: 00000000 00000071

释放m3后,m3:00000000 000000ff
m6: 0x7ffff56740c0
m6: 00000000 000000ff
munmap_chunk(): invalid pointer
Aborted (core dumped)

m3的大小标志改成无规律的0xff,先释放m3, 再申请m6后,再释放m6。此时报无效指针的错误‘munmap_chunk(): invalid pointer’。

说明释放内存时,free只根据传入地址的前8个字节来判断内存的块大小,而0xff并不满足内存分配规则,所以报错。

将0xff改成0x21, 0x31...等满足规则的值,程序正常运行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值