Linux C程序内存管理

前言

现代的应用程序基本都运行在独立的虚拟地址空间中,在这段地址空间中,程序可以根据自己的需要为代码指令、运行数据等分配内存资源。为了便于实现,大多数程序都会遵循相对一致的规则对内存进行管理。

C程序的内存布局

在32位的系统中,程序支持最大可寻址4GB大小的内存空间,下图是32位Linux系统中,C程序在被装载运行后,在内存空间中的布局:
在这里插入图片描述
32位的Linux系统默认将高地址的1GB空间分配给内核,剩余的3GB空间分配给用户程序,称为用户地址空间。用户程序使用的内存空间通常包含如下几个部分:

  • 保留未用:从低地址0到代码段起始部分的一段内存空间,一般都被保留未使用,程序也无权限进行访问;
  • 程序代码段:代码段包含提供给CPU执行的指令部分。通常,代码段是只读的,目的就是防止程序运行过程中,发生意外导致指令被篡改。在32位系统上,代码段通常起始于虚拟地址0x8048000;64位系统则一般起始于虚拟地址0x400000
  • 只读数据段:该段通常对应于可执行文件的.rodata段,保存了程序中不可变的数据,如常量、字符串常量等
  • 初始化数据段:已初始化的数据部分,对应可执行文件的.data
  • 未初始化数据段:未初始化的数据部分,对应于可执行文件的.bss段
  • :应用程序通常在堆中进行动态内存分配,比如我们平时使用malloc分配的内存,都是从堆内存中获得。一般堆使用的内存区域都是紧接在数据段之后,并且会在程序分配内存时动态向上扩展
  • :栈用于维护函数调用的上下文。每次执行函数调用时,都会将函数返回地址以及调用者的环境信息保存到堆栈中;并且当前函数还会在栈上为局部变量分配存储空间。栈空间由处理器自动进行维护,用户无需干涉

C变量的存储类别

C语言程序中的变量依据其在程序运行期间的生存周期,可以分为静态存储、动态存储:

  • 静态存储:静态存储的变量,在整个程序的生命周期内都存在,包括全局变量和静态局部变量等。静态存储区中的变量,由编译器在程序编译和链接时进行分配;
  • 动态存储:动态存储的变量,由程序在运行期间,根据需要动态地分配内存进行存储。堆和栈上保存的数据都属于动态分配的方式,区别是栈空间由系统自动分配和释放;堆空间需要程序手动进行分配,并且手动进行释放

考虑如下的C程序,定义了不同存储类别的变量,可以对比查看不同变量的存储性质:

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

int g_init_var = 1; // 全局变量,静态存储方式

int main(int argc, char *argv[])
{
    char *ptr = NULL;
    static int static_var = 1;  // 静态局部变量,静态存储方式
    int x;  // 栈上局部变量

    ptr = (char*)malloc(sizeof(int));   // 堆上内存分配
    if (ptr != NULL) {
        free(ptr);
    }

    return 0;
}

栈,又称堆栈,是现代计算机程序极为重要的组成部分,几乎所有的函数调用都依赖于栈进行实现。栈维护了一个函数调用过程中所需要的信息,被称为栈帧。栈帧中一般维护了以下几类重要的数据:

  • 函数返回地址:函数返回地址记录了函数执行完成后,退出函数调用时需要执行的下一条指令;
  • 函数参数:在调用子函数时,程序将函数参数依序保存在栈上,子函数使用帧指针+偏移的方式访问这些参数;
  • 保存的寄存器:保存了函数调用前后需要保持不变的寄存器;
  • 局部变量:函数调用过程中使用的非静态局部变量均默认保存在栈上,函数退出后,这些变量也会被释放而不可使用

如下图,是一个函数调用过程中,栈帧所保存的信息:
在这里插入图片描述

在程序运行过程中,每次执行函数调用,都会建立这样的一个栈帧,连续的过程调用会让栈不断朝着“栈顶”的方向进行扩展;在函数调用完成后,栈帧会被自动释放,栈也会随之缩减。在大多数体系结构上,栈的扩展方向都是由高地址向低地址进行扩展。

堆内存

堆用于存放程序运行时动态分配的内存区域,可动态扩展或缩减。在进程的地址空间中,堆往往占据着很大一部分,程序可以从中申请很大的连续内存进行使用。与栈空间会随着函数调用自动分配和释放相比,堆内存需要程序手动进行管理。C运行库提供了mallc和free两个接口用于C程序对堆内存的管理。当程序调用malloc时,malloc会从系统分配指定大小的内存添加到堆上;当调用free时,free将指定的内存从堆上移除。需要注意的是,堆内存一旦申请,如果程序忘记释放,那么在整个程序的运行期间,这段内存都会存在,从而导致内存泄漏。C程序内存分配示意如下:
在这里插入图片描述

替代的堆内存分配程序

默认情况下,程序都是使用glibc中提供的malloc/free接口来分配内存,但很多系统已经提供了替代默认堆分配接口的库,有兴趣的可以去了解一下。这里给出两个在多处理器环境下,使用多线程的应用程序中具有良好性能表现的两个内存分配库:

  • TCMalloc:TCMalloc库由Google-perftools工具集提供,并且是完全开源的
  • jemalloc:jemalloc库是FreeBSD 8.0中的默认存储空间分配程序

相关参考

  • 《程序员的自我修养—链接、装载与库》
  • 《Unix环境高级编程》
posted @ 2020-03-22 18:07  Aspiresky  阅读(47)  评论(0)    收藏  举报