系统栈与高级语言栈的比较

本文最后更新于 2025年9月16日 下午

系统栈模型

在C语言中,主要分为编译时和运行时两个步骤,编译时完成数据的准备过程,运行时通过系统栈完成代码的执行过程

编译时

  • 将C代码通过预处理(识别宏定义根据条件进行宏替换)
  • 代码编译(将C文件编译为后缀为.s的汇编文件)
  • 汇编(将汇编代码翻译为机器码,生成.o的机器码文件)
  • 链接,整理多份文件,解决符号引用和地址定位信息,整理内存布局,生成可执行文件
  • 加载 将可执行文件加载到内存中
  • 执行 在内存中找到入口方法,从入口方法启动执行

运行时

运行时的核心实现是通过 指令指针和栈指针以及寄存器的读写过程实现的。

通过指令指针,查询需要执行的代码在代码段中存储的对应位置。

通过栈指针,向栈中临时存储 数据结构,返回地址,基地址指针信息。

通过寄存器,进行地址查询和数值计算。

栈执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
示例C代码:
int add(int a, int b) {
int result = a + b;
return result;
}

int main() {
int x = 5, y = 3;
int sum = add(x, y);
return 0;
}

执行过程栈变化:

1. 程序启动 - main函数开始
┌─────────────────┐ ← 高地址 (栈底)
│ │
│ (栈空间) │
│ │
│ │
└─────────────────┘ ← 低地址 (栈顶) SP指向这里
系统启动后,由内核分配栈空间,以及栈空间起始地址。
数据加载向低地址延申

2. main函数栈帧建立
┌─────────────────┐ ← 高地址
│ │
│ 返回地址(OS) │ ← main的返回地址
├─────────────────┤
│ 旧的BP值 │ ← 保存的基址指针
├─────────────────┤ ← BP指向这里
int x = 5 │ ← 局部变量x
├─────────────────┤
int y = 3 │ ← 局部变量y
├─────────────────┤
int sum │ ← 局部变量sum(未初始化)
└─────────────────┘ ← SP指向这里
CPU通过读取机器码,创建数据存储在栈空间

3. 调用add(x, y) - 参数压栈
┌─────────────────┐ ← 高地址
│ │
│ 返回地址(OS) │
├─────────────────┤
│ 旧的BP值 │
├─────────────────┤
int x = 5
├─────────────────┤
int y = 3
├─────────────────┤
int sum │
├─────────────────┤
│ 参数b = 3 │ ← add函数的参数b
├─────────────────┤
│ 参数a = 5 │ ← add函数的参数a
├─────────────────┤
│ 返回地址 │ ← add函数执行完后的返回地址
└─────────────────┘ ← SP指向这里
加载add函数,参数值在编译时的已经通过偏移确定
将参数值,返回地址,main BP一起压入操作栈中

4. add函数栈帧建立
┌─────────────────┐ ← 高地址
│ │
│ 返回地址(OS) │
├─────────────────┤
│ 旧的BP值 │
├─────────────────┤
int x = 5
├─────────────────┤
int y = 3
├─────────────────┤
int sum │
├─────────────────┤
│ 参数b = 3
├─────────────────┤
│ 参数a = 5
├─────────────────┤
│ 返回地址 │
├─────────────────┤
│ 旧的BP值 │ ← 保存main的BP
├─────────────────┤ ← BP指向这里(add的栈帧开始)
int result = 8 │ ← add函数的局部变量
└─────────────────┘ ← SP指向这里
寄存器通过机器码直接读取参数值进行运算,将结果压入栈中

5. add函数执行 - 计算过程
┌──────────────────────────┐
│ │
│ CPU寄存器操作过程: │
│ │
│ EAX ← [BP+8] │ // 加载参数a到寄存器EAX
│ EBX ← [BP+12] │ // 加载参数b到寄存器EBX
│ EAX ← EAX + EBX │ // 寄存器中执行加法运算
│ [BP-4] ← EAX │ // 将结果存储到result变量
│ │
└──────────────────────────┘

栈中数据不变,计算在寄存器中完成:
┌─────────────────┐
│ ...main栈帧... │
├─────────────────┤
│ 参数b = 3
├─────────────────┤
│ 参数a = 5
├─────────────────┤
│ 返回地址 │
├─────────────────┤
│ 旧的BP值 │
├─────────────────┤ ← BP
int result = 8 │ ← 计算结果存储在这里
└─────────────────┘ ← SP

6. add函数返回 - 栈帧清理
┌─────────────────┐
│ ...main栈帧... │
├─────────────────┤
│ 参数b = 3 │ ← 这些会被清理
├─────────────────┤
│ 参数a = 5 │ ← 这些会被清理
├─────────────────┤
│ 返回地址 │ ← 弹出到PC寄存器
├─────────────────┤
│ (已清理的数据) │
├─────────────────┤
│ (已清理的数据) │
└─────────────────┘ ← SP回到这里
add函数运算结束,进行函数栈清理,返回main函数栈底

返回值通过EAX寄存器传递: EAX = 8

7. 回到main函数 - 接收返回值
┌─────────────────┐ ← 高地址
│ │
│ 返回地址(OS) │
├─────────────────┤
│ 旧的BP值 │
├─────────────────┤ ← BP恢复到main的栈帧
int x = 5
├─────────────────┤
int y = 3
├─────────────────┤
int sum = 8 │ ← EAX寄存器的值赋给sum
└─────────────────┘ ← SP指向这里
将返回值存储到sum变量中

指针寄存器状态:
══════════════════
PC (程序计数器): 指向当前执行的指令地址
SP (栈指针): 指向栈顶
BP (基址指针): 指向当前函数栈帧的底部
EAX/EBX/... : 通用寄存器,用于计算和数据传递

高级语言栈模型(双栈结构)

以Java实现为例,在Java中,代码执行通常被分为3个阶段,编译时,主要进行Java字节码文件的准备过程,类加载时,这是一个宽泛的过程主要是JVM的启动,准备,从入口函数启动Java程序,发生类调用时,查询类,查询类加载器,完成类加载的增量过程,运行时,JVM解释器解释字节码交给系统栈执行的过程,可以看出类加载和运行是混杂进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
Java程序执行时的栈处理过程
═══════════════════════════════════════════════════════════════

示例Java代码:
public class Example {
public static int add(int a, int b) {
int result = a + b;
return result;
}

public static void main(String[] args) {
int x = 5, y = 3;
int sum = add(x, y);
System.out.println(sum);
}
}

Java执行的双层栈结构:
═══════════════════════

第一层: JVM虚拟栈 (栈帧结构)
第二层: 系统栈 (JVM运行在其上)

详细执行过程:

1. JVM启动 - main方法字节码加载
┌─────────────────────────────────┐ JVM虚拟栈
│ │
│ (空栈) │
│ │
└─────────────────────────────────┘
┌─────────────────────────────────┐ 系统栈
│ JVM进程栈空间 │
│ (C/C++实现的JVM代码) │
└─────────────────────────────────┘

2. main方法栈帧建立
JVM虚拟栈:
┌─────────────────────────────────┐ ← 高地址
│ main方法栈帧: │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: (空) ││
│ ├─────────────────────────────┤│
│ │ 局部变量表: ││
│ │ [0] args: String[] ││ ← 方法参数
│ │ [1] x: int = 5 ││ ← 局部变量插槽1
│ │ [2] y: int = 3 ││ ← 局部变量插槽2
│ │ [3] sum: int (未初始化) ││ ← 局部变量插槽3
│ ├─────────────────────────────┤│
│ │ 方法信息: ││
│ │ - 常量池引用 ││
│ │ - 方法元数据 ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘

系统栈 (JVM解释器的C代码):
┌─────────────────────────────────┐
│ JVM解释器栈帧: │
│ - 当前执行的字节码指令指针 │
│ - JVM栈指针 │
│ - 局部变量表指针 │
└─────────────────────────────────┘

3. 执行 add(x, y) 调用 - 字节码指令序列

字节码指令:
iload_1 // 加载局部变量x到操作数栈
iload_2 // 加载局部变量y到操作数栈
invokestatic Example.add(II)I // 调用static方法

JVM虚拟栈状态变化:

3a. iload_1 执行后:
┌─────────────────────────────────┐
│ main方法栈帧: │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: ││
│ │ [top] 5 (x的值) ││ ← 栈顶
│ ├─────────────────────────────┤│
│ │ 局部变量表: (不变) ││
│ │ [0] args [1] x=5 [2] y=3 ││
│ │ [3] sum ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘

3b. iload_2 执行后:
┌─────────────────────────────────┐
│ main方法栈帧: │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: ││
│ │ [top] 3 (y的值) ││ ← 栈顶
│ │ [ ] 5 (x的值) ││
│ ├─────────────────────────────┤│
│ │ 局部变量表: (不变) ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘

4. add方法栈帧建立 (invokestatic执行)
JVM虚拟栈:
┌─────────────────────────────────┐ ← 高地址
│ add方法栈帧: │ ← 新栈帧
│ ┌─────────────────────────────┐│
│ │ 操作数栈: (空) ││
│ ├─────────────────────────────┤│
│ │ 局部变量表: ││
│ │ [0] a: int = 5 ││ ← 参数从操作数栈传递
│ │ [1] b: int = 3 ││ ← 参数从操作数栈传递
│ │ [2] result: int (未初始化) ││ ← 局部变量插槽
│ ├─────────────────────────────┤│
│ │ 返回地址: main方法PC+1 ││
│ └─────────────────────────────┘│
├─────────────────────────────────┤
│ main方法栈帧: │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: (清空) ││ ← 参数已传递给add
│ ├─────────────────────────────┤│
│ │ 局部变量表: (不变) ││
│ │ [0] args [1] x=5 [2] y=3 ││
│ │ [3] sum ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘

5. add方法执行 - 字节码指令处理

字节码序列:
iload_0 // 加载参数a到操作数栈
iload_1 // 加载参数b到操作数栈
iadd // 弹出两个数,相加,结果压栈
istore_2 // 弹出结果,存储到局部变量result
iload_2 // 加载result到操作数栈(准备返回)
ireturn // 返回int值

5a. iload_0, iload_1, iadd执行后:
┌─────────────────────────────────┐
│ add方法栈帧: │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: ││
│ │ [top] 8 (5+3的结果) ││ ← iadd计算结果
│ ├─────────────────────────────┤│
│ │ 局部变量表: ││
│ │ [0] a=5 [1] b=3 [2] result ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘

5b. istore_2执行后:
┌─────────────────────────────────┐
│ add方法栈帧: │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: (空) ││
│ ├─────────────────────────────┤│
│ │ 局部变量表: ││
│ │ [0] a=5 [1] b=3 ││
│ │ [2] result=8 ││ ← 计算结果存储
│ └─────────────────────────────┘│
└─────────────────────────────────┘

6. 方法返回 (iload_2, ireturn执行)
┌─────────────────────────────────┐
│ main方法栈帧: (add栈帧已清理) │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: ││
│ │ [top] 8 (返回值) ││ ← add方法的返回值
│ ├─────────────────────────────┤│
│ │ 局部变量表: ││
│ │ [0] args [1] x=5 [2] y=3 ││
│ │ [3] sum ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘

7. 接收返回值 (istore_3执行)
┌─────────────────────────────────┐
│ main方法栈帧: │
│ ┌─────────────────────────────┐│
│ │ 操作数栈: (空) ││
│ ├─────────────────────────────┤│
│ │ 局部变量表: ││
│ │ [0] args [1] x=5 [2] y=3 ││
│ │ [3] sum=8 ││ ← 返回值存储完成
│ └─────────────────────────────┘│
└─────────────────────────────────┘

JVM解释器在系统栈中的实现 (简化的C代码逻辑):
═══════════════════════════════════════════════════════

系统栈中JVM解释器的处理过程:

void execute_bytecode() {
// 系统栈帧中的局部变量
unsigned char* pc; // 字节码程序计数器
StackFrame* current_frame; // 当前JVM栈帧指针
int* operand_stack; // 操作数栈指针
Object** local_vars; // 局部变量表指针

while (true) {
switch (*pc) {
case ILOAD_1:
// 从局部变量表加载到操作数栈
push_operand(local_vars[1]);
pc++;
break;

case IADD:
// 从操作数栈弹出两个数相加
int b = pop_operand();
int a = pop_operand();
push_operand(a + b);
pc++;
break;

case INVOKESTATIC:
// 创建新栈帧,参数传递
create_new_frame();
transfer_parameters();
pc = method_entry_point;
break;

case IRETURN:
// 清理当前栈帧,返回值传递
int return_value = pop_operand();
destroy_current_frame();
push_operand_to_caller(return_value);
pc = return_address;
break;
}
}
}

系统栈与高级语言栈的比较
http://gadoid.io/2025/09/16/系统栈与高级语言栈的比较/
作者
Codfish
发布于
2025年9月16日
许可协议