小白从一块主板到一行代码:彻底搞懂进程、线程、栈和堆
小白从一块主板到一行代码:彻底搞懂进程、线程、栈和堆
你有没有想过,当你在电脑上运行一个 C 程序时,屏幕后面到底发生了什么?
前阵子我从「电脑里到底有什么」这个最朴素的问题出发,一路往下追问,最终把进程、线程、栈和堆这些概念串成了一条完整的链路。这篇文章,就是把这条链路完整记录下来,帮你把散落的知识点拼成一张图。
一、先看硬件:一台电脑里到底有什么
电脑可以拆成我们看得见的三部分:显示器、主机、键盘鼠标。真正的秘密都在主机(机箱)里。
主机内部大致是这样的:

电脑
├── 显示器
├── 主机(机箱)
│ ├── 主板(就是那一大块 PCB 板)
│ │ ├── CPU (直接插在 CPU 插座上)
│ │ ├── 内存条 (插在内存插槽上)
│ │ ├── 显卡 (插在 PCIe 插槽上)
│ │ └── CMOS 电池 (装/焊在主板上)
│ ├── 硬盘(通过 SATA 线连接到主板)
│ ├── 电源
│ └── 散热风扇
└── 键盘 / 鼠标
这里有两个容易误会的地方,先讲清楚:
- 主板就是那块 PCB 板。 PCB(印刷电路板)是材质名字,主板是功能名字,它们是同一个东西,不是两个器件。
- 硬盘一般不「在」主板上。 CPU、内存条是直接插在主板上的,但硬盘是独立的部件,通过数据线连到主板。
再记住三个角色分工:
| 部件 | 角色 | 为什么 |
|---|---|---|
| CPU | 大脑 | 负责计算,所有指令都靠它执行 |
| 内存条 | 临时仓库 | 通电才能存数据,断电就清空 |
| 硬盘 | 永久仓库 | 断电数据还在 |
一句话:CPU 干活,内存存「正在用的」,硬盘存「长期留的」。
二、操作系统:一个有特权的常驻程序
硬件之上,是操作系统(Linux、Windows 等)。它和我们的程序一样,本质是软件,是一堆 C 代码编译成的机器指令。
但它有个特别之处:
- 平时:安静地躺在硬盘里,什么都不干。
- 开机时:BIOS/UEFI 把它从硬盘加载进内存,然后启动它。
- 运行中:常驻内存不走,一直管理所有硬件。
它凭什么能管硬件?靠硬件提供的特权机制——CPU 被分成两种状态:
| 状态 | 谁在里面 | 能干什么 |
|---|---|---|
| 内核态 | 操作系统 | 直接操作 CPU、内存、硬盘、网卡 |
| 用户态 | 你的程序 | 只能算自己的数据,碰不了硬件 |
所以你的程序想读文件时,自己没权限碰硬盘,只能喊一句 open()——这就是系统调用:CPU 切到内核态,内核帮你读硬盘,把结果还给你,再切回用户态。
还有个容易被搞混的点:CPU 执行的,不只是操作系统的代码。 操作系统内核代码在 CPU 上跑,你的程序代码也在 CPU 上跑,两者交替。操作系统是「调度者」,它安排自己跑一会儿(管硬件),再安排你的程序跑一会儿。
三、进程:操作系统发的「虚拟地址空间」
我们申请创建一个进程,可以理解成申请一个「任务」。操作系统同意后,会给这个进程分配一个虚拟地址空间。
关键在这里:这块内存并不是进程真正独享的物理内存,而是操作系统在物理内存里划出来借给进程用的一片区域,让进程「以为」自己拥有连续的内存。
这个虚拟地址空间内部,被分成四片区域:

四片区域各司其职:
| 区域 | 装什么 | 谁来管 |
|---|---|---|
| 栈 Stack | 局部变量、函数参数、返回地址 | 编译器自动管理 |
| 堆 Heap | malloc / new 分配的内存 | 我们手动管 + OS 配合 |
| 数据段 | 全局变量、静态变量 | 程序结束才释放 |
| 代码段 | 编译后的机器指令 | 只读,不可改 |
四、栈和堆:一个自动,一个手动
栈和堆是初学者最常迷糊的一对,用一张表说清楚:
| 对比项 | 栈 Stack | 堆 Heap |
|---|---|---|
| 放什么 | 局部变量、函数参数 | malloc / new 动态分配 |
| 谁管理 | 编译器自动 | 程序员手动 free |
| 速度 | 快(指针一挪就好) | 慢(要找空闲块) |
| 大小 | 小(通常几 MB) | 大(受物理内存限制) |
| 增长方向 | 向下(高地址 → 低地址) | 向上(低地址 → 高地址) |
| 生命周期 | 函数结束自动消失 | 手动释放才消失,忘了就泄漏 |
关于「谁管理」再精确一句:栈是编译器自动生成代码管理的(CPU 硬件还有专用的栈指针配合);堆是靠 malloc / free 这些库函数管理,而这些库函数底层会调系统调用,找操作系统要地皮。所以堆是「我们 + OS 一起工作」。
可以用一小段代码直观验证四个区域的地址:
#include <stdio.h>
#include <stdlib.h>
int global_var = 100; // 数据段
int main(void) {
int stack_var = 10; // 栈
int *heap_ptr = malloc(sizeof(int)); // 堆
*heap_ptr = 20;
printf("全局变量(数据段): %p\n", (void*)&global_var);
printf("局部变量(栈) : %p\n", (void*)&stack_var);
printf("malloc(堆) : %p\n", (void*)heap_ptr);
printf("函数地址(代码段): %p\n", (void*)main);
free(heap_ptr); // 堆必须手动释放
return 0;
}
编译运行后,你会看到四类地址的大小关系,正好对应上面的内存布局图:栈地址通常最大(顶部),代码段地址最小(底部)。
五、线程:进程里的多个「小任务」
一个进程里可以有多条线程,相当于把大任务拆成几个可以并发执行的小任务。
线程最核心的一条规则:
同一个进程的多个线程,共享堆、数据段、代码段,只有栈是各自独立的。
为什么栈要独立?因为每个线程有自己的函数调用链(main 调 A,A 再调 B……),需要各自的空间来保存这些调用现场。而它们要操作同一份数据(共享堆和全局变量),所以其他三区共享。
举个创建线程的例子:
#include <stdio.h>
#include <pthread.h>
void *worker(void *arg) {
int id = *(int *)arg;
printf("线程 %d 在跑\n", id);
return NULL;
}
int main(void) {
pthread_t t1, t2;
int a = 1, b = 2;
pthread_create(&t1, NULL, worker, &a); // 创建线程 1
pthread_create(&t2, NULL, worker, &b); // 创建线程 2
pthread_join(t1, NULL); // 等待线程结束
pthread_join(t2, NULL);
return 0;
}
编译时要链接 pthread 库:
gcc demo.c -o demo -pthread
./demo
一个常见的疑问:多线程是不是必须多核 CPU 才能跑?
答案是:单核也能跑多线程。 单核上靠「时间片轮转」快速切换,看起来像同时进行;多核上才是真正的并行。简单说,多线程任务不依赖多核,但多核能让它真正同时跑、更快。
六、串起来:从代码到 CPU 的完整链路
把上面所有概念拼起来,就是这一条链路:
硬件(CPU / 内存 / 硬盘)
↓ 被管理
操作系统(内核代码,有特权,常驻内存)
↓ 分配虚拟地址空间 + 调度
进程(一个虚拟地址空间 = 栈 / 堆 / 数据段 / 代码段)
↓ 内部真正干活的
线程(执行流,共享空间,各有各的栈)
↓ 被 OS 调度到
CPU 核心(从内存取指令,真正执行)
注意最后一步的精确说法:CPU 执行的不是内存,而是代码(指令)。内存是装指令和数据的地方。操作系统把线程调度到 CPU 核心上,CPU 从内存里取出指令来执行。
拿「厨师」打个比方:内存是菜谱本,CPU 是厨师,操作系统是安排谁去做菜的人。是厨师照着菜谱本做菜,不是菜谱本自己去做菜。
七、顺带一问:编译器从哪来
既然操作系统是 C 写的,C 又需要编译器来翻译,那编译器又是哪来的?这是个「先有鸡还是先有蛋」的经典问题。
答案是自举(bootstrapping):
最底层:机器码(0101)
↑ 手写
汇编器
↑ 用汇编写
第一个简单的 C 编译器
↑ 用旧编译器编译新编译器
更强的编译器
↑ 不断自举
现在的 gcc / clang(能编译它们自己)
最早的程序员只能直接手写 0101 机器码,或者手写汇编器;有了汇编器,才写出第一个简单的 C 编译器;之后每一次升级,都是「旧编译器编译新编译器」,一步步自我增强到现在。
写在最后
把整条链用一句话收个尾:
硬件(CPU、内存、硬盘)上跑着操作系统;操作系统是有特权的常驻程序,负责调度 CPU、管理内存;我们申请进程时会分到一个虚拟地址空间(栈、堆、数据段、代码段四区);线程共享这个空间但各有各的栈;最后操作系统把线程调度到 CPU 核心上,CPU 从内存取指令执行。
搞懂这条链路,进程、线程、栈和堆就不再是四个孤立的名词,而是同一张图上的不同楼层。剩下的,就是多写几段代码,去实际感受它们各自的位置和脾气。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)