C语言编译原理
详细呈现了C语言的编译过程
C语言编译原理
C语言编译原理
文件名:add.c
```C add.c
#include
int main() {
int a = 1 + 1; // 定义变量a并赋值为1+1
printf(“a = %d\n”, a); // 输出结果
return 0;
}
1
2
3
4
## 直接编译(compile)
通过clang编译器进行编译,在terminal中
```shell
clang add.c
这会直接生成一个叫做a.out的可执行文件——即内容是二进制(Universal Binary)文件,但这还不是机器码(machine code),这个文件需要通过cpu中的解码器进行最终翻译,才是“机器码”。clang命令还可以通过-o方法生成exec文件,内容是完全等价的。
1
clang -o add add.c
这会生成一个叫做add的exec文件,双击就执行。
查看二进制文件
使用hexdump、otool和xxd工具和查看机器码。
- 查看二进制的二进制文件
1
xxd -b a.out
000080b8: 01001000 00111111 00000000 00000000 00000001 00000000 H?.... 000080be: 00000000 00000000 00011100 00000000 00000000 00000000 ...... 000080c4: 00000001 00000000 00000000 00000001 00000000 00000000 ...... 000080ca: 00000000 00000000 00000000 00000000 00000000 00000000 ...... 000080d0: 00000010 00000000 00000000 00000000 00000010 00000000 ...... 000080d6: 00000000 00000000 00100000 00000000 01011111 01011111 .. .__ 000080dc: 01101101 01101000 01011111 01100101 01111000 01100101 mh_exe 000080e2: 01100011 01110101 01110100 01100101 01011111 01101000 cute_h 000080e8: 01100101 01100001 01100100 01100101 01110010 00000000 eader. 000080ee: 01011111 01101101 01100001 01101001 01101110 00000000 _main. 000080f4: 01011111 01110000 01110010 01101001 01101110 01110100 _print 000080fa: 01100110 00000000 00000000 00000000 00000000 00000000 f..... 00008100: 11111010 11011110 00001100 11000000 00000000 00000000 ...... 00008106: 00000001 10010000 00000000 00000000 00000000 00000001 ...... 0000810c: 00000000 00000000 00000000 00000000 00000000 00000000 ...... 00008112: 00000000 00010100 11111010 11011110 00001100 00000010 ...... 00008118: 00000000 00000000 00000001 01111100 00000000 00000010 ...|.. 0000811e: 00000100 00000000 00000000 00000010 00000000 00000010 ...... 00008124: 00000000 00000000 00000000 01011100 00000000 00000000 ...\.. 0000812a: 00000000 01011000 00000000 00000000 00000000 00000000 .X.... 00008130: 00000000 00000000 00000000 00001001 00000000 00000000 ......
这里是输出的片段,完整的输出很长。第一列是十六进制的内存地址,中间六列是二进制的数据,每一段是一个字节(8位),最右侧列是xxd进行ASC2解码的结果,可以看到有些是能解出来的,有些解出来是乱码,ASC2乱码的部分很可能是机器码。
- 二进制是看不懂的,我们还是希望看看十六进制。此处我们使用otool工具查看TEXT字段的内容,otool会自动给我们十六进制的码
1
otool -t a.out
a.out: (__TEXT,__text) section 0000000100003f48 d100c3ff a9027bfd 910083fd 52800008 0000000100003f58 b81f43a8 b81fc3bf 52800048 b81f83a8 0000000100003f68 b85f83a9 aa0903e8 910003e9 f9000128 0000000100003f78 90000000 913e8000 94000005 b85f43a0 0000000100003f88 a9427bfd 9100c3ff d65f03c0
当然,这段十六进制的码本质上和二进制的码一样让人看不懂,我们添加-V进行反编译操作,就能得到这段代码的汇编语言版本了。
1
otool -tV a.out
a.out: (__TEXT,__text) section _main: 0000000100003f48 sub sp, sp, #0x30 0000000100003f4c stp x29, x30, [sp, #0x20] 0000000100003f50 add x29, sp, #0x20 0000000100003f54 mov w8, #0x0 0000000100003f58 stur w8, [x29, #-0xc] 0000000100003f5c stur wzr, [x29, #-0x4] 0000000100003f60 mov w8, #0x2 0000000100003f64 stur w8, [x29, #-0x8] 0000000100003f68 ldur w9, [x29, #-0x8] 0000000100003f6c mov x8, x9 0000000100003f70 mov x9, sp 0000000100003f74 str x8, [x9] 0000000100003f78 adrp x0, 0 ; 0x100003000 0000000100003f7c add x0, x0, #0xfa0 ; literal pool for: "a = %d\n" 0000000100003f80 bl 0x100003f94 ; symbol stub for: _printf 0000000100003f84 ldur w0, [x29, #-0xc] 0000000100003f88 ldp x29, x30, [sp, #0x20] 0000000100003f8c add sp, sp, #0x30 0000000100003f90 ret
这就是很清晰的汇编语言了,左列是地址,中间列是助记符(Mnemonic),右列是操作数(Operands),指定指令的操作目标或参数,告诉 CPU 对什么数据进行操作以及如何操作。
真实编译过程
真实的编译过程其实比这个要再复杂一些。 ![[真实编译过程.png]]
- .i 文件是预编译的结果,它会在简简单单的add.c前添加一堆乱七八糟看不懂的玩意,本质还是高级语言。
- .s 文件就是汇编语言了,也是人类可读的,知识需要一点脑子。清晰展现了各种各样的指令。
- .o 文件是目标文件,有啥用我不知道
- .out 文件是最终的二进制文件,属于双击即可运行的那种。
本文由作者按照
CC BY 4.0
进行授权