Linux 内存分配/内存管理 相关接口

news2024/9/27 19:27:22

Linux 内存分配/内存管理 相关接口

    • 分配栈内存
      • alloca()
    • 分配堆内存
      • 直接分配
        • malloc()
      • 分配初始化空间
        • calloc()
      • 分配对齐空间
        • posix_memalign()
        • aligned_alloc()
        • 过时:memalign()
        • 过时:valloc()
        • 过时:pvalloc()
    • 修改块的大小
      • realloc()
      • reallocarray()
    • 修改内存分配参数
      • mallopt()
    • 检查内存一致性
      • mcheck()
      • mprobe()
    • 读取内存使用信息
      • mallinfo2()
      • malloc_usable_size()
    • 释放空间
      • free()
    • 参考文献

一个进程可以通过增加堆的尺寸来分配内存。堆是一个用来存放动态分配的变量的空间,它位于未初始化数据段(bss)之后,它的顶叫做 program break ,这个地方会根据内存的分配和释放而变化。一般来讲C语言堆内存的分配一般会使用 malloc() ,它是基于 brk()和sbrk() 所实现的。

img

Linux GNU C 中动态内存分配相关的接口如下:

void *malloc (size_t size)
    分配一个 size 字节的块。

void free (void *addr)
    释放之前由 malloc 分配的块。

void *realloc (void *addr, size_t size)
    使以前由 malloc 分配的块更大或更小,可能通过将其复制到新位置。

void *reallocarray (void *ptr, size_t nmemb, size_t size)
    将 malloc 先前分配的块的大小更改为 nmemb * size 个字节,与 realloc 一样。

void *calloc (size_t count, size_t eltsize)
    使用 malloc 分配一个 count * eltsize 字节块,并将其内容设置为零。

void *valloc (size_t size)
    从页边界开始分配大小字节块。

void *aligned_alloc (size_t size, size_t alignment)
    从 alignment 倍数的地址开始分配 size 字节块。

int posix_memalign (void **memptr, size_t alignment, size_t size)
    从 alignment 倍数的地址开始分配 size 字节块。

void *memalign (size_t size, size_t boundary)
    分配一个 size 字节的块,从一个 boundary 倍数的地址开始。

int mallopt (int param, int value)
    调整可调参数。

int mcheck (void (*abortfn) (void))
    告诉 malloc 对动态分配的内存执行偶尔的一致性检查,并在发现不一致时调用 abortfn。

struct mallinfo2 mallinfo2 (void)
    返回有关当前动态内存使用情况的信息。

下面将逐一介绍:

分配栈内存

alloca()

函数 alloca() 支持一种半动态分配,其中块是动态分配但自动释放的。 使用 alloca() 分配块是一个显式操作;您可以根据需要分配任意数量的块,并在运行时计算大小。但是,当您退出调用 alloca() 的函数时,所有块都会被释放, 就像它们是在该函数中声明的自动变量一样。没有办法明确地释放空间

#include <stdlib.h>

// size: 分配size个字节大小的内存空间
void * alloca (size t size)

返回值 : 成功将返回内存空间的指针。

不要在函数调用的参数中使用 alloca() ——你会得到不可预知的结果,因为 alloca() 的栈空间会出现在函数参数空间的中间的栈上。

要避免的一个例子是:

foo (x, alloca (4), y);

可以修改为:

void *y;
y = alloca(size);
func(x, y, z);

alloca()malloc() 对比

int
open2 (char *str1, char *str2, int flags, int mode)
{
    char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
    stpcpy (stpcpy (name, str1), str2);
    return open (name, flags, mode);
}
int
open2 (char *str1, char *str2, int flags, int mode)
{
    char *name = malloc (strlen (str1) + strlen (str2) + 1);
    int desc;
    if (name == 0)
        fatal ("virtual memory exceeded");
    stpcpy (stpcpy (name, str1), str2);
    desc = open (name, flags, mode);
    free (name);
    return desc;
}

作为使用 alloca 的示例,这里有一个函数,它打开一个由两个参数字符串连接而成的文件名,并返回一个文件描述符或-1表示失败。如上所见,使用 alloca 更简单。但是 alloca 还有其他更重要的优点和一些缺点:

alloca 可能优于 malloc 的原因

  • 使用 alloca 浪费的空间非常小,而且速度非常快。(它由 GNU C 编译器开放编码。)。
  • 由于 alloca 没有用于不同大小的块的单独池,因此用于任何大小块的空间都可以重用于任何其他大小。 alloca 不会导致内存碎片。
  • 使用 longjmp 完成的非本地退出在通过调用 alloca 的函数退出时自动释放使用 alloca 分配的空间。这是使用 alloca 的最重要原因。

为了说明这一点,假设您有一个函数 open_or_report_error 如果成功则返回一个描述符,如 open,但如果失败则不返回其调用者。如果文件无法打开,它会打印一条错误消息并使用 longjmp 跳到程序的命令级别。让我们更改 open2 以使用此子例程:

int
open2 (char *str1, char *str2, int flags, int mode)
{
    char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
    stpcpy (stpcpy (name, str1), str2);
    return open_or_report_error (name, flags, mode);
}

由于 alloca 的工作方式,即使发生错误,它分配的内存也会被释放,无需特别努力。

相比之下,如果以这种方式更改 open2 的先前定义(使用 malloc 和 free),则会产生内存泄漏。即使您愿意进行更多更改来修复它,也没有简单的方法可以做到这一点。

alloca malloc 相比的缺点

  • 如果您尝试分配的内存超出机器所能提供的范围,您将不会收到干净的错误消息。相反,你会得到一个致命的信号,就像你从无限递归中得到的一样;可能是分段违规(请参阅程序错误信号)。
  • 一些非 GNU 系统不支持 alloca,因此它的可移植性较差。然而,用 C 语言编写的 alloca 的较慢仿真可用于具有此缺陷的系统。

GNU C可变大小数组

在 GNU C 中,您可以用一个可变大小的数组来替换大多数使用的 alloca 。下面是 open2 的样子:

int open2 (char *str1, char *str2, int flags, int mode)
{
    char name[strlen (str1) + strlen (str2) + 1];
    stpcpy (stpcpy (name, str1), str2);
    return open (name, flags, mode);
}

但是 alloca 并不总是等价于可变大小的数组,原因如下:

  • 可变大小数组的空间在数组名称范围的末尾被释放。用 alloca 分配的空间一直保留到函数结束。

  • 可以在循环中使用 alloca ,在每次迭代时分配一个额外的块。这对于可变大小的数组是不可能的。

注意:如果在一个函数中混合使用 alloca 和可变大小的数组,退出声明了可变大小数组的作用域会释放在该作用域执行期间分配有 alloca 的所有块。

分配堆内存

直接分配

malloc()

malloc() 的函数原型如下:

#include <stdlib.h> 

// size:需要分配的内存大小,以字节为单位
void *malloc(size_t size);

返回值 :返回值为 void * 类型,如果申请分配内存成功,将返回一个指向该段内存的指针, void * 并不是说没有返回值或者返回空指针,而是返回的指针类型未知,所以在调用 malloc() 时通常需要进行强制类型转换,将 void * 指针类型转换成我们希望的类型如果分配内存失败(譬如系统堆内存不足)将返回 NULL ,如果参数size为0,返回值也是 NULL

如果没有更多可用空间,则 malloc 返回一个空指针。您应该检查每次调用 malloc 的值。编写一个调用 malloc 并在值为空指针时报告错误的子例程很有用,仅当值非零时才返回。该函数通常称为 xmalloc

void *xmalloc (size_t size)
{
    void *value = malloc (size);
    if (value == 0)
        fatal ("virtual memory exhausted");
    return value;
}

这是一个使用 malloc 的真实示例(通过 xmalloc )。函数 savestring 会将一系列字符复制到新分配的以空字符结尾的字符串中:

char *savestring (const char *ptr, size_t len)
{
    char *value = xmalloc (len + 1);
    value[len] = '\0';
    return memcpy (value, ptr, len);
}

malloc 为您提供的块保证是对齐的,以便它可以保存任何类型的数据。在 GNU 系统上,地址在 32 位系统上始终是 8 的倍数,在 64 位系统上始终是 16 的倍数 。很少需要任何更高的边界(例如页面边界);对于这些情况,请使用 aligned_alloc posix_memalign

分配初始化空间

calloc()

calloc() 函数用来动态地分配内存空间并初始化为0,其函数原型如下所示:

#include <stdlib.h> 

// 在堆中申请nmemb个长度为size的连续空间,并将每一个字节都初始化为0
void *calloc(size_t nmemb, size_t size);

返回值 :分配成功返回指向该内存的地址,失败则返回 NULL

calloc()malloc() 的一个重要区别是: calloc() 在动态分配完内存后,自动初始化该内存空间为零malloc() 不初始化,里边数据是未知的垃圾数据 。下面的两种写法是等价的:

// calloc()分配内存空间并初始化 
char *buf1 = (char *)calloc(10, 2); 

// malloc()分配内存空间并用memset()初始化 
char *buf2 = (char *)malloc(10 * 2); 
memset(buf2, 0, 20);

使用示例:

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

int main(int argc, char *argv[]) 
{
    int *base = NULL; 
    int i; 
    
    /* 校验传参 */ 
    if (2 > argc) exit(-1); 
    
    /* 使用calloc申请内存 */ 
    base = (int *)calloc(argc - 1, sizeof(int)); 
    if (NULL == base) 
    { 
        printf("calloc error\n"); 
        exit(-1); 
    } 
    
    /* 将字符串转为int型数据存放在base指向的内存中 */ 
    for (i = 0; i < argc - 1; i++) 
        base[i] = atoi(argv[i+1]); 
    
    /* 打印base数组中的数据 */ 
    printf("你输入的数据是: "); 
    for (i = 0; i < argc - 1; i++) 
        printf("%d ", base[i]); putchar('\n'); 
    
    /* 释放内存 */ 
    free(base); 
    exit(0); 
}

运行结果:

image-20230506112950260

分配对齐空间

posix_memalign()

函数原型如下:

#include <stdlib.h>

// memptr:void **类型的指针,内存申请成功后会将分配的内存地址存放在*memptr中
// alignment:设置内存对其的字节数,alignment必须是2的幂次方(譬如2^4、2^5、2^8等),同时也要是sizeof(void *)的整数倍
int posix_memalign(void **memptr, size_t alignment, size_t size);

posix_memalign() 函数用于在堆上分配 size 个字节大小的对齐内存空间,将 *memptr 指向分配的空间,分配的内存地址将是参数 alignment 的整数倍。 参数 alignment 表示对齐字节数, alignment 必须是2的幂次方(譬如24、25、2^8等),同时也要是 sizeof(void *) 的整数倍,对于32位系统来说, sizeof(void *) 等于4,如果是64位系统 sizeof(void *) 等于8。

返回值 :成功将返回0;失败返回非0值

使用示例:

#include <stdio.h> 
#include <stdlib.h> 
int main(int argc, char *argv[])
{ 
    int *base = NULL; 
    int ret; 
    
    /* 申请内存: 256字节对齐 */ 
    ret = posix_memalign((void **)&base, 256, 1024); 
    if (0 != ret) 
    { 
        printf("posix_memalign error\n"); 
        exit(-1); 
    }
    
    /* 使用内存 */ 
    // base[0] = 0; 
    // base[1] = 1; 
    // base[2] = 2; 
    // base[3] = 3; 
    
    /* 释放内存 */ 
    free(base); 
    exit(0); 
}

aligned_alloc()

aligned_alloc() 函数用于分配size个字节大小的内存空间,返回指向该空间的指针 。此函数是在 ISO C11 中引入的,因此 可能比 posix_memalign() 对现代非 POSIX 系统具有更好的可移植性

函数原型如下:

#include <stdlib.h>

// alignment:用于设置对齐字节大小,alignment必须是2的幂次方(譬如2^4、2^5、2^8等)
// size:设置分配的内存大小,以字节为单位。参数size必须是参数alignment的整数倍
void *aligned_alloc(size_t alignment, size_t size);

返回值 :成功将返回内存空间的指针,内存空间的起始地址是参数alignment的整数倍;失败返回NULL。

使用示例:

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

int main(int argc, char *argv[]) 
{ 
    int *base = NULL; 
    
    /* 申请内存: 256字节对齐 */ 
    base = (int *)aligned_alloc(256, 256 * 4); 
    if (base == NULL) 
    { 
        printf("aligned_alloc error\n"); 
        exit(-1); 
    } 
    
    /* 使用内存 */ 
    // base[0] = 0; 
    // base[1] = 1; 
    // base[2] = 2; 
    // base[3] = 3; 
    
    /* 释放内存 */ 
    free(base); 
    exit(0); 
}

过时:memalign()

memalign() 函数已过时,应改用 aligned_alloc() posix_memalign()

分配一个由 size 指定大小,地址是 alignment 的倍数的内存块。 memalign()aligned_alloc() 参数是一样的,它们之间的区别在于:对于参数 size 必须是参数 alignment 的整数倍这个限制条件, memalign() 并没有这个限制条件。

#include <malloc.h> 

// alignment:用于设置对齐字节大小, alignment 必须是 2 的幂次方(譬如 2^4、 2^5、 2^8 等)
// size: 设置分配的内存大小,以字节为单位。参数 size 必须是参数 alignment 的整数倍
void *memalign(size_t alignment, size_t size); 

使用示例:

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

int main(int argc, char *argv[]) 
{ 
    int *base = NULL; 
    
    /* 申请内存: 256字节对齐 */ 
    base = (int *)memalign(256, 1024); 
    if (base == NULL) 
    {
        printf("memalign error\n"); 
        exit(-1); 
    }
    
    /* 使用内存 */ 
    // base[0] = 0; 
    // base[1] = 1; 
    // base[2] = 2; 
    // base[3] = 3; 
    
    /* 释放内存 */ 
    free(base); 
    exit(0); 
}

过时:valloc()

valloc() 函数已过时,应改用 aligned_alloc() posix_memalign()

分配 size 个字节大小的内存空间,返回指向该内存空间的指针, 内存空间的地址是页大小(pagesize) 的倍数。 使用 valloc 就像使用 memalign 并将页面大小作为第一个参数的值传递。它是这样实现的:

#include <malloc.h> 

// size: 分配size个字节大小的内存空间
void *valloc(size_t size);

返回值 : 成功将返回内存空间的指针。

valloc()memalign() 类似, 只不过 valloc() 函数内部实现中,使用了页大小作为对齐的长度

在程序当中,可以通过系统调用 getpagesize() 来获取内存的页大小。

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

int main(int argc, char *argv[]) 
{ 
    int *base = NULL; 
    
    /* 申请内存: 1024个字节 */ 
    base = (int *)valloc(1024);
    if (base == NULL) 
    { 
        printf("valloc error\n");
        exit(-1); 
    } 
    
    /* 使用内存 */ 
    // base[0] = 0; 
    // base[1] = 1; 
    // base[2] = 2; 
    // base[3] = 3; 
    
    /* 释放内存 */ 
    free(base); 
    exit(0); 
}

过时:pvalloc()

pvalloc valloc 相似,不过将分配的空间大小扩展为页大小的倍数。

#include <malloc.h>
 
// size: 分配size个字节大小的内存空间
void *pvalloc(size_t size);

返回值 : 成功将返回内存空间的指针。

修改块的大小

当使用块时,通常无法确定最终需要多大的块。例如,块可能是一个缓冲区,用于保存从文件中读取的行;无论最初制作缓冲区多长时间,都可能遇到更长的行。 此时就可以通过调用 realloc()reallocarray() 使块更长 。这些函数在 stdlib.h 中声明。

realloc()

#include <stdlib.h>

// 将地址为 ptr 的块的大小更改为 newsize
void * realloc (void *ptr, size t newsize)

由于块末尾之后的空间可能正在使用中, realloc() 可能会发现有必要将块复制到有更多可用空间的新地址realloc() 的返回值是块的新地址。如果需要移动块, realloc() 会复制旧的内容。

如果你为 ptr 传递一个空指针, realloc() 的行为就像 malloc (newsize) 。否则,如果 newsize 为零,realloc 释放块并返回 NULL。否则,如果 realloc 无法重新分配请求的大小,则返回 NULL 并设置 errno;原始块不受干扰。

reallocarray()

#include <stdlib.h>

// 将地址为 ptr 的块的大小更改为足够长以包含 nmemb 元素的向量(vector),每个元素的大小为size。
void * reallocarray (void *ptr, size t nmemb, size t size)

reallocarray 函数等效于 realloc (ptr, nmemb * size) ,但如果乘法溢出, reallocarray 会安全失败,方法是将 errno 设置为 ENOMEM ,返回一个空指针,并保持原始块不变。

当分配块的新大小是可能溢出的乘法结果时,应使用 reallocarray 而不是 realloc

malloc 一样,如果没有可用的内存空间使块变大, realloc reallocarray 可能会返回空指针。 发生这种情况时,原始块保持不变;它没有被修改或搬移

在大多数情况下,当 realloc 失败时,原始块发生的情况并没有什么不同,因为应用程序在内存不足时无法继续,唯一要做的就是给出一个致命的错误消息。编写和使用通常称为 xrealloc xreallocarray 的子例程通常很方便,它们像 xmalloc malloc 所做的那样处理错误消息:

void *xreallocarray (void *ptr, size_t nmemb, size_t size)
{
    void *value = reallocarray (ptr, nmemb, size);
    if (value == 0)
        fatal ("Virtual memory exhausted");
    return value;
}

void *xrealloc (void *ptr, size_t size)
{
    return xreallocarray (ptr, 1, size);
}

您还可以使用 realloc reallocarray 使块更小。这样做的原因是为了避免在只需要一点内存空间时占用大量内存空间。在几种分配实现中,有时需要复制一个块,因此如果没有其他可用空间,它可能会失败。

修改内存分配参数

mallopt()

可以使用 mallopt 函数调整动态内存分配的一些参数。该函数是通用的 SVID/XPG 接口,定义在 malloc.h 中。

#include <malloc.h>

// param: 指定要设置的参数
// value: 要设置的新值
int mallopt (int param, int value)

param 的选择有如下:

参数作用
M_MMAP_MAX使用 mmap 分配的最大块数。
将此设置为零将禁用所有 mmap 的使用。该参数的默认值为 65536。
通过将环境变量 MALLOC_MMAP_MAX_ 设置为所需的值,也可以在启动时为进程设置此参数。
M_MMAP_THRESHOLD使用 mmap 系统调用将所有大于此值的块分配到正常堆之外。 这样可以保证这些块的内存 free 后可以返回给系统。请注意,小于此阈值的请求仍可能通过 mmap 分配。
如果未设置此参数,则默认值设置为 128 KiB,并动态调整阈值以适应程序的分配模式。如果设置了参数,则禁用动态调整,并将值静态设置为输入值。
通过将环境变量 MALLOC_MMAP_THRESHOLD_ 设置为所需的值,也可以在启动时为进程设置此参数。
M_PERTURB如果非零,则在分配内存块(由 calloc 分配时除外)和释放时,根据此参数的某些低位的位填充内存块。 这可用于调试未初始化或已释放堆内存的使用。请注意,此选项不保证释放的块将具有任何特定值。它只保证块被释放之前的内容将被覆盖。
此参数的默认值为 0。
通过将环境变量 MALLOC_PERTURB_ 设置为所需的值,也可以在启动时为进程设置此参数。
M_TOP_PAD此参数确定当需要扩展 arena 时从系统获得的额外内存量。 它还指定缩小 arena 时要保留的字节数。这提供了堆大小的必要滞后,从而可以避免过多的系统调用。
此参数的默认值为 0。
通过将环境变量 MALLOC_TOP_PAD_ 设置为所需的值,也可以在启动时为进程设置此参数。
M_TRIM_THRESHOLD这是最顶层的可释放块的最小大小(以字节为单位),它将触发系统调用以将内存返回给系统。 如果未设置此参数,则默认值设置为 128 KiB,并动态调整阈值以适应程序的分配模式。如果设置了参数,则禁用动态调整,并将值静态设置为提供的输入。
通过将环境变量 MALLOC_TRIM_THRESHOLD_ 设置为所需的值,也可以在启动时为进程设置此参数。
M_ARENA_TEST该参数指定在对 arena 数量限制进行测试之前可以创建的 arena 数量。 如果设置了 M_ARENA_MAX,则忽略该值。
此参数的默认值在 32 位系统上为 2,在 64 位系统上为 8。
通过将环境变量 MALLOC_ARENA_TEST 设置为所需的值,也可以在启动时为进程设置此参数。
M_ARENA_MAX此参数设置要使用的 arena 数量,而与系统中的核心数量无关。
此可调参数的默认值为 0,这意味着对 arenas 数量的限制由在线 CPU 内核数决定。对于 32 位系统,限制是在线内核数的两倍,在 64 位系统上,它是八倍的核心在线数量。请注意,默认值并非源自 M_ARENA_TEST 的默认值,而是独立计算的。
通过将环境变量 MALLOC_ARENA_MAX 设置为所需的值,也可以在启动时为进程设置此参数。

检查内存一致性

mcheck()

可以使用 mcheck 函数要求 malloc 检查动态内存的一致性,并使用 LD_PRELOAD 环境变量预加载 malloc 调试库 libc_malloc_debug 。这个函数是一个 GNU 扩展,在 mcheck.h 中声明。

#include <mcheck.h>

// abortfn: 一致性检查出错时调用的函数
int mcheck (void (*abortfn) (enum mcheck status status))

调用 mcheck 告诉 malloc 执行偶尔的(occasional)一致性检查 。这些将捕获诸如写入超过使用 malloc 分配的块的末尾之类的东西。 abortfn 参数是发现不一致时调用的函数。如果您提供一个空指针,则 mcheck 使用一个默认函数,该函数打印一条消息并调用 abort (请参阅终止程序)。您提供的函数使用一个参数调用,该参数说明检测到哪种不一致;其类型如下所述。

一旦你用 malloc 分配了任何东西,再开始分配检查为时已晚, mcheck 必须在第一个内存分配函数之前调用。 一旦在之后调用, mcheck 在这种情况下什么都不做。如果调用太晚,该函数返回 -1,否则返回 0(成功时)。

安排尽早调用 mcheck 的最简单方法是在链接程序时使用选项 “-lmcheck” ;那么你根本不需要修改你的程序源。 或者,您可以使用调试器在程序启动时插入对 mcheck 的调用,例如,这些 gdb 命令将在程序启动时自动调用 mcheck

(gdb) break main
Breakpoint 1, main (argc=2, argv=0xbffff964) at whatever.c:10
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>call mcheck(0)
>continue
>end
(gdb) ...

mprobe()

#include <mcheck.h>

// pointer: 检查地址指针,必须是 malloc 或 realloc 返回的指针
enum mcheck_status mprobe (void *pointer)

mprobe 函数允许显式检查特定分配块中的不一致。 必须已经在程序开始时调用了 mcheck 来进行偶尔的检查;调用 mprobe 请求在调用时进行额外的一致性检查

返回值enum mcheck_status ,包含以下类型:

枚举类型意义
MCHECK_DISABLED在第一次分配之前没有调用 mcheck 。无法进行一致性检查
MCHECK_OK未检测到不一致
MCHECK_HEAD块之前的数据被立即修改。这通常发生在数组索引或指针减少太多时
MCHECK_TAIL块之后的数据被立即修改。当数组索引或指针增加太多时,通常会发生这种情况
MCHECK_FREE该块已被释放

读取内存使用信息

mallinfo2()

可以 调用 mallinfo2 函数获取有关动态内存分配的信息

#include <malloc.h>

struct mallinfo2 mallinfo2 (void)

数据类型:struct mallinfo2

struct mallinfo2
{
  size_t arena;    /* 从系统分配的未映射空间。malloc 用 sbrk 分配的内存的总大小,以字节为单位。 */
  size_t ordblks;  /* 可用块的数量。内存分配器从操作系统内部获取内存块大小,然后将它们分割以满足各个 malloc 请求 */
  size_t smblks;   /* fastbin块的数量 */
  size_t hblks;    /* 使用 mmap 分配的块的总数 */
  size_t hblkhd;   /* 使用 mmap 分配的内存的总大小,以字节为单位 */
  size_t usmblks;  /* 始终为0,为向后兼容而保留 */
  size_t fsmblks;  /* 释放的fastbin块中的可用空间 */
  size_t uordblks; /* malloc 分配的块所占用的内存总大小 */
  size_t fordblks; /* 空闲(未使用)块占用的内存总大小 */
  size_t keepcost; /* 通常与堆末端接壤的最高可释放块的大小(即虚拟地址空间数据段的高端) */
};

malloc_usable_size()

可以 调用 malloc_usable_size 函数获取用户可用的空间大小

#include <malloc.h>

// __ptr: 检查地址指针
size_t malloc_usable_size (void *__ptr)

返回值 :可用空间大小

释放空间

free()

在堆上分配的内存,需要开发者自己手动释放掉,通常使用 free() 函数释放堆内存, free() 函数原型如下所示:

#include <stdlib.h> 

// ptr:指向需要被释放的堆内存对应的指针
void free(void *ptr);

返回值 :无返回值

使用示例:

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

#define MALLOC_MEM_SIZE (1 * 1024 * 1024)

int main(int argc, char *argv[	])
{
    char *base = NULL;
    
    /*申请堆内存*/
    base = (char *)malloc(MALLOC_MEM_SIZE);
    if (NULL == base){
        printf("malloc error \n");
        exit(-1);
    }
           
    /*初始化申请到的堆内存 */
    memset(base, 0x0, MALLOC_MEM_SIZE);
           
    /*使用内存*/
    /*………………*/
           
    /*释放内存*/
    free(base);
    exit(0);
}

由于 Linux 系统中,当一个进程终止时,内核会自动关闭它没有关闭的所有文件(该进程打开的文件,但是在进程终止时未调用close()关闭它)。同样,对于内存来说,也是如此! 当进程终止时,内核会将其占用的所有内存都返还给操作系统,这包括在堆内存中由malloc()函数所分配的内存空间 。基于内存的这一自动释放机制,很多应用程序通常会省略对free()函数的调用。

这在程序中分配了多块内存的情况下可能会特别有用,因为加入多次对free()的调用不但会消耗品大量的CPU时间,而且可能会使代码趋于复杂。

虽然依靠终止进程来自动释放内存对大多数程序来说是可以接受的,但最好能够在程序中显式调用 free() 释放内存,原因有二:

  1. 显式调用 free() 能使程序具有更好的 可读性和可维护性
  2. 对于很多程序来说, 申请的内存并不是在程序的生命周期中一直需要 ,大多数情况下,都是根据代码需求动态申请、释放的,如果申请的内存对程序来说已经不再需要了,那么就已经把它释放、归还给操作系统, 如果持续占用,将会导致内存泄漏 ,也就是人们常说的“你的程序在吃内存”!

参考文献

1:glibc 知:手册03:虚拟地址分配和分页_xmalloc_canpool的博客-CSDN博客

2:7. Linux的内存分配_linux内存分配_猴子头头123的博客-CSDN博客

3:linux C编程4-系统信息/时间/内存分配/随机数/定时器__sc_pagesize_邻居家的小南瓜的博客-CSDN博客

4:【正点原子】STM32MP1嵌入式Linux C应用编程指南V1.4 - 章节7.6

5:Memory-Allocation——The GNU C Library



如有疑问或错误,欢迎和我私信交流指正。
版权所有,未经授权,请勿转载!
Copyright © 2023.05 by Mr.Idleman. All rights reserved.


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/496884.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20230506在Ubuntu22.04下使用python3下载合并ts切片

20230506在Ubuntu22.04下使用python3下载合并ts切片 2023/5/6 19:42 本文主要是和WIN7/WIN10下的差异比对&#xff01; 一、 Z:\1575\buquan-ts1574.py import requests from multiprocessing import Pool def mission(url,n): headers {"User-Agent":"M…

php语法基础

基础语法 1&#xff0c;php标记符 ①&#xff0c;XML风格 <?php echo "这是标准风格的标记"; ?>②脚本风格 <script language"php"> echo 这是脚本风格的标记; </script>③简短风格 <? echo "这是简短风格的标记"…

62.网页设计规则#8_视觉层次

什么是视觉层次&#xff1f; 视觉层次是关于确定设计中哪些元素是最重要的。视觉层次是为了吸引人们的注意力关注这些最重要的元素。视觉层次是关于为用户定义一个“路径”,引导他们浏览页面我们使用位置、大小、颜色、间距、边框和阴影的组合来建立元素/组件之间有意义的视觉…

leetcode:环形链表(详解)

前言&#xff1a;内容包括-题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读&#xff0c;拓展问题 题目&#xff1a; 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&…

【BIM+GIS】Supermap打开BIM Revit模型的方式

Revit导出Supermap GIS格式数据的方法通常有三种:插件式导出、直接导入和标准交换格式(IFC)导出。 文章目录 一、Revit安装Supermap插件1. 安装Supermap插件2. UDB导出模型3. 打开模型二、Revit导出IFC格式1. Revit导出IFC2. Supermap导入IFC一、Revit安装Supermap插件 1. …

115-Linux_C语言访问mysql及操作数据库

文章目录 一.C语言访问mysql1.连接数据库使用的头文件和库文件2.初始化连接句柄3.连接数据库4.关闭连接5.执行sql语句6.提取结果7.获取结果集中有多少行8.取出结果集中的一行记录9.查看记录行的列数10.释放结果集占用的内存11.获取错误信息 二.连接数据库三.操作数据库 一.C语言…

Linux 中实现 ssh 免密登录

Linux 中实现 ssh 免密登录 1. 使用命令行 在控制端使用命令生成私钥密钥对&#xff0c;执行命令 ssh-keygen -t rsa ,一路默认回车即可&#xff0c;然后会在 .ssh/ 目录下生成两个文件 id_rsa 和 id_rsa.pub&#xff0c;如下图。 使用命令 ssh-copy-id root192.168.16.4&…

电力NLP:指令票规范识别

文章目录 任务目的想法讲解数据集介绍1电气主语2操作任务判断数据集3操作内容判断数据集4错误词数据集 解法讲解程序、数据集下载链接 任务目的 识别调度指令票&#xff08;或者其它操作票&#xff09;是否规范。 想法讲解 按石第2014—16号定值单投入石双西线161开关6区保护…

web三大作用域+servlet生命周期

Web三大作用域 Application ServlectContext &#xff1a; 作用于整个web应用&#xff0c;随程序的停止而失效。 使用&#xff1a; request.getServletContext().setAttribute("参数名","参数值");//servlet获取Application对象并传入数据 Application.g…

React antd 日期选择控件踩坑 <DatePicker> Table Ant Design ProTable

背景 需求&#xff1a;一个带日期的字段 后端接口给值时默认设置为这个日期值 不给值时就是默认状态 <DatePicker defaultValue{val} onChange{handleChange} {...props} />这里 val 是我最终从后端获取到的日期数据 可能有值可能没有值 按照官方 API 和 demo 写 应…

实验四 微程序控制器实验报告

我班算是几乎最后一个做实验的班级了&#xff0c;报告参考了一些朋友提供的数据加上一些自己的主观拙见&#xff0c;本人水平有限加之制作仓促难免有错误&#xff0c;望大家批评指正。 4.1 微程序控制器实验 一、实验目的 (1) 掌握微程序控制器的组成原理。 (2) 掌握微程…

springboot实习管理系统的设计与实现

摘 要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;实习管理也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#xff0c;而实…

一键免费部署你的私人 ChatGPT 网页应用-ChatGPT Next Web

ChatGPT-Next-Web是一款基于GPT-3.5的在线聊天机器人应用程序。它可以自动回复用户输入的消息&#xff0c;并提供有用的信息和服务。该应用程序使用了最先进的自然语言处理技术和GPT-3.5模型&#xff0c;可以生成自然流畅的文本&#xff0c;并提供准确和个性化的回复。 项目地…

(浙大陈越版)数据结构 第二章 线性结构 2.3 队列

目录 2.3.1 队列及顺序存储实现 什么是队列 概念&#xff1a; 特性&#xff1a; 队列的抽象数据类型描述 队列的顺序存储实现 解决方案&#xff1a; 2.3.2 队列的链式存储实现 2.3.1 队列及顺序存储实现 什么是队列 概念&#xff1a; 和堆栈一样&#xff0c;是一种受…

黑马点评项目导入

文章目录 开篇导读项目地址导入SQL项目架构介绍后端项目导入前端项目导入 开篇导读 亲爱的小伙伴们大家好&#xff0c;马上咱们就开始实战篇的内容了&#xff0c;相信通过本章的学习&#xff0c;小伙伴们就能理解各种redis的使用啦&#xff0c;接下来咱们来一起看看实战篇我们…

刚刚!BingChat全面开放,人人可用!

大家好&#xff0c;我是鸟哥。 如题&#xff0c;微软真是下血本。昨天毫无征兆的宣布BingChat全面开放&#xff0c;人人可用&#xff01;众所周知ChatGPT得使用门槛有多高&#xff0c;而BingChat底层调用的是GPT4.0的模型&#xff0c;这无疑是白嫖GPT4.0最简单的姿势了。鸟哥一…

阿里云服务器镜像怎么选?操作系统版本选择说明

阿里云服务器镜像怎么选择&#xff1f;云服务器操作系统镜像分为Linux和Windows两大类&#xff0c;Linux可以选择Alibaba Cloud Linux&#xff0c;Windows可以选择Windows Server 2022数据中心版64位中文版&#xff0c;阿里云百科来详细说下阿里云服务器操作系统有哪些&#xf…

【移动端网页布局】flex 弹性布局 ④ ( 设置子元素是否换行 | flex-wrap 样式说明 | 代码示例 )

文章目录 一、设置子元素是否换行 : flex-wrap 样式说明1、flex-wrap 样式引入2、flex-wrap 样式取值说明 二、代码示例1、代码示例 : 默认情况下 flex 弹性布局子元素不会自动换行2、代码示例 : 自动换行 一、设置子元素是否换行 : flex-wrap 样式说明 1、flex-wrap 样式引入 …

统计字符串字符出现的次数

输入一个字符串&#xff0c;输出字符及相应字符出现的次数。 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… 地址&#xff1a;https://lqp…

navicat连接云服务器mysql教程

包含navicat报错解决&#xff01; 文章目录 1 点击连接后填写相关内容2 报错解决2.1 1130 - Host XXX is not allowed to connect to this MySQL server。2.2 服务器未开启mysql端口通道 1 点击连接后填写相关内容 主机名或ip地址&#xff1a;填写服务器公网ip 用户名和密码&a…