深入理解C指针之二:C内存管理

  内存管理对所有程序来说都很重要。有时候内存由运行时系统隐式的管理,比如为变量自动分配内存。在这种情况下,变量分配在它所处的函数的栈帧上(每个函数都有它自己的栈帧,用来保存它的局部变量和返回地址等)。如果是静态或全局变量,内存处于程序的数据段,会被自动清零。数据段是一个区别于可执行代码和运行时系统管理的其它数据的内存区域。

  C语言也支持动态内存管理,对象就是从堆上分配出来的内存。这是用分配和释放函数手动实现的,这个过程被称为动态内存管理。在C中动态分配内存的基本步骤有:

  1、用malloc类的函数分配内存;

  2、用这些内存支持应用程序;

  3、用free函数释放内存。

int* pi = (int*) malloc (sizeof(int));
*pi = 5;
printf(" pi is : %d\n address : %p\n", *pi, pi);
free(pi);
// pi is : 5
// address : 0x8843008

  malloc函数的参数指定要分配的字节数。如果成功则返回从堆上分配的内存的指针,如果失败则返回空指针。sizeof操作符能确定在宿主系统中应该分配的正确字节数,因为在不同的系统中,数据的字节数可能是不同的。每次调用malloc或类似函数时,结尾都应有对应的free函数调用,以防止内存泄露。

  如果不再使用已分配的内存却没有将其释放就会发生内存泄露,导致内存泄露的情况可能如下:

  1、丢失内存地址;

  2、应该调用free函数却没有调用(隐式泄露);

  以下几个内存分配函数可以用来管理动态内存,大部分包含在stdlib.h头文件中:

  1、malloc:从堆上分配内存

  2、realloc:在之前分配的内存块的基础上,将内存重新分配为更大或更小的部分

  3、calloc:从堆上分配内存并清零

  4、free:将内存块返回堆

  动态内存从堆上分配,对于一连串内存分配调用,系统不保证内存的顺序和所分配内存的连续性。不过,分配的内存会根据指针的数据类型对齐,比如4字节的整数会分配在能被4整除的地址边界上。堆管理器返回的地址是最低字节的地址。

  malloc函数从堆上分配一块内存,分配的字节数由唯一的参数制定,返回值是void指针,如果内存不足,则返回NULL。此函数不会清空或修改内存,所以新分配的内存包含垃圾数据。

void* malloc(size_t);

  执行malloc函数会执行以下操作:

  1、从堆上分配内存;

  2、内存不会被修改或者清空;

  3、返回首字节的地址;

  void指针可以转换成任意指针,但是显示的指针类型转换能获得对c++的更好兼容性,也能使代码更清晰。注意函数参数尽量使用sizeof操作符,而且不要试图接引一个未初始化内存数据的指针变量。

int* mpi = (int*) malloc (sizeof(int));if(mpi!=NULL)
{
//pointer OK
}
else{
//pointer not OK
}

  初始化静态或全局变量时不能调用动态内存分配函数。但是对于静态变量,可以用一个单独的赋值语句来给变量分配内存。

static int* spi;
spi =  (int*) malloc (sizeof(int));

  使用calloc函数会在分配的同时清空内存。清空内存意思是将其内容置为二进制的0。

void* calloc(size_t numElements, size_t elementSize);

  calloc函数根据两个参数的乘积来分配内存,并返回指向内存的第一个字节的指针。如果不能分配内存,则会返回NULL。执行calloc可能比malloc慢,因为它要做额外的内存清理工作。

  如果需要增加或减少为指针分配的内存,可以使用realloc函数

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

  第一个参数是指向原内存快的指针,第二个是请求的大小。返回值是指向重新分配的内存的指针。如果请求的新内存大小比原来的小,多余的内存会返还给堆。如果比原来的大,可能的话,会紧挨着当前分配的内存区域分配新的内存,否则会在堆的其它区域分配并把就的内存复制到新区域。如果请求大小是0,那么就释放内存。如果无法分配,则返回空指针。注意永远不要使用超出内存分配的地址。

  使用free函数将不再使用的内存返还给系统。

void free(void* ptr);

  指针参数应该指向由malloc函数分配的内存,这块内存会被返还给堆。尽管该指针仍然指向该内存,但是该内存现在被认为包含垃圾数据,并且可能被写入新的数据。该指针则称为迷途指针。注意空指针不指向任何东西,迷途指针还是指向一个地址。如果是在一个函数里面分配的内存,那么就应该在同一个函数里面释放它。

  重复释放是指两次释放同一块内存。第二次调用free会引发运行时异常。两个指针指向同一个内存地址称为别名。别名可能导致重复释放。

  堆管理器不一定会在free函数后将内存返回给操作系统,而是可能给程序后续使用。

  迷途指针是指访问已释放的内存的指针。使用迷途指针会导致不可预期的行为和潜在的安全隐患。在指针别名的情况下,迷途指针更难以察觉。

int *gpi;
{
int tmp = 5;
*gpi = &tmp;
}

  大部分编译器把块语句当做一个栈帧。在块语句退出是,分配在栈帧上的tmp会出栈,gpi变成迷途指针,指向一块已经被自动回收的内存。

  除了使用函数手动分配内存外,还有一些非标准技术可以用来实现C的动态内存管理。这些技术的关键特性在于自动释放内存。内存不再使用之后会被收集起来备用,释放的内存成为垃圾,因此这个过程也叫垃圾回收。垃圾回收使程序员不必费心考虑何时释放内存,更专注程序本身的问题。垃圾回收技术包括RAII和异常处理等。

时间: 2024-11-03 21:50:49

深入理解C指针之二:C内存管理的相关文章

SoC软件架构设计之二:内存管理单元的硬件设计实现

程序的大部分代码都可以在必要的时候才加载到内存去执行,运行完后可以被直接丢弃或者被其他代码覆盖.我们PC上同时跑着很多的应用程序,每个应用程序使用的虚拟地址空间几乎可以整个线性地址空间(除了部分留给操作系统或者预留它用),可以认为每个应用程序都独占了整个虚拟地址空间(字长是32的CPU是4G的虚拟地址空间),但我们的物理内存只是1G或者2G.即多个应用程序在同时竞争使用这块物理内存,其必然会导致某个时刻只存在程序的某个片段在执行,也即是所有程序代码和数据分时复用物理内存空间-这就是内存管理单元(

指针和数组及内存管理

1. 指针和地址 TCPL 中给指针的定义是: A pointer is a group of cells (often two or four) that can hold an address . int value = 10; int *pvalue = &value; 上面这个语句的内存模型是: 注意: &(取地址)操作符只对内存中存在的对象起作用,如变量和数组类型.不能对表达式,常量和寄存器变量使用取地址操作. *(析取)操作符应用于一个指针变量时,取得这个指针变量所指向的对象.

cocos2dx 启动过程详解二:内存管理和回调

在上一篇的第二部分中,我们有一句代码待解释的: // Draw the Scene void CCDirector::drawScene(void) { -- //tick before glClear: issue #533 if (! m_bPaused) //暂停 { m_pScheduler->update(m_fDeltaTime);   //待会会解释这里的内容 } -- } 这里是一个update函数,经常会写像this->schedule(schedule_selector(X

深入理解java虚拟机二,内存管理机制

java 虚拟机自动内存管理. java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同区域 1 程序计数器 每个线程都有一个独立的计数器,用来指示需要执行的字节码的位置. 2 虚拟机栈 虚拟机栈是用来描述java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于储存局部变量表,操作栈,动态链接,方法出口等信息. 每一个方法被调用直至执行完成的过程,就对应着一个栈帧从虚拟机栈中从入栈到出栈的过程. 虚拟机栈线程私有,声明周期和线程一样. 局部变量表所需的内存空间在

深入理解JVM(二)自动内存管理机制

2.1 C.C++内存管理是由开发人员管理,而Java则交给了JVM进行自动管理 2.2 JVM运行时数据区:方法区.堆(运行时线程共享),虚拟机栈.本地方法栈.程序计数器(运行时线程隔离,私有) 2.2.1 程序计数器(Program Counter Register):每一个线程都独有一个程序计数器,并且分配了一块线程私有的小块内存,程序运行时,这个计数器会记录字节码文件运行的行数,当线程切换时,则通过这个行数继续执行下面的操作 2.2.2 虚拟机栈(Java Virtual Machine

Block介绍(二)内存管理与其他特性

我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情况来讨论block的存放位置: 1.栈和堆 以下情况中的block位于堆中: void foo() { __block int i = 1024; int j = 1; void (^blk)(void); void (^blkInHeap)(void); blk = ^{ printf("%d,

操作系统原理(二)——内存管理之页面置换算法

页面置换算法 1. 总述 为提高内存利用率,解决内存供不应求的问题,更加合理的使用内存,人们创造了分页式内存抽象.同时有一个虚拟内存的概念,是指将内存中暂时不需要的部分写入硬盘,看上去硬盘扩展了内存的容量,所以叫做“虚拟”内存.使用虚拟内存,应用程序可以使用比实际物理内存更大的内存空间.可以认为这个更大的内存空间就在硬盘上,只有将某一部分需要被用到时,才被写入真实内存:当它暂时不再被用到时,又被写回硬盘.分页式内存管理将物理内存分为等大的小块,每块大小通常为1K.2K.4K等,称为页帧:逻辑内存

iOS开发之oc(十二)--内存管理ARC

(一)自动内存管理 --ARC 1>简单点说就是让编译器完成堆空间的引用计数的加减,自动释放,程序员不再写retain 和release等方法 2>OC的自动内存管理不同于java 的垃圾回收,而是在预处理是直接在应该保留的地方加上retain,在应该改释放的地方加上release,是直接添加代码 3>从效率上讲,ARC优于手动内存管理 2.1.1 ARC 的局限性 1>使用ARC可能因为代码的不规范,导致内存提前释放 2>导入第三方库,或者导入旧代码,这些代码不支持ARC

《Linux内核设计与实现》读书笔记(十二)- 内存管理

内核的内存使用不像用户空间那样随意,内核的内存出现错误时也只有靠自己来解决(用户空间的内存错误可以抛给内核来解决). 所有内核的内存管理必须要简洁而且高效. 主要内容: 内存的管理单元 获取内存的方法 获取高端内存 内核内存的分配方式 总结 1. 内存的管理单元 内存最基本的管理单元是页,同时按照内存地址的大小,大致分为3个区. 1.1 页 页的大小与体系结构有关,在 x86 结构中一般是 4KB或者8KB. 可以通过 getconf 命令来查看系统的page的大小: [[email prote