`
javasogo
  • 浏览: 1770261 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

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

阅读更多
相比静态地分配内存空间,使用动态内存分配具有明显的优势:

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

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

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

要理解内存管理, 首先要理解程序在内存中的布局, 既: 内存程序映像.

标准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);
注意: 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.

使用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, 则进行错误处理.
分享到:
评论

相关推荐

    08内存及存储管理(下)

    08内存及存储管理(下) [编辑]romanilu的公告 Name:李&nbsp;勤 Q_Q: &lt;!--QQ--&gt; &lt;a target=blank href=tencent://message/?uin=18879308&Site=http://blog.csdn.net/romanilu/&Menu=yes&gt;&lt;img border="0...

    谭浩强C语言对应C语言课程PPT.zip

    2.C语言程序基本组成(识记): 3.基本数据类型: 3.1 标识符与基本数据类型(识记), 3.2 常量与变量(领会) 3.3 内存的概念(识记) 4.基本输入、输出函数(领会): 5.运算符与表达式(简单应用): 5.1 ...

    计算机二级C语言考试题预测

    算法的空间复杂度是指算法程序中指令(或语句)的条数 C. 算法的有穷性是指算法必须能在执行有限个步骤之后终止 D. 以上三种描述都不对 (2) 以下数据结构中不属于线性数据结构的是(C) A. 队列 B. 线性表 C. 二叉树 ...

    Python(面向对象编程语言) v3.4.0.zip

    当你运行你的程序的时候,连接/ 转载器软件把你的程序从硬盘复制到内存中并且运行。而Python语言写的程序不需要编译成二进制代码。你可以直接从源代码 运行 程序。在计算机内部,Python解释器把源代码转换成称为字节...

    Linux C | Linux标准I/O编程

    程序要获取资源(如内存分配、读写串口)必须通过操作系统来完成,及用户向操作系统发出服务请求,操作系统收到请求后执行相关的代码来处理。 linux系统调用按照功能大致可分为进程控制、进程间通信、文件系统控制...

    mbedtls-2.13 最新

    mbed tls库的设计可以轻松地与现有(嵌入式)应用程序集成,并为安全通讯、密码学和密钥管理提供构建模块.本教程将帮助你了解如何执行这些步骤. mbed tls 被设计成尽可能松散耦合,让你只需要整合你需要的部分,而不需要...

    c++ 面试题 总结

    代码的位置必须在物理内存中才能被运行,由于现在的操作系统中有非常多的程序运行着,内存中不能够完全放下,所以引出了虚拟内存的概念。把哪些不常用的程序片断就放入虚拟内存,当需要用到它的时候在load入主存...

    windows用户称拦截api

    其中0x00000000--0x7fffffff是属于用户层的空间.0x80000000--0xffffffff则属于共享内核方式分区,主要是操作系统的线程调度,内存管理,文件系统支持,网络支持和所有设备驱动程序。对于用户层的进程,这些地址空间...

    键盘上每个键作用!!! (史上最全的)­

    certmgr.msc----证书管理实用程序­ calc-----------启动计算器­ charmap--------启动字符映射表­ cintsetp-------仓颉拼音输入法­ cliconfg-------SQLSERVER客户端网络实用程序­ clipbrd--------剪贴板查看器­...

    BB1407openwrt-RG100A_DB120-squashfs-cfe.bin

    这样就可以自动启动了,但现在还没有运行,可运行命令/etc/init.d/syncy start 来启动程序,也可以通过命令/etc/init.d/syncy stop来停止程序,有管理界面的也可以在启动项里来启动或停止同步程序。 最后,希望...

    NT Locale Emulator Advance (中文名稱: NT全域通)

    Product Name : NT Locale Emulator Advance (NT全域通) short for NTLEA Component Description: ntlea.exe - NTLEA GUI shell and...A:是的,您可以自由转载这个程序,但请务必保持其完整,且不要修改任何内容。

    SweetScape 010 Editor 8 汉化版

    十六进制编辑器是一种允许您查看和编辑二进制文件中个别 字节的程序,而高级的十六进制编辑器(包括 010 Editor)还允许您编辑硬盘驱动器、软盘驱动器、内存密钥、闪存驱动器、光驱和进程中的字节。 SweetScape 010 ...

    springmybatis

    程序代码 程序代码 Create TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userName` varchar(50) DEFAULT NULL, `userAge` int(11) DEFAULT NULL, `userAddress` varchar(200) DEFAULT NULL, ...

    二十三种设计模式【PDF版】

    很简单一个模式,就是在内存中保留原来数据的拷贝. 设计模式之 Interpreter(解释器) 主要用来对语言的分析,应用机会不多. 设计模式之 Visitor(访问者) 访问者在进行访问时,完成一系列实质性操作,而且还可以扩展. ...

    OllyDBG最终完美版

    Labeler.dll (v1.33.108 汉化版) 4、图表插件 OllyFlow.dll (v0.71 汉化版,我从IDA中提取了个wingraph32.exe放在插件目录下,用于配合这个插件) 5、断点管理 olly_bp_man.dll (汉化版,在我机器上不能用,...

    arswp2

    &lt;br&gt;三、系统要求 1、硬件需求 硬 盘:至少拥有 2.5MB 以上的剩余硬盘空间 内 存:64M以上内存,推荐 128M 及以上内存 处理器:中央处理器(CPU)最低配置为 Pentiun 500 以上 &lt;br&gt;2、软件...

    PHP Memcached应用实现代码

    这里简单介绍一下,memcached 是高效、快速的分布式内存对象缓存系统,主要用于加速 WEB 动态应用程序。 二、memcached 安装 首先是下载 memcached 了,目前最新版本是 1.1.12,直接从官方网站即可下载到 memcached-...

Global site tag (gtag.js) - Google Analytics