文章

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. 查看二进制的二进制文件
    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乱码的部分很可能是机器码。

  2. 二进制是看不懂的,我们还是希望看看十六进制。此处我们使用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]]

  3. .i 文件是预编译的结果,它会在简简单单的add.c前添加一堆乱七八糟看不懂的玩意,本质还是高级语言。
  4. .s 文件就是汇编语言了,也是人类可读的,知识需要一点脑子。清晰展现了各种各样的指令。
  5. .o 文件是目标文件,有啥用我不知道
  6. .out 文件是最终的二进制文件,属于双击即可运行的那种。
本文由作者按照 CC BY 4.0 进行授权