C语言中内存分配那些事儿

时间:2022-11-30 02:07:11
C程序的内存结构

C语言的之所以复杂,首先它的内存模型功不可没。不像某些那样的高级语言只需要在使用对象的时候,用new创建。所有之后的事情,你不需要操心。对于C语言,所有与内存相关的东西,都需要熟悉,否则,时间一久,总会踩着雷。下图是典型的一个C程序的内存结构,当然还有一个重要的前提,这样的一种布局是在虚拟内存中的:

C语言中内存分配那些事儿

关于虚拟内存内核维护了一个页表(page table),用来表示虚拟内存对物理内存地址或者磁盘(交换区,swap area)间的一种映射关系。并非所有的虚拟地址都需要在物理内存上映射,不然不管多大的内存条的计算机,多开几个进程,内存就消耗没了。当你需要使用内存时再去向操作系统申请,如果请求合法,那么内核为页表添加表项,建立一种虚地址对物理地址间的映射关系。同理,当需要释放时,就解除映射,把资源还回去。

关于虚拟内存的优点《the linux programming interface》给出了比较完善的回答:

Processes are isolated from one another and from the kernel, so that one process can’t read or modify the memory of another process or the kernel. This is accomplished by having the page-table entries for each process point to distinct sets of physical pages in RAM (or in the swap area).

Where appropriate, two or more processes can share memory. The kernel makes this possible by having page-table entries in different processes refer to the same pages of RAM. Memory sharing occurs in two common circumstances: 
– Multiple processes executing the same program can share a single (readonly) copy of the program code. This type of sharing is performed mplicitly when multiple programs execute the same program file (or load the same shared library).
– Processes can use the shmget() and mmap() system calls to explicitly request sharing of memory regions with other processes. This is done or the purpose of interprocess communication.

The implementation of memory protection schemes is facilitated; that is, pagetable entries can be marked to indicate that the contents of the corresponding page are readable, writable, executable, or some combination of these protections. Where multiple processes share pages of RAM, it is possible to specify that each process has different protections on the memory; for example, one process might have read-only access to a page, while another has read-write access.

Programmers, and tools such as the compiler and linker, don’t need to be concerned with the physical layout of the program in RAM.

Because only a part of a program needs to reside in memory, the program loads and runs faster. Furthermore, the memory footprint (i.e., virtual size) of a process can exceed the capacity of RAM.

One final advantage of virtual memory management is that since each process uses less RAM, more processes can simultaneously be held in RAM. This typically leads to better CPU utilization, since it increases the likelihood that, at any moment in time, there is at least one process that the CPU can execute.

本文主要就是为了讨论heap区内存分配的一些细节。

使用系统调用

通常我们称堆的当前边界为“program break”。分配堆区的内存,就是将program break向高地址移动的过程。UNIX系统中有两个系统调用与这个program break关系最密切:

int brk(void *addr);

void *sbrk(intptr_t increment);

sbrk()是通过把program break移动increment的长度实现内存的增加和释放。由于虚拟内存按页分配,所以increment的值并不是实际分配的结果。只要不是页的大小的整数倍,就会多给一个页,不是四舍五入,永远只会多给,除非内存不足。sbrk(0)显示当前的program break。当试图访问program break之外的内存时会产生SIGSEV信号,发生段错误。brk() 函数和sbrk()用法大同小异。

 int main()
{
int* p = sbrk();
*(p+) =;
printf("**\n");
*(p+) =;
}

这样一段代码,向内核申请100字节的内存,实际上映射的是一个内存页,行4访问内存页的最后4个字节并且改写,行6访问映射关系之外的内存显然是非法的,程序的运行结果如下:

$a.out

  **
  Segmentation fault

    用brk()/sbrk()释放内存时,也不定会立即解除映射关系。当program break 下降超过一个页时,才有可能将申请的物理内存返还给内核。当然释放之后所有的对这块内存的操作都是未定义的,与玩火无异。同时program break移动还要注意的一点就是,program break的位置不能移动到heap区之外的地方,比如bss区,数据区等等,这样的行为基本也属于作死的行为之中。

使用C标准库函数

malloc()/free() 绝对是C语言中使用最广泛的函数之一了。相比brk()/sbrk()他接口更加简单,也允许随意释放内存。(brk()/sbrk() 不能随意释放是由于program break往下移动的释放内存的时候,会把顶部“无辜”的元素也释放了。)例如这样的情况(这里内存映射解除了):

C语言中内存分配那些事儿

而free()释放并没有这样的“坑”,因为free释放内存不一定会移动program break。如果要free() 释放的内存上方(高内存地址处)仍然有没有释放的内存,那么program break就不会移动,因此也不会解除映射关系,也就是说这块内存并没有返还给内核。而是作为空闲的内存交给free维护去了,待下次malloc申请时,再返回这块内存(如果够用的话)给malloc返回。那么free又如何知道释放内存的大小的呢?这是由于malloc返回的内存拥有一个比较特殊的结构:

C语言中内存分配那些事儿

在这块内存的前面记录着这块内存的大小。当回收这块内存时,就会记录下他的长度和地址。当再次malloc时就会比较空闲内存列表是否有符合要求的内存,交给程序“二次使用”(或者N次使用)。当然至于用不用空闲内存列表的内存还要取决于具体情况:

1.如果空余的内存比malloc申请的大,那么就切割一部分给malloc返回,剩余的部分再看做是一块空闲的内存,留给下次的malloc使用。

2.如果malloc时没有合适的空闲的内存,那么就会像普通情况那样移动program break,或许申请新的内存(可能上回映射的时候会有富余,就不需要重新映射)。

知道了这些基本的实现之后,我们却发现malloc()、free()是比较危险的函数了,使用申请的内存时一定要小心,特别是边界的情况,否则结果可能是灾难性的。比如这样的一种情况,使用分配的内存后,仅仅越界了1个字节,而这一个字节恰恰记录着另一块内存的长度,当释放这块内存的时候,free维护了错误的长度,而下回有申请内存时把这块内存交给malloc那么一场“灾难”便到来了。

其余的内存分配函数

void *calloc(size_t nmemb, size_t size);

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

calloc()与malloc类似,分配nmemb个大小为size的对象,但是与malloc不同的是:calloc会把分配的内存初始化为0.

realloc() 正如名字那样是“重新分配”的意思,用来调整已经分配内存ptr的大小,如果ptr之后的内存不够就会申请一块新的区域,将原有内存原样复制过去,新增加的内存不作初始化。因此返回的结果可能与ptr不同,实际上不部分时候都是不相同的。因此realloc效率是不够高的。万不得已的时候,建议不要使用。

void *alloca(size_t size);

作用是在上分配内存。manual上是这样描述的:

The alloca() function allocates size bytes of space in the stack frame of the caller. This temporary space is automatically freed when the function that called alloca() returns to its caller.

在栈上分配内存的需要的场景不多,比如setjmp,longjmp执行非局部跳转的时候需要使用分配的内存时,就应该考虑alloca,因为他申请的内存会自动的释放,所以不会出现longjmp“回跳”时候,内存泄露的情况。这样的函数偶尔用一用还是有利于身心健康的。

C语言中内存分配那些事儿的更多相关文章

  1. C语言中内存分配 (转)

    在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要介绍内存管理基本概念,重 ...

  2. 【转】C语言中内存分配

    原文:C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要 ...

  3. C语言中内存分配

     C语言中内存分配   在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要 ...

  4. C语言中内存分配问题:

    推荐: C语言中内存分配 Linux size命令和C程序的存储空间布局 本大神感觉,上面的链接的内容,已经很好的说明了: 总结一下: 对于一个可执行文件,在linux下可以使用 size命令列出目标 ...

  5. (十一)C语言中内存堆和栈的区别

    在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认 ...

  6. C语言中内存对齐规则讨论(struct)

    C语言中内存对齐规则讨论(struct) 对齐: 现代计算机中内存空间都是按着byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地 ...

  7. java中内存分配策略及堆和栈的比较

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...

  8. DLL函数中内存分配及释放的问题

    DLL函数中内存分配及释放的问题 最近一直在写DLL,遇到了一些比较难缠的问题,不过目前基本都解决了.主要是一些内存分配引起问题,既有大家经常遇到的现象也有特殊的 情况,这里总结一下,做为资料. 错误 ...

  9. C++中内存分配、函数调用和返回值问题

    转载博客:http://blog.csdn.net/q_l_s/article/details/52176159(源地址找不到,就贴了这位大神的博客地址,他也是转载的,不过要是学习的话,他的博客很不错 ...

随机推荐

  1. Atitit.提升 升级类库框架后的api代码兼容性设计指南

    Atitit.提升 升级类库框架后的api代码兼容性设计指南 1. 增加api直接增加,版本号在注释上面增加1 2. 废弃api,使用主见@dep1 3. 修改api,1 4. 修改依赖import, ...

  2. js == 判断

    根据阮一峰介绍的算法文章分12部分可判断: 如果x不是正常值(比如抛出一个错误),中断执行. 如果y不是正常值,中断执行. 如果Type(x)与Type(y)相同,执行严格相等运算x === y. 如 ...

  3. NSArray数字和排序

    1.数字 int main(int argc, const char * argv[]) { @autoreleasepool { //添加空白 [NSNull null] // NSArray *a ...

  4. STL之Queue(Q)

    STL的Queue(数据结构中的队列): 特点:FIFO 先进先出: 自适应容器(即容器适配器)   栈适配器STL queue  STL中实现的Queue: 用list来实现queue: queue ...

  5. 爬虫总结_python

    import sqlite3 Python 的一个非常大的优点是很容易写很容易跑起来,缺点就是很多不那么著名的(甚至一些著名的)程序和库都不像 C 和 C++ 那边那样专业.可靠(当然这也有动态类型 ...

  6. html学习笔记二

    html图片标记 <html> <head> <title>图片演示</title> </head> ----------------图片演 ...

  7. 雷林鹏分享:jQuery EasyUI 数据网格 - 扩展编辑器

    jQuery EasyUI 数据网格 - 扩展编辑器 一些常见的编辑器(editor)添加到数据网格(datagrid),以便用户编辑数据. 所有的编辑器(editor)都定义在 $.fn.datag ...

  8. Windows系统制作Ubuntu启动U盘(命令行)

    背景 现今Ubuntu系统的使用越来越多,考虑到日常办公还是用Windows系统,但开发的需求常常要有Linux系统.因此将Linux系统安装到U盘不失为一种好的选择.在Windows系统上制作Ubu ...

  9. List&lt&semi;Map&lt&semi;String&comma; Integer&gt&semi;&gt&semi; 同key的value全部累加合并

    public static void main(String[] args){ List<Map<String,Object>> list1 = new ArrayList&l ...

  10. Kotlin&lpar;2&rpar;&colon; 优雅地扩展类的方法和属性

    欢迎Follow我的GitHub, 关注我的CSDN. 个人博客: http://www.wangchenlong.org/, 最新内容. Kotlin由JetBrains公司推出, 是兼容Java的 ...