当前位置: 首页 > news >正文

操作系统——堆与栈详解:内存结构全面科普

文章目录

  • 堆与栈详解:内存结构全面科普
    • 一、程序内存结构总览
    • 二、各段介绍及特点
      • 1. 代码段 `.text`
      • 2. 数据段 `.data`
      • 3. BSS段 `.bss`
      • 4. 堆区 `Heap`
      • 5. 栈区 `Stack`
    • 三、C语言实例分析
    • 四、深入理解:为什么堆空间可能不连续?
      • 1. 堆内部结构:链表或树管理内存块
      • 2. 如何减少堆碎片?
      • 栈的内存空间是连续的吗?
        • ✅ 在**虚拟内存**(也叫逻辑地址)上,栈是**连续**的
        • ❌ 在**物理内存**上,栈可能是**不连续**的
        • 举个简单例子
        • 总结一句话(面试也能用上)
        • 拓展:堆和栈的对比(便于理解)
    • 五、用户栈与内核栈
      • 为什么不能共用?
    • 六、线程与栈:每个线程都有自己的栈
    • 七、常见问题汇总与图解
      • 1. 返回局部变量地址错在哪里?
      • 2. malloc 分配的内存一定连续吗?
    • 八、总结对比表
    • 九、思维导图助记
    • 十、小结

堆与栈详解:内存结构全面科普

一、程序内存结构总览

一个程序在运行时,通常会被划分为以下几大内存区域:

+-----------------------+
|  栈区(Stack)        | ← 高地址
+-----------------------+
|  堆区(Heap)         |
+-----------------------+
|  BSS段(.bss)        |
+-----------------------+
|  数据段(.data)      |
+-----------------------+
|  代码段(.text)      | ← 低地址
+-----------------------+

二、各段介绍及特点

1. 代码段 .text

  • 作用:存放程序的机器指令(即编译后的函数代码)。
  • 特点:只读;有些系统允许写,但一般不建议修改;可被多个进程共享。
  • 内容:函数定义、字符串常量。
const char *str = "hello"; // "hello" 就存在代码段中

2. 数据段 .data

  • 作用:存放已初始化的全局变量或静态变量
  • 特点:程序运行前即分配,生命周期随程序存在。
  • 内容:如 int a = 100; (全局变量)全局变量才算是程序的数据,
    局部变量不算程序的数据,只能算是函数的数据
int a = 10; // 全局变量,属于数据段
static int b = 5; // 静态变量,已初始化,也属于数据段

3. BSS段 .bss

  • 作用:存放未初始化的全局变量和静态变量(或初始化为0的)。
  • 特点:也属于静态分配,初始值默认为0,不占用可执行文件空间。
  • 内容int b;(全局变量未赋值)
static int x; // BSS段
char *p1;     // 如果是全局变量且未初始化,也属于BSS段

4. 堆区 Heap

  • 作用:用于动态内存分配,大小可变。
  • 特点:由程序员控制(malloc/new 分配,free/delete 释放)。
  • 生命周期:手动控制;忘记释放会造成内存泄漏
  • 碎片问题:频繁分配/释放会造成"堆碎片"。
int *p = (int *)malloc(sizeof(int)); // p 指向的内存属于堆
delete p; // 释放堆空间

5. 栈区 Stack

  • 作用:存储函数调用产生的局部变量、函数参数、返回地址等。
  • 特点先进后出;自动分配和释放;空间有限。
  • 生命周期:函数调用开始创建,调用结束销毁。
  • 注意:不能返回局部变量地址,地址将失效!
void func() {int x = 10; // 局部变量,属于栈char buf[20]; // 数组也是分配在栈上
}

三、C语言实例分析

#include <stdio.h>
#include <string.h>int a = 0;             // 数据段
char *p1;              // BSS段(全局未初始化)int main() {int b;             // 栈char s[] = "abc";  // s在栈,"abc"在常量区(代码段)char *p2;          // 栈char *p3 = "123456"; // "123456"在代码段常量区,p3在栈static int c = 0;  // BSS段(静态未初始化)p1 = (char *)malloc(10); // 堆strcpy(p1, "123456");    // 字符串常量放在代码段return 0;
}

四、深入理解:为什么堆空间可能不连续?

1. 堆内部结构:链表或树管理内存块

  • 堆由操作系统管理一组内存块(free list)。
  • 每次调用 malloc/new 都会在空闲块中找合适的。
  • 多次申请/释放会形成碎片:
堆内存示意图:
[已用][空闲][已用][空闲][空闲][已用]此时申请一个较大的块就失败了,因为虽然空闲块之和够大,但!!!不连续!!!

堆分配的空间在逻辑地址(虚拟地址)上是连续的,但在物理地址上是不连续的(因为采用了页式内存管理,windows下有段机制、分页机制),如果逻辑地址空间上已经没有一段连续且足够大的空间,则分配内存失败。

2. 如何减少堆碎片?

  • 大块连续分配,避免频繁申请小块内存。
  • 使用内存池、自定义分配器(如 tcmalloc)等技术。

栈的内存空间是连续的吗?

✅ 在虚拟内存(也叫逻辑地址)上,栈是连续

操作系统为每个进程分配一个独立的虚拟内存空间。栈空间会在这个虚拟地址空间中分配出一整块连续的地址范围。比如:

0 x 7 f f f f f f f e 000 → 0 x 7 f f f f f f f 0000 0x7fffffffe000 \rightarrow 0x7fffffff0000 0x7fffffffe0000x7fffffff0000

这是一段连续的地址范围,栈从高地址开始往低地址增长(这和堆相反,堆是从低地址往高地址增长)。

❌ 在物理内存上,栈可能是不连续

虽然虚拟地址是连续的,但实际映射到的物理内存(真实内存条上的位置)可能是分散的。这是因为现代操作系统使用了分页机制,可以把每页(通常是4KB)分别映射到不同的物理内存页上。


举个简单例子

我们假设你的函数里写了这样的代码:

void foo() {int a = 1;int b = 2;int c = 3;
}

这 3 个变量都存放在栈上,它们在内存中是 连续排列 的。比如:

地址    内容
0x7fffd4a0    -> 3 (变量 c)
0x7fffd4a4    -> 2 (变量 b)
0x7fffd4a8    -> 1 (变量 a)

你可以看到变量是一个挨着一个地分配在内存中的,栈空间是连续的。

总结一句话(面试也能用上)

栈的空间在虚拟地址上是连续的,用于保存函数调用相关的临时数据;虽然在物理内存上可能不连续,但对程序员来说是完全透明的。


拓展:堆和栈的对比(便于理解)
属性栈(Stack)堆(Heap)
分配方式编译器自动分配(快速)程序员手动分配(malloc/new)
空间大小通常较小(几 MB)通常较大(GB级别)
地址增长方向从高地址向低地址增长从低地址向高地址增长
内存连续性逻辑上连续逻辑上不一定连续
管理方式系统自动管理需要程序员手动管理(要记得释放)

五、用户栈与内核栈

栈类型所属空间用途
用户栈用户空间存储用户函数调用相关数据,如局部变量等
内核栈内核空间存储中断/系统调用时内核执行所需信息

为什么不能共用?

  • 安全性:如果只用用户栈,内核需保护,不可让用户空间访问内核栈。
  • 资源限制:如果只用系统栈,系统栈通常较小,用户程序调用深度大,容易溢出。

六、线程与栈:每个线程都有自己的栈

  • 程序运行靠线程执行
  • 每个线程创建时,系统分配独立栈空间
  • 所有线程共享堆区和全局变量,但各自栈空间互不干扰
void *thread_func(void *arg) {int thread_var = 5; // 在线程独立栈中
}int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);// 主线程和子线程有独立栈
}

七、常见问题汇总与图解

1. 返回局部变量地址错在哪里?

int* func() {int x = 10;return &x; // 错误!x是栈变量,函数结束x消失
}

2. malloc 分配的内存一定连续吗?

  • 逻辑地址上看是连续的。
  • 物理地址上可能不是,因为现代操作系统采用虚拟内存 + 分页机制

八、总结对比表

区域作用分配方式生命周期示例变量
代码段存机器指令、常量编译分配程序始终存在函数、字符串常量
数据段初始化全局/静态变量编译分配程序始终存在int a = 10;
BSS段未初始化的全局/静态变量编译分配程序始终存在static int x;
动态分配程序员控制手动释放malloc/new
函数内变量、调用信息自动分配函数调用期局部变量、参数

九、思维导图助记

内存区域
├── 代码段:函数、常量字符串
├── 数据段:初始化的全局变量
├── BSS段 :未初始化的全局/静态变量
├── 堆区  :malloc/new 动态变量
└── 栈区  :局部变量、函数调用帧

十、小结

  • 栈适合存放生命周期短、容量小的数据,操作快,但空间小。
  • 堆适合存放生命周期长、大小不定的数据,但操作慢。
  • 合理分配内存,养成良好释放内存的习惯,能显著提高程序稳定性和效率!
http://www.xdnf.cn/news/90181.html

相关文章:

  • 电商平台比价 API 接口,避免人工比价的低效与误差
  • Mellanox网卡qos设置
  • window如何关闭指定端口
  • 嵌入式人工智能应用-第三章 opencv操作8 图像特征之LBP特征 下
  • 【C++游戏引擎开发】第20篇:基于物理渲染(PBR)——辐射度量学
  • 如何一键提取多个 PPT 幻灯片中的备注到 TXT 记事本文件中
  • 爱普生FC-12M晶振在车载系统中广泛应用
  • Spring事件机制,如何使用Spring事件监听器
  • Vue 实例 VM 访问属性
  • 【MySQL】索引失效问题详解
  • STM32单片机入门学习——第46节: [14-1] WDG看门狗
  • 怎样用 esProc 提速主子表关联时的 EXISTS
  • 利用参考基因组fa和注释文件gff提取蛋白编码序列
  • 定义python中的函数和类
  • SVT-AV1编码器中的模块
  • 如何收集用户白屏/长时间无响应/接口超时问题
  • linux命令集
  • 来啦,烫,查询达梦表占用空间
  • SVT-AV1编码器初始化函数
  • Linux 系统监控基石:top 命令详解与实战指南
  • 华为仓颉编程语言基础概述
  • JavaFX深度实践:从零构建高级打地鼠游戏(含多物品与反馈机制)
  • Windows7升级Windows10,无法在此驱动器上安装Windows
  • 可预测的随机逻辑 -- b01lers CTF when wp
  • 关于大数据的基础知识(三)——数据安全与合规
  • 谐振模态图
  • 【OSG学习笔记】Day 6: Day 6: 几何体(Geometry)的创建与自定义
  • IP-Guard加密系统开启不了,说连接失败了,IPG数据库更改为多用户模式修复成功。
  • 【C++】Json-Rpc框架项目介绍(1)
  • 审计平台本地部署遇到的坑