>> >> >> Reference << << << <<<<<<Ref>>>>>>
Memory Layout
Modified: 2025-12-31 | Author:ljf12825

每个C程序都由几个不同的内存区域组成,它们分别负责不同类型的数据存储

内存布局

代码段(Text Segment)

数据段(Data Segment)

数据段分为两个部分:已初始化数据段和未初始化数据段

例如

int globalVar = 5; // 存储在已初始化数据段
static int staticVar; // 存储在BSS段,默认为0

堆区(Heap)

例如

int *p = (int*)malloc(sizeof(int) * 10); // 在Heap上分配10各int大小的内存
free(p); // 释放内存

栈区(Stack)

例如

void func() {
    int a = 10; // 存储在栈上
}

内存映射区(Memory Mapped Segment)

保留区(Reserved Space)

操作系统可能会为一些特定用途(如系统调用、保留给操作系统使用等)保留一部分内存空间,通常在程序启动时就已经设定好

内存布局图示

高地址
┌─────────────────┐ 0xFFFF...
│   内核空间      │ ← 操作系统内核专用
├─────────────────┤
│   栈顶          │ ← 初始栈指针(ESP/RSP)
│   ↓ 栈增长方向  │
│  ┌─────────────┐│
│  │   栈区      ││ ← 自动管理,存储局部变量
│  │             ││
│  └─────────────┘│
│                 │
│    ...空闲...   │
│                 │
│  ┌─────────────┐│
│  │   堆区      ││ ← 手动管理,动态分配
│  │   ↑增长方向 ││
│  └─────────────┘│
│   堆起始        │ ← brk指针
├─────────────────┤
│   共享库        │ ← 内存映射区(.so文件等)
├─────────────────┤
│    BSS段        │ ← 未初始化全局/静态变量
├─────────────────┤
│   数据段        │ ← 已初始化全局/静态变量
├─────────────────┤
│   代码段        │ ← 程序指令(只读)
└─────────────────┘ 0x0000...
低地址

内存布局调试工具*

使用size命令

size a.out 

使用nm命令查看符号表

nm a.out | sort 

使用objdump查看详细段信息

objdump -h a.out 

内存布局的产生

程序并不是天生就知道如何分配内存的
C语言本身也没有内存管理机制,它只是提供了访问内存的能力(如指针和直接操作内存)。内存管理是通过操作系统和运行时库来实现的
操作系统通过分配和管理内存(例如通过系统调用、堆管理等),使得程序能够以一种抽象的方式使用内存
这种内存布局由三个方面共同决定

操作系统的内存管理

OS在程序启动时负责分配内存给程序。具体来说,操作系统为每个运行的程序创建一个进程,每个进程拥有独立的虚拟地址空间,操作系统会将该虚拟地址空间分为不同的区域,如栈区、堆区、数据段等

编译器的作用

编译器将源代码编译成机器代码时,会对程序的数据、代码进行整理,并为它们指定具体的内存布局。例如

编译器通常不直接处理堆的内存分配,但它会生成调用这些内存管理函数(如malloc)的代码。堆的内存分配是在运行时由程序控制的

程序员的角色

尽管操作系统和编译器负责大部分内存布局,但程序员在编写代码时会决定哪些数据存储在哪里。程序员可以显式指定

在很多情况下,程序员必须手动管理内存(特别是堆内存)。例如,当程序员使用malloc为数据分配内存时,操作系统通过系统调用将内存分配给程序,但程序员必须负责释放这块内存(通过free函数)

操作系统的内存分配机制

操作系统和C运行时使用多种算法管理堆内存

算法原理特点
首次适应使用第一个足够大的空闲块速度快,可能产生碎片
最佳适应使用大小最接近的空闲块减少浪费,增加搜索时间
最差适应使用最大的空闲块减少小碎片
伙伴系统按2的幂次分配块减少外部碎片,可能浪费空间

内存保护机制

内存保护机制是操作系统和硬件共同实现的一种技术,用于保证每个进程只能访问自己被授权访问的内存区域,从而防止非法的内存访问,确保程序和系统的安全性和稳定性

内存保护机制的主要目标

  1. 隔离不同进程的内存空间:防止进程间的内存泄漏或恶意篡改
  2. 防止程序访问不该访问的内存:避免程序因错误访问不属于它的内存区域而引起的崩溃或数据损坏
  3. 提高系统的稳定性:通过有效的内存访问控制,系统能更好地处理内存错误,降低系统崩溃的风险

内存保护的核心技术

虚拟内存

虚拟内存是现代操作系统内存管理的重要组成部分。操作系统将物理内存(RAM)抽象为虚拟地址空间,每个进程都拥有自己的独立虚拟地址空间,这样可以防止不同进程之间互相干扰。虚拟内存的工作原理包括

内存访问权限

操作系统和硬件通过内存保护器(如x86架构的保护模式寄存器)来限制对内存的访问,分配不同的权限给程序的各个部分。常见的内存访问权限包括

这些权限是通过内存保护单元(MPU)或内存管理单元(MMU)来管理的,当程序试图访问没有权限的内存时,操作系统会触发异常,通常导致程序崩溃或被终止

页面保护

在虚拟内存系统中,操作系统通过页表为每个内存页面设置访问权限。例如,某些区域(如堆和栈)可能会有写权限,但无法执行;代码段则会有执行权限,但无法写入。页面保护的常见类型有

栈保护(Stack Protection)

栈保护技术用于防止栈溢出攻击。这些攻击利用程序的栈区溢出覆盖返回地址,从而执行恶意代码。现代操作系统和编译器通过以下方式防止栈溢出

地址空间布局随机化(ASLR)

地址空间布局随机化(ASLR, Address Space Layout Randomization)是操作系统用于增强安全性的一项技术,它通过随机化程序内存的加载位置,使得攻击者无法预测内存地址。ASLR的应用

内存映射保护

内存映射区(例如共享库或动态加载的文件)也需要内存保护。操作系统会为这些区域设置只读或只写的权限,防止程序修改共享库中的代码。这对于防止DLL注入攻击至关重要

内存布局的操作系统实现

不同操作系统(如Linux, Windows等)可能有不同的内存管理策略。例如,Linux使用brkmmap系统调用来分配堆内存,而Windows则通过堆管理器(Heap Manager)管理堆内存

Linux内存管理

在Linux中,内存管理机制较为灵活,支持多种内存分配策略。对于堆内存的管理,主要依赖于两种系统调用:brkmmap

brksbrk系统调用

这两种方法的优势在与它们直接操作进程的数据段,且速度较快。然而,它们有一些限制

mmap系统调用

Windows内存管理

在Windows中,内存管理更加依赖于操作系统提供的堆管理器(Heap Manager),它为每个进程提供了一套完整的堆管理功能。Windows使用堆管理器为进程提供堆的分配和释放

Windows堆管理器

堆分配与释放

内存池机制(Memory Pool)

堆扩展与收缩

特性LinuxWindows
堆内存分配方式brk(传统) + mmap(现代)堆管理器(Heap Manager)
堆扩展brk修改数据段结束位置,mmap动态映射内存动态扩展堆,使用VirtualAlloc()扩展
内存管理机制内存管理通过页表、MMU进行访问控制通过堆管理器管理多个堆,并使用内存池加速分配
堆内存分配函数malloc(), free()HeapAlloc(), HeapFree()
内存池机制使用内存池减少碎片和提高效率
分配与释放效率mmap适用于大块内存,brk适用于小块内存堆管理器优化了内存分配,特别是小块内存
操作系统控制通过系统调用(brk, mmap)管理内存通过堆管理器自动管理内存,提供更高级的控制

实际内存分配实例

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

int global_init = 10;           // data segment
int global_uninit;              // BSS
static int static_var = 20;     // data segment

int main() {
    int local_stack = 30;       // stack
    static int static_local;    // BSS, init 0

    int *heap_ptr = (int*)malloc(sizeof(int) * 100);  // heap

    printf("Text Segment:   main function address = %p\n", main);
    printf("Data Segment:   global_init = %p, %d\n", &global_init, global_init);
    printf("BSS:    global_uninit = %p, %d\n", &global_uninit, global_uninit);
    printf("Stack:     local_stack = %p, %d\n", &local_stack, local_stack);
    printf("Heap:     heap_ptr = %p\n", heap_ptr);

    free(heap_ptr);
    return 0;
}

溢出

内存泄漏

内存对齐(Memory Alignment)

内存管理

动态内存分配