本文最后更新于 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] │ │ EBX ← [BP+12] │ │ EAX ← EAX + EBX │ │ [BP-4] ← EAX │ │ │ └──────────────────────────┘
栈中数据不变,计算在寄存器中完成: ┌─────────────────┐ │ ...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 iload_2 invokestatic Example.add(II)I
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 iload_1 iadd istore_2 iload_2 ireturn
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; 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; } } }
|