[原]chap7内存分配

王一妃 17/07/17 12:22:15

内存分配

在堆上分配内存

所谓堆是一段长度可变的虚拟内存,始于进程的未初始化数据段末尾(BSS)。通常将堆的当前内存边界称为“program break”。

我们依然结合进程内存布局看来。

这里写图片描述

改变堆的大小其实就像命令内核改变进程的program break位置一样,最初,program break正好位于未初始化数据段末尾之后。在program break的位置抬升后,程序可以访问新分配区域内的任何内存地址,而此时物理内存页尚未分配。内核会在进程首次试图访问这些虚拟内存地址时自动分配新的物理内存页。

这里写图片描述

系统调用brk()会将program break设置为参数addr所指定的位置。由于内存以页为单位进行分配,addr实际为下一个页的边界处。

当试图将program break设置为一个低于其初初始值的位置时,也就是低于&end位置时,可能会导致无法预知的问题。比如说我们常见的分段内存访问错误(segmention fault)

调用sbrk()将program break在原有地址上增加incer参数的大小,调用成功返回 一个program break的地址,返回的是新增大小的起始地址。(可以想到,你可以调用sbrk(0)去跟踪一个堆的情况)

malloc函数族详解

对于malloc函数我们并不陌生,学习c语言时学过malloc的用法。

这里写图片描述

malloc函数在堆上分配size字节大小的内存,返回新内存起始处的地址,⚠️所分配的内存未经初始化,若无法分配内存,malloc返回null。由于malloc返回内存块采用内存对齐的方式,在大多数硬件架构上,malloc是基于8或16字节边界来分配内存。

free函数释放ptr所指向的内存块,一般情况下,free并不降低program break的位置,而是将这块内存添加到空闲内存列表中供后续使用。原因如下:
1. 被释放的内存一般位于堆的中间而不是顶端。
2. 这样会减少sbrk系统调用的使用次数
3. 降低program break的收益不大,因为对于分配大量内存的程序来说,它们通常倾向于持有已分配内存或者是反复释放和重新分配内存,而不是释放所有内存后在持续运行。


我们可以探究一下free()对于progeram break的影响。下面有段程序,在分配了多块内存后,根据命令行参数来释放其中的部分或者全部。程序前两个命令行参数指定分配内存块的数量,大小,第三个参数表示释放内存的循环步长,有两个状态,为1释放每块内存,为2隔一块释放一块内存,第4、5个参数设定需要释放内存块的范围。步长默认设定1。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define MAX_ALLOCS 1000000
#define GN_GT_0 2
#define GN_ANY_BASE 0100
#define GN_BASE_8 0200
#define GN_BASE_16 0400

static long getNum(const char *fname, const char *arg, int flags, const char* name){
    long res;
    char *endptr;
    int base;
    if (arg == NULL || *arg == '\0'){
        printf("error getnum\n");
        return 0;
    }

    base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? :(flags & GN_BASE_16) ? 16 : 10;
    res = strtol(arg, &endptr, base);
    return res;
}

int getInt(const char *arg, int flags, const char *name){
    long res;
    res = getNum("getInt", arg, flags, name);
    if(res > INT_MAX || res < INT_MIN){
        printf("error getnum\n");
        return 0;
    }

    return (int)res;
}

//分配内存块的数量,大小,释放内存的循环步长,需要释放内存块的范围

int main(int argc, char *argv[]){
    char *ptr[MAX_ALLOCS];
    int freeStep, freeMin, freeMax, blockSize, numAllocs, j;
    printf("\n");

    numAllocs = getInt(argv[1], GN_GT_0, "num-allocs");
    blockSize = getInt(argv[2], GN_GT_0 | GN_ANY_BASE, "block-size");

    freeStep = (argc > 3) ? getInt(argv[3], GN_GT_0, "step") : 1;
    freeMin = (argc > 4) ? getInt(argv[4], GN_GT_0, "min") : 1;
    freeMax = (argc > 5) ? getInt(argv[5], GN_GT_0, "mac") : numAllocs;

    printf("Initial program break:    %10p\n", sbrk(0));

    printf("Allocating %d*%d bytes\n", numAllocs, blockSize);
    for(j = 0; j < numAllocs; j++){
        ptr[j] = malloc(blockSize);
        if(ptr[j] == NULL){
            exit(1);
        }
    }

    printf("Program break is now:    %10d\n", sbrk(0));
    printf("Freeing blocks from %d to %d in steps of %d\n", freeMin, freeMax, freeStep);
    for(j = freeMin - 1; j < freeMax; j += freeStep){
        free(ptr[j]);
    }

    printf("After free(), program break is:    %10d\n", sbrk(0));

    return 0;
}

这里写图片描述

首先我分配1000个内存块,然后每隔一块内存释放一块内存。在释放所有内存块后,堆顶仍然为malloc后的大小。

这里写图片描述

然后尝试释放部分内存块的情况,只留最后一块内存不释放,发现堆顶不变。


这里写图片描述

这里有出现了一个问题,根据资料来看,如果在堆顶释放完整的一组连续内存块的话,free会调用sbrk使堆顶降下来,而我运行现实并没有任何变化。这种情况下,free函数的glibc实现会在释放内存时将相邻的空间内存合并为一整块更大的内存,因而也有能力辨识出堆顶的>整个空闲区域。所以实际情况下堆顶应该是降低的。


malloc()和free()的实现

作者:Sequin_YF 发表于2017/7/17 12:22:15 原文链接
阅读:6 评论:0 查看评论