团队合作使梦想成真

或者,是编译C程序的四个阶段。

编译C程序是一个多阶段的过程。 该过程可以分为四个单独的阶段:预处理,编译,组装和链接。 传统的C编译器通过调用其他程序来处理每个阶段来协调这个过程。

由于眼见为实,因此我将使用以下程序来说明该过程的每个步骤:

  #include  
 整型 
主(无效)
{
puts(“你好,霍尔伯顿!”);
返回0;
}

该程序将称为hello.c。

预处理

编译的第一阶段称为预处理。 在此阶段,预处理器将以#字符开头的行解释为预处理器命令。

在解释命令之前,预处理器会进行一些初始处理。 这包括连接连续的行(以\结尾的行)和去除注释。

要打印预处理阶段的结果,请将-E选项传递给cc

 gcc -E hello.c 

在此步骤中,预处理器转到上面程序顶部一行中提到的头文件,并将其与hello.c文件的内容连接在一起。

  #1“ hello.c” 
#1“ ”
#1“ ”
#1“ /usr/include/stdc-predef.h” 1 3 4
#1“ ” 2
#1“ hello.c”
#1“ /usr/include/stdio.h” 1 3 4
#27“ /usr/include/stdio.h” 3 4
#1“ /usr/include/features.h” 1 3 4
#374“ /usr/include/features.h” 3 4
#1“ /usr/include/x86_64-linux-gnu/sys/cdefs.h” 1 3 4
#385“ /usr/include/x86_64-linux-gnu/sys/cdefs.h” 3 4
#1“ /usr/include/x86_64-linux-gnu/bits/wordsize.h” 1 3 4
#386“ /usr/include/x86_64-linux-gnu/sys/cdefs.h” 2 3 4
#375“ /usr/include/features.h” 2 3 4
#398“ /usr/include/features.h” 3 4
#1“ /usr/include/x86_64-linux-gnu/gnu/stubs.h” 1 3 4
#10“ /usr/include/x86_64-linux-gnu/gnu/stubs.h” 3 4
#1“ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h” 1 3 4
#11“ /usr/include/x86_64-linux-gnu/gnu/stubs.h” 2 3 4
#399“ /usr/include/features.h” 2 3 4
#28“ /usr/include/stdio.h” 2 3 4
  #1“ /usr/lib/gcc/x86_64-linux-gnu/4.8/include/stddef.h” 1 3 4 
#212“ /usr/lib/gcc/x86_64-linux-gnu/4.8/include/stddef.h” 3 4
typedef long unsigned int size_t;
#34“ /usr/include/stdio.h” 2 3 4
  #1“ /usr/include/x86_64-linux-gnu/bits/types.h” 1 3 4 
#27“ /usr/include/x86_64-linux-gnu/bits/types.h” 3 4
#1“ /usr/include/x86_64-linux-gnu/bits/wordsize.h” 1 3 4
#28“ /usr/include/x86_64-linux-gnu/bits/types.h” 2 3 4
  typedef unsigned char __u_char; 
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;
  typedef签名的字符__int8_t; 
typedef unsigned char __uint8_t;
typedef有符号short int __int16_t;
typedef unsigned short int __uint16_t;
typedef签名的int __int32_t;
typedef unsigned int __uint32_t;
  typedef签署long int __int64_t; 
typedef unsigned long int __uint64_t;
  [省略行] 
  extern int ftrylockfile(FILE * __ stream)__attribute__((__nothrow__,__leaf__)); 
  extern void funlockfile(FILE * __ stream)__attribute__((__nothrow__,__leaf__)); 
#943“ /usr/include/stdio.h” 3 4
  #2“ hello.c” 2 
 整型 
主(无效)
{
puts(“ Hello,Holberton!”);
返回0;
}

最后看到我们的原始程序吗?

汇编

编译的第二阶段令人困惑,称为编译。 在此阶段,预处理后的代码将转换为汇编指令。 这些构成了人类可读的中间语言。 此步骤允许C代码包含内联汇编指令,并允许使用不同的汇编器。

要保存编译阶段的结果,请将-S选项传递给cc

 gcc -S hello.c 

这将创建一个名为hello.s的文件,其中包含生成的汇编指令。 生成以下输出:

  .file“ hello.c” 
.section .rodata
.LC0:
.string“你好,霍尔伯顿!”
。文本
.globl主
.type main,@功能
主要:
.LFB0:
.cfi_startproc
pushq%rbp
.cfi_def_cfa_offset 16
.cfi_offset 6,-16
movq%rsp,%rbp
.cfi_def_cfa_register 6
movl $ .LC0,%edi
看涨期权
movl $ 0,%eax
popq%rbp
.cfi_def_cfa 7,8
退回
.cfi_endproc
.LFE0:
.size main,.- main
.ident“ GCC:(Ubuntu 4.8.4-2ubuntu1〜14.04.3)4.8.4”
.section .note.GNU-stack,“”,@ progbits

部件

在汇编阶段,使用汇编程序将汇编指令转换为机器代码或目标代码。 输出包含要由目标处理器运行的实际指令。

要保存汇编阶段的结果,请将-c选项传递给cc

 gcc -c hello.c 

运行上面的命令将创建一个名为hello.o的文件,其中包含程序的目标代码。 该文件的内容为二进制格式,可以通过运行以下命令之一使用hexdumpod检查该文件:

 hexdump hello.o 
od -c hello.o

这是此阶段输出的一小部分摘录:

  0000000 457f 464c 0102 0001 0000 0000 0000 0000 
0000010 0001 003e 0001 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0138 0000 0000 0000
0000030 0000 0000 0040 0000 0000 0040 000d 000a
0000040 4855 e589 00bf 0000 e800 0000 0000 00b8
0000050 0000 5d00 48c3 6c65 6f6c 202c 6f48 626c
0000060 7265 6f74 216e 0000 4347 3a43 2820 6255
0000070 6e75 7574 3420 382e 342e 322d 6275 6e75
0000080 7574 7e31 3431 302e 2e34 2933 3420 382e
0000090 342e 0000 0000 0000 0014 0000 0000 0000
00000a0 7a01 0052 7801 0110 0c1b 0807 0190 0000
00000b0 001c 0000 001c 0000 0000 0000 0015 0000
00000c0 4100 100e 0286 0d43 5006 070c 0008 0000
00000d0 2e00 7973 746d 6261 2e00 7473 7472 6261
00000e0 2e00 6873 7473 7472 6261 2e00 6572 616c
00000f0 742e 7865 0074 642e 7461 0061 622e 7373
0000100 2e00 6f72 6164 6174 2e00 6f63 6d6d 6e65
0000110 0074 6e2e 746f 2e65 4e47 2d55 7473 6361
0000120 006b 722e 6c65 2e61 6865 665f 6172 656d
0000130 0000 0000 0000 0000 0000 0000 0000 0000
*
0000170 0000 0000 0000 0000 0020 0000 0001 0000
0000180 0006 0000 0000 0000 0000 0000 0000 0000
0000190 0040 0000 0000 0000 0015 0000 0000 0000
00001a0 0000 0000 0000 0000 0001 0000 0000 0000
00001b0 0000 0000 0000 0000 001b 0000 0004 0000
00001c0 0000 0000 0000 0000 0000 0000 0000 0000
00001d0 0598 0000 0000 0000 0030 0000 0000 0000
00001e0 000b 0000 0001 0000 0008 0000 0000 0000
00001f0 0018 0000 0000 0000 0026 0000 0001 0000
0000200 0003 0000 0000 0000 0000 0000 0000 0000
0000210 0055 0000 0000 0000 0000 0000 0000 0000
0000220 0000 0000 0000 0000 0001 0000 0000 0000
0000230 0000 0000 0000 0000 002c 0000 0008 0000
0000240 0003 0000 0000 0000 0000 0000 0000 0000
0000250 0055 0000 0000 0000 0000 0000 0000 0000

哇! 您不高兴编译器为我们完成了所有这些工作吗?

连结中

在汇编阶段生成的目标代码由处理器可以理解的机器指令组成,但是程序的某些部分混乱或丢失。 要生成可执行程序,必须重新排列现有片段,并填充缺少的片段。此过程称为链接。

链接器排列目标代码段,以便某些段中的函数可以成功调用其他段中的函数。 它还添加了包含程序使用的库函数指令的片段。 在我们的hello.c程序中,链接器将为puts函数添加目标代码。

该阶段的结果是最终的可执行程序。 当不带选项运行时, cc将将此文件命名为a.out 。 如果要将可执行程序命名为更适合您的程序的名称,请使用-o选项

  gcc -o hello_holberton hello.c