• C标准库堆内存函数


    C标准库堆内存函数有4个:mallocfreecallocrealloc,其函数声明放在了#include <stdlib.h>中,主要用来申请和释放堆内存。

    堆内存的申请和释放(wikichs),需要发起系统调用,会带来昂贵的上下文切换(用户态切换到内核态),十分耗时。另外,这些过程可能是带锁的,难以并行化。

    对于操作系统而言,内存管理的基本单位是页(通常为4K),而不是需要4 Bytes时,就给你分配4 Bytes,释放4 Bytes时,就给你释放4 Bytes。

    因此,为了提升效率,操作系统会调用系统api(windows上是VirtualAllocVirtualFree,其他平台是mmapmunmap)来实现一个Ansi C内存分配器(工作在用户态),供C标准库堆内存函数来使用。

    不同操作系统Ansi C内存分配器实现方案有所不同

    windows:MSVCRT.DLL中使用NT heap实现

    linux:glibc中使用ptmalloc实现

    Android:使用jemalloc实现

    内存碎片与碎片整理

    (1)内存碎片(fragmentation):即空闲内存不能被利用。分为外部碎片(在分配单元间的未使用的内存);内部碎片(在分配单元中未使用的内存)

            

    (2)内存碎片的罪魁祸首就是小块内存的频繁分配

    (3)内存碎片无法避免,只能通过内存分配器算法来减少,例如:接近大小的内存就近分配,释放时能合并就合并,从而减少碎片

    (4)上面讲的内存碎片指的是虚拟内存碎片,OS是不管的,OS只管物理内存。

    (5)平时我们说的内存碎片整理(defragment)或内存紧缩(memory compaction),是指OS对物理内存进行的碎片整理,把分开小的物理内存页移动在一起形成一个大的整块。

             OS整理完物理内存后,会用新的物理内存地址来更新虚拟内存与物理内存映射表,这些对于上层逻辑都是透明的。

             虚拟内存是不能进行碎片整理的,主要原因是碎片整理会移动内存,上层逻辑的指针地址确还是指向老的地址,这会导致致命错误。

    内存分配器的好坏标准:

    (1)分配和释放的效率

    (2)内存分配器的利用率。包括以下几个方面:

            ① 内存对齐导致的不可使用的内存碎片(内部碎片)

            ② 内存碎片太严重,使得分配大块内存时,找不到空闲块,最后导致内存分配失败(外部碎片)

            ③ 内存页始终有被使用,导致分配器无法及时释放该页的内存占用,使得整个内存分配器的内存占用被撑得很大,缩不回去

            

    void* malloc( size_t size )

    形参size为要求分配的字节数。如果函数执行成功,malloc返回获得内存空间的首地址;如果函数执行失败,那么返回值为NULL。

    由于 malloc函数值的类型为void型指针,因此,可以将其值类型转换后赋给任意类型指针,这样就可以通过操作该类型指针来操作从堆上获得的内存空间。

    需要注意的是,malloc函数分配得到的内存空间是未初始化的。可通过调用memset来将其初始化为全0。

    int* p = (int *) malloc(sizeof(int)*100);
     
    if (p == NULL)
    {
        printf("Can’t get memory!
    ");
    }
     
    memset(p, 0, sizeof(int)*100);

    void free( void* ptr )

    从堆上获得的内存,在程序结束之前,系统不会将其自动释放,需要程序员来自己管理,防止出现内存泄露。

    free(p);
    p = NULL;

    void* calloc( size_t num, size_t size )

    calloc函数的功能与malloc函数的功能相似,都是从堆分配内存。

    函数返回值为void*。如果执行成功,从堆上获得size * num大小的堆内存,并返回该内存块的首地址。如果执行失败,函数返回NULL。

    malloc函数不同的是,calloc函数得到的内存块会被初始化为全0。由于提供了2个参数,比较适合为数组申请空间,可以将size设置为数组元素的空间长度,将num设置为数组的容量。

    int* p = (int *) calloc(100,  sizeof(int));
     
    if (p == NULL)
    {
        printf("Can’t get memory!
    ");
    }

    void *realloc( void *ptr, size_t new_size )

    为ptr重新分配大小为size的一块内存空间。下面是这个函数的工作流程:

    ① 如果ptr为NULL,则函数相当于malloc(new_size),试着分配一块大小为new_size的内存,如果成功将地址返回,否则返回NULL。

    ② 如果ptr不为NULL,查看ptr是不是在堆中,如果不是的话会抛出realloc invalid pointer异常。如果ptr在堆中,则查看new_size大小。

    (a)如果new_size大小为0,则相当于free(ptr),将ptr指向的内存空间释放掉,返回NULL。

    (b)如果new_size小于原大小,只有new_size大小的数据会保存,后面地址的数据可能会丢失;

    (c)如果new_size等于原大小,什么都没有做;

    (d)如果new_size大于原大小,则查看ptr指向的位置还有没有足够的连续内存空间,如果有的话,分配更多的空间,返回的地址和ptr相同;

             如果没有的话,在更大的空间内查找,如果找到new_size大小的空间,将旧的内容拷贝到新的内存中,把旧的内存释放掉,则返回新地址,否则返回NULL。

    int* p = (int*)malloc(sizeof(int));
    *p = 3;
    printf("p=%p
    ", p);  // p=0000020B2966E310
    printf("*p=%d
    ", *p); // *p=3
    
    p = (int*)realloc(p, sizeof(int));  // 什么也不做
    printf("p=%p
    ", p);  // p=0000020B2966E310
    printf("*p=%d
    ", *p); // *p=3
    
    p = (int*)realloc(p, 1024 * sizeof(int)); // 创建4KB的内存块  注:4KB为一个页面的大小
    printf("p=%p
    ", p); // p=0000020B29673A50  注:由于不能在原来地址上扩容,会将原来地址内存释放,并在新地址申请内存块
    printf("*p=%d
    ", *p); // *p=3
    
    realloc(p, 0);  // 相当于free(p)
    p = NULL;

    参考

    malloc、calloc、realloc之间的区别

    Linux C 堆内存管理函数malloc()、calloc()、realloc()、free()详解

    内存优化总结:ptmalloc、tcmalloc和jemalloc

    ptmalloc,tcmalloc和jemalloc内存分配策略研究

    操作系统是如何处理进程虚拟地址空间碎片的? 

    内存管理:虚拟地址空间布局

  • 相关阅读:
    zpf 视图
    html5本地存储
    sqlite 使用记录
    百度开放平台
    PHP exit() 输出
    yum笔记
    手动编译安装软件
    while循环
    linux下面测试网络带宽 (转载)
    软件包管理器的核心功能(笔记)
  • 原文地址:https://www.cnblogs.com/kekec/p/14605660.html
Copyright © 2020-2023  润新知