C程序中的内存管理

3D Slicer中文教程(三)—数据加载及保存方式 1.打开数据与保存数据 (1)打开数据 ——可以将数据拖拽到3D Slicer应用窗口或者从菜单栏工具栏打开。 ——多种方式加载大量数据。 有关DICOM数据,请参阅DICOM模块文档。 对于几乎所有其他(包括场景),您可以使用添加数据对话框。 有关支持的数据格式的列表,请参阅此列表。 请注意,仅部分支持列出的某些格式。 ——对于一些更复杂的格式(特别是DWI),建议首先使用DICOM到n... 阅读详情
 相比静态地分配内存空间,使用动态内存分配具有明显的优势:

1, 分配空间的大小够精确: 设想一个读取用户输入行的程序, 如果使用静态分配的数组作为buffer, 那么, 你如何确定该数组的长度呢? 太大或太小都不合适. 因为你无法事先知道用户输入字符串的长度. 而使用动态内存分配就精准多了.

2, 静态分配的空间大小无法更改, 而动态分配的内存大小是可调的.

所以, 理解C语言中的动态内存分配对于编写实用, 有效, 安全的程序来说必不可少. 本文假设你使用C语言编程, 且使用GNU/Linux系统. (其实由于现在的许多系统都是POSIX兼容的, 本文的内容使用于任何操作系统, 只是其中提到的某些工具仅存于GNU/Linux上.)

要理解内存管理, 首先要理解程序在内存中的布局, 既: 内存程序映像. 可参考本blog的 GNU/Linux平台的C程序开发及程序运行环境

标准C中的内存管理函数

函数原型如下:

#include <stdlib.h>

void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
若返回的指针=NULL, 则失败, 否则成功.

void free(void *ptr);
                                                                                  ISO C


上表列出了标准C规定的四个常用内存管理函数, 一般而言, 这4个函数已经足够我们进行内存管理了.

malloc(), calloc(), realloc()返回的指针若不为NULL, 那么该指针指向被分配数据块中的第一个元素. 并且, 保证该指针能针对各种数据类型满足对齐要求.

void *malloc(size_t size);

调用malloc()的步骤
1, 针对你想要存放数据的数据结构, 声明一个指向它的指针.
2, 计算你需要分配的字节数. 通常利用sizeof(数据结构) * n, n为你要使用的数据结构的个数.
3, 调用malloc()分配内存, 并将它所返回的通用指针(void *)显式地映射为指向该数据结构的指针, 并检查malloc()是否返回了NULL, 若返回值为NULL, 则进行错误处理.

实现代码
struct num {
    int x, y, z;
};
struct num *p;
int n;

if ((p = (struct num *)malloc(n * sizeof(struct num))) == NULL) {
    错误处理;
}
使用p指向的内存; (记得初始化!)

标准C中规定, void *p是一个通用指针, 可以将它赋予任何类型的数据. 但在这里最好还是显式地将它的类型映射为需要分配的数据类型. why? 先看看下面替代上面使用malloc()函数的语句:
if ((p = malloc(n * sizeof(*p))) != NULL)
这里将sizeof的参数换成了*P, 这样, 即便p被修改, 指向了不同的数据结构, sizeof也能计算正确的字节数. 这里省略了类型映射, 但加上它以后, 能够在p指向不同的数据结构后, 编译时给出警告信息.
总之, 我们使用这样的语句来调用malloc():
if ((p = (ds *)malloc(n * sizeof(*p))) == NULL)
其中(ds *)将通用指针显式地映射到要分配的数据类型.

另外, 传统c中使用char *作为通用指针, C++要求对malloc()的返回值进行显式的映射.

void *realloc(void *ptr, size_t newsize);

使用realloc()可以调整之前分配的数据块大小. 包括增加或则减小. 一般而言不用减小已分配数据块的大小.

关于realloc()的原型, 有几点值得注意:
1, newsize是调整数据块大小后最终的值, 并非差量.
2, 若ptr != NULL && nesize == 0, 则 realloc(p, 0)等价于free(p).
3, ptr指向需要调整的数据块, 若ptr == NULL, relalloc(NULL, newsize)等价于malloc(newsize).

虽然可以用realloc()实现free()和malloc()的功能, 但不推荐这样做. 还是使用标准的接口比较合适.

调用realloc()的步骤:
1, 计算你新需要的字节数.
2, 找到指向你需要调整的数据块的指针(它是malloc()或calloc()甚至realloc()的返回值.), 并将它和新的字节大小传递给realloc(). 注意不要用增量!
3, 调用realloc()分配内存, 并将它所返回的通用指针(void *)显式地映射为指向该数据结构的指针, 并检查malloc()是否返回了NULL, 若返回值为NULL, 则进行错误处理.

注意: GNU Coding Standards规定: 即便realloc()失败, 之前分配的数据块也会保持不变, 可以继续使用.

实现代码
这里继续上述malloc()中的代码, 假设之前分配的n个num结构不够, 还需要再分配m个:
struct num *q;
int newsize = (n+m) * sizeof(*p);

if ((q = (struct num *)realloc(p, newsize)) == NULL) {
    错误处理;
}
p = q;
继续使用p;

注意: realloc()返回的地址赋给了一个新的指针q. 在调用完realloc()之后, 又将q的值赋给p , 继续使用p. 为何如此麻烦呢?  原因有二:

(1)看看上面的框框, GNU保证即便realloc()返回NULL, 之前调用malloc()分配给p的数据块也能使用, 但若直接把realloc()的返回值赋给p, 可能令p = NULL, 使得之前p指向的数据段无法使用.

(2)使用realloc()时, 脑子里应该时刻铭记一点: 由于对之前的数据块大小进行了调整, realloc()可能将以前的数据块挪到内存中别的位置. 考虑增大数据块的情况: 若之前分配的数据块所在的内存空间所剩的空间不够, 那么realloc()会将以前的数据块拷贝到内存中其他位置, 并释放之前分配的数据块. 这样之前的p就指向了无效的区域. 即便调用realloc()来减小数据块, 该数据块也可能被移到内存中的其他位置!

某个已分配的数据块b1, 调用realloc()调整b1大小得到b2之后, 不能假设b1和b2的第一个元素在同一位置. 指向b1的所有指针必须被更新!
引用原b1数据块中的元素有两种途径:
1, 使用数组下标.
2, 使用被更新后的指针. 绝不能使用以前指向p1的指针!

void *calloc(size_t nobj, size_t size);

calloc()可视为malloc()的一个封装, 下面是它可能的一个实现:
void *calloc(size_t nobj, size_t size)
{
    void *p;
    size_t total;

    total = nobj * size;
    if ((p = malloc(total)) != NULL) {
       memset(p, '/0', total);

    return p;
}

调用calloc()的方法与malloc()相同. calloc()与malloc()的区别在于两点:
1, calloc()将分配的内存数据块中的内容初始化为0. 这里的0指的是bitwise, 既每个位被清0, 具体的数值由要联系数据结构中各元素的类型.

2, 传递给calloc()的参数有2个, 第一个是想要分配的数据结构的个数, 第二个是数据结构的大小.

如果传递给malloc()或calloc()的size = 0, 标准C并未规定返回的指针一定为NULL, 它可能为非NULL. 但是这种情况下不能引用该指针.

void free(void *ptr);

在完成对动态分配的数据块的使用之后, 要
通过调用free()来 释放它. 这里所说的"释放"是指将该数据块占用的内存放回到堆中, 以后再调用malloc(), calloc()或realloc()时可以利用该数据块占用的内存段. 注意free()并不能够改变进程地址空间的大小, 被释放的内存仍位于堆空间中.

如果不及时释放内存, 会引发内存泄露(memory leaks), 特别是运行时间比较长的程序要注意这个问题, 如果发生了内存泄露, 系统即便不因为缺少内存资源而崩溃, 也会由于内存抖动(memory thrashing)而性能下降.

调用free()的方法:
free(p);
p = NULL;

调用free()时, 有几点注意:
1, p必须指向由malloc(), calloc()或realloc()返回的地址. 即传递给free()的参数必须是数据块第一个元素的地址.  因为malloc()的实现往往在分配的数据块的首部存储一些用来管理分配的数据块的记账信息. 如果不将数据块首部地址传递给free(), free()无法知道数据块的具体信息, 也就无法释放. 把NULL传递给free()是合法的, free()不起任何作用.

NULL == ((void *)0), 在现代系统上, 地址0不在进程地址空间之内, 引用0地址会引发段错误.

2, 谨防"dangling pointer(野指针)", 当p指向的数据块被释放后, p就成为了一个野指针. 如果再次通过p引用数据就存在问题了. p可能指向了内存中别的位置.( 如果在p被释放之后没有调用内存分配函数, p可能还指向原来的数据块, 但该情况不确定). 所以, 在调用free(p);之后, 要紧接着将p设为NULL. 这样如果引用p, 就会马上引起段错误. 不会干扰程序的其他地方.

3, 一个数据块只能被释放一次, 如果对同一数据块释放多次, 会引发问题. (多次调用free(NULL)不存在任何问题.)

4, 被释放的内存依然位于进程地址空间, 用于以后调用malloc(), calloc(), realloc()返回的数据块.


在栈上分配内存: alloca()

前面的malloc(), calloc(), realloc()都在堆上分配内存, 需要显式地释放所分配的内存. 如果使用alloca()在栈上分配内存, 由于每次函数返回时都会释放它所在的栈空间,  alloca()所分配的内存会像动态变量一样被自动释放.

alloca()的原型:

#include <alloca.h>
void *alloca(size_t size);

不推荐使用alloca(), 因为它不属于ISO C或POSIX标准, 依赖于具体的系统和编译器, 即便在支持它的系统上, 它的实现也有bug.


brk()和sbrk()系统调用

在UNIX系统中, malloc(), calloc(), realloc(), free()这4个函数都是在brk()和sbrk()这两个系统函数基础上实现的.  在应用程序中, 极少见到这两个函数, 这里对它们做一个简单介绍, 并利用它们来查看进程地址空间信息.

brk(), sbrk()的原型:

#include <unistd.h>
int brk(void *end_data_segment);
void *sbrk(intptr_t increment);

brk()j将进程地址空间的data段尾(既内存程序映像的堆尾)设置为end_data_segment所指向的位置. 若成功, 返回0, 否则返回-1.

sbrk()使用差量来调整进程地址空间data段尾的位置, 并返回之前data段尾的地址.

下面看看这样一个程序, 它显示进程地址空间的相关信息:

     1    /*
     2     * Show address of code, data and stack sections,
     3     * as well as BSS and dynamic memory.
     4     */
     5   
     6    #include <stdio.h>
     7    #include <malloc.h>        /* for definition of ptrdiff_t on GLIBC */
     8    #include <unistd.h>
     9    #include <alloca.h>        /* for demonstration only */
    10   
    11    extern void afunc(void);    /* a function for showing stack growth */
    12   
    13    int bss_var;            /* auto init to 0, should be in BSS */
    14    int data_var = 42;        /* init to nonzero, should be in data */
    15   
    16    int
    17    main(int argc, char **argv)   
    18    {
    19        char *p, *b, *nb;
    20        int i;
    21        printf("Text Locations:/n");
    22        printf("/tAddress of main: %p/n", (void *)main);
    23        printf("/tAddress of afunc: %p/n", afunc);
    24   
    25        printf("Stack Locations:/n");
    26        afunc();
    27   
    28        p = (char *) alloca(32);
    29        if (p != NULL) {
    30            printf("/tStart of alloca()'ed array: %p/n", p);
    31            printf("/tEnd of alloca()'ed array: %p/n", p + 31);
    32        }
    33   
    34        printf("Data Locations:/n");
    35        printf("/tAddress of data_var: %p/n", & data_var);
    36   
    37        printf("BSS Locations:/n");
    38        printf("/tAddress of bss_var: %p/n", & bss_var);
    39   
    40        nb = sbrk((ptrdiff_t) 0);
    41        printf("Heap Locations:/n");
    42        printf("/tInitial end of heap: %p/n", nb);
    43        b = sbrk((ptrdiff_t) (32));    /* lower heap address */
    44        printf("/t  sbrk return : %p/n", b);
    45       
    46   
    47        nb = sbrk((ptrdiff_t) 0);
    48        printf("/tNew end of heap: %p/n", nb);
    49   
    50        b = sbrk((ptrdiff_t) -16);    /* shrink it */
    51        nb = sbrk((ptrdiff_t) 0);
    52        printf("/tFinal end of heap: %p/n", nb);
    53       
    54        printf("Command-Line Arguments:/n");
    55        for (i = 0; argv[i] != NULL; i++)
    56          printf("/tAddress of arg%d(%s) is %p/n", i, argv[i], &(argv[i]));
    57   
    58        return 0;
    59    }
    60   
    61    void
    62    afunc(void)
    63    {
    64        static int level = 0;        /* recursion level */
    65        auto int stack_var;        /* automatic variable, on stack */
    66   
    67        if (++level == 3)        /* avoid infinite recursion */
    68            return;
    69   
    70        printf("/tStack level %d: address of stack_var: %p/n",
    71                level, & stack_var);
    72        afunc();            /* recursive call */
    73    }

在Linux, x86系统中, 代码段开始于 0x08048000; 栈底地址开始于0xc0000000.
万字长文带你由浅入深夯实ARM汇编基础——汇编指令及寻址方式最全梳理(附示例)! 见到汇编就害怕?读完这篇,扫除汇编拦路虎!!(示例丰富,可做手册,随手查阅!) 阅读详情

相关推荐

ST7102驱动移植与调试实战

本文详细介绍了在君正T40平台上移植和调试ST7102 LCD驱动芯片的实战经验。内容涵盖硬件接口配置、时序参数调整、寄存器初始化序列编写及常见问题解决,重点解析了ST7102单芯片集成显示与触控功能的特性,为嵌入式设备显示驱动开发提供实用指南。

k6l7m8n9的博客 1406

数据结构(三)——单链表的插入与删除

单链表的插入: 假设存储元素e的结点为s,要实现p、p->next和s直间逻辑关系的变化,只需要将s插入到p和p->next之间即可。也就是说,让p的后继结点称为s的后继结点,s变成p的后继结点,如下图: 这个操作的代码只需要两行: s->next = p->next; p->next = s; 注意:这两行代码的顺序不能交换,否则s->next就等于s,插入也就失败了

qq_34528825的博客 1353

一步一步搭建S32K14x的AutoSar项目----S32DS 代码工程的搭建

一步一步搭建S32K14X的AutoSar项目----S32DS代码工程的搭建

wo580231的博客 3487

内存抖动

内存抖动(memory thrashing) 现代操作系统都有一个叫虚拟内存的概念。操作系统如果只使用物理内存作为可用内存的话,会很受限制。于是就提出一种以廉价硬盘代替昂贵内存的方法,“扩充”可用内存。    于是,就在硬盘上划出一部分硬盘空间用来暂时存放内存数据。当系统进程发现物理内存不够了,就在内存空间上找一些不活跃的进程,把它占用的内存复制到硬 盘上,空出来的内存就可以重新使用了

cainiao366的专栏 2067

捷联惯导总结--初始对准,位置标定,INS姿态更新,GPS/INS组合

惯导及组合导航回顾  2018.09.16 今天和17系的同学一起把惯导的流程捋了一遍,为了加深自己的记忆,这里在前面把心得大致列出来。 我们这里只考虑捷联式惯导及松组合 首先拿到惯性传感器(加速度计和陀螺仪)需要对其进行标定,是为了求出零偏,比例因子,交轴耦合这些误差。这些误差有的虽然在出厂时会给出,但是考虑到实际环境不同,还是要做标定。一般采用的是六位置旋转法,针对加速度计和陀螺仪同时...

赤豆几维 5万+

T-Box功能详解及应用

T - BOX,其全称为 Telematics Box,作为远程 / 车载通信模块,是一款集车身网络与无线通讯功能于一体的智能终端设备。它充当了多媒体车机与 TSP(汽车远程服务提供商)以及互联网之间的无线路由器角色,为用户创造了远程访问的可能。用户借助T - BOX能够对车辆配备的各个 ECU(电子控制单元)进行远程操控,包括远程启动或停止发动机开启或关闭空调天窗后备箱门车门,以及实现闪灯鸣笛等功能。

wgl110110的博客 5640

free()函数相关

//C语言基础知识复习 free()函数包含在头文件 stdlib.h中 malloc、calloc、realloc均包含在这个头文件中 (后两个函数用法与作用已经忘记了) 问:若释放指针p的下一个结点的内存空间,应该怎样操作? ①q=p->next; free(q); ②free(p->next);// 这种方法也行——青岛大学王卓老师数据结构04周02讲 ...

A_Alx1459的博客 654

【前端圭臬】二:你知道的和不知道的 HTML

关于 HTML,说几个你可能不太关注的问题。

Evan 的博客 4757

free(q)和delete q;

3、new建立的是一个对象,而malloc分配的是一块内存;new建立的对象可以用成员函数访问,不要直接访问它的地址空间;malloc分配的是一块内存区域,用指针访问,可以在里面移动指针;new出来的指针是带有类型信息的,而malloc返回的是void指针。2、new做两件事,一是分配内存,二是调用类的构造函数;4、new/delete是保留字,不需要头文件支持;1、new/delete是C++的操作符,而malloc/free是C中的函数。而malloc和free只是分配和释放内存。...

程序员,他们想的是什么?他们想的永远都是技术,他们崇尚的也永远都是技术。 213

利用 IP 中的集成调试功能来调试 PCI Express 链接训练问题

JTAG Debugger”和“In-system IBERT”功能结合在一起即可提供即时信息,用于判断链接训练问题的可能原因。“test_rd.tcl”文件可读取 BRAM 中存储的调试数据,并输出 *.dat 文件,如下所示。同样,您可能看到某一箭头旁的数字较大,表明可能存在不稳定的链接。要采集 PCIe 链接眼图,请在 IP 配置 GUI 的“Add. Debug Options”选项卡中选择“启用 In-System IBERT (Enable In-System IBERT)”选项。

qq_43416206的博客 533

企业级大模型落地:私有化部署的安全合规全流程实战指南

摘要:本文针对企业在大模型私有化部署中面临的数据泄露、合规风险及系统稳定性难题,提出一套覆盖数据安全、模型合规、审计追踪与灾备方案的全流程解决方案。通过双加密管道(国密SM4传输加密+AES-GCM存储加密)、SGX可信执行环境等技术,实现数据全生命周期保护;结合等保2.0三级要求与《生成式AI服务管理暂行办法》,提供合规性实施细则;并给出本地GPU集群、混合云、边缘推理盒子三种企业级部署方案及成本模型。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 904

MemoryThrashing:抖音直播解决内存抖动实践

动手点关注干货不迷路????背景介绍直播 OOM 问题比较棘手难以定位,主要体现在涉及的业务很多,从定位到解决花费时间比较久。为了提前触达问题,提高定位的效率,也是对现有工具的补充,提出直播内存抖动解决方案- MemoryThrashing。为什么要提出这个方案 ?现有的 “MemoryGraph” 工具可以通过抓取的“MemoryGraph” 文件分析 OOM 成因,比如内...

字节跳动技术团队官方博客 2449

为什么我的OpenClaw安装后无法启动?Gateway服务故障排查全攻略

OpenClaw是一款功能强大的自动化工具,但其安装和运行依赖于多个服务组件,其中Gateway服务是核心组件之一。如果Gateway服务无法启动,整个OpenClaw系统将无法正常运行。本文将详细介绍OpenClaw安装后无法启动的常见原因及故障排查方法,帮助你快速定位并解决问题。

一键难忘的博客 4622

内存管理模拟程序c语言,模拟实现C语言中的内存管理

这里模拟了C语言中的内存管理,当我们要创建或者使用一个对象时,那么这个对象会调用retain方法,计数+1,当我们要释放对象,我们会调用free,这里注意要对计数记性判断,如果是0的话,那么就会销毁。#import int cnt = 0;void fun (charchar * p){printf("%c\n",p[0]);}charchar * retain1(charchar * p){//...

weixin_31100517的博客 629

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

[转载]C程序中的内存管理

相比静态地分配内存空间,使用动态内存分配具有明显的优势: 1, 分配空间的大小够精确: 设想一个读取用户输入行的程序, 如果使用静态分配的数组作为buffer, 那么, 你如何确定该数组的长度呢? 太大或太小都不合适. 因为你无法事先知道用户输入字符串的长度. 而使用动态内存分配就精准多了. 2, 静态分配的空间大小无法更改, 而动态分配的内存大小是可调的. 所以, 理解C语言中的动态内存分...

javasogo 87

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

C语言中的内存管理技巧:提高程序效率和减少内存泄漏

内存管理是C语言编程中重要的技巧,通过合理使用动态内存分配、内存复用和内存泄漏检测等方法,可以提高程序的效率和健壮性。在进行内存管理时,需要注意资源释放、内存对齐和防御性编程等技巧。通过不断的实践和研究,可以提高对内存管理的理解和应用能力,编写更高效和可靠的程序。

极客代码 1278

为什么你的C程序总是崩溃?深度解读内存管理中的致命陷阱与应对方案

掌握C语言内存管理常见陷阱及解决方案,有效避免程序崩溃。针对缓冲区溢出、野指针、内存泄漏等问题,解析malloc与free使用要点,提升程序稳定性与安全性。嵌入式开发、系统编程必备技能,值得收藏。

LiteCompile的博客 819

《C程序设计中的指针、内存管理与高效算法实战》

无论以何种方式分配的内存,在使用完毕后都必须通过free函数进行释放,并将指针置为NULL,以避免产生野指针。内存泄漏是常见的问题之一,指分配的内存未被释放,长期运行的程序必须严格防范。` 声明了一个指向整型数据的指针。指针的类型决定了指针进行算术运算时的步长,例如,一个int型指针加1,其值实际会增加`sizeof(int)`个字节。深刻理解指针与数组的紧密关系是进行高效编程的基础,数组名在多数情况下可以视为指向数组首元素的常量指针。通过指针,可以避免不必要的数据复制,直接操作内存,从而提升性能。

wkq609385552的专栏 300

黑马程序员--Objective-C之--OC中的内存管理

------IOS培训期待与您交流! ------- 对于面向对象的变成语言,程序需要不断地创建对象。 初始,创建的所有程序通常都有指针指向它,程序可能需要访问这些对象的实例变量或调用这些对象的方法,随着程序的不断执行,程序再次创建了一些新的对象, 而那些老的对象已经不会再被调用,也不再有指针指向他们,如果程序没有回收他们占用的内存,就会出现内存泄露。 如果程序一直泄露内存,那么...

aishazha9159的博客 186
上一篇: u-boot
下一篇: ARM嵌入式汽车节能控制系统的设计
Linux先生
博客等级 码龄20年 97粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值