c程序编译过程四个阶段(windows上测试)

 
前言:


  gcc(windows 的mingw64)按照四步完成生成可执行文件。包括预处理、编译、汇编、链接)。

  gcc 按照 * .c、*.i、*.s、*.o、*.exe  各个阶段序生成各个阶段的相对应文件,也可跨步骤任几个阶段直接生成后阶段文件。

  mingw64的安装,这里就不写了。

一、程序编译过程四个阶段如下:

 1.、Preprocessing (预处理)
        第一个阶段编译的第一阶段称为预处理。在此阶段,以字符开头的行#被预处理器解释为预处理器命令。这些命令形成了一种简单的宏语言,具有自己的语法和语义。这种语言用于通过提供内联文件、定义宏和有条件地省略代码的功能来减少源代码中的重复。在解释命令之前,预处理器会进行一些初始处理。这包括连接续行(以 结尾的行\)和删除注释。 要打印预处理阶段的结果,请使用参数选项传递 -E

       1)展开宏,头文件,检查代码是否有误。 输出(生成)目标文件是 *.i 

 2、Compilation(编译)
       第二个阶段会把c代码翻译成汇编代码,根据指定的处理器架构(本机处理器为 intel(R) Core(TM) I5-6200U CPU @2.30GHZ , 64位操作系统)
       这些代码构成了一种中间的人类可读的汇编语言。此步骤的允许 C 代码包含内联汇编指令并允许使用不同的汇编程序。一些编译器还支持使用集成汇编器,在编译阶段直接生成机器码,避免了生成中间汇编指令和调用汇编器的开销。要保存编译阶段的结果,请使用参数选项 -S

       1)将 *.c  直接输出为目标文件.s,即汇编代码 *. s 
       2)将 * .i   输出为目标文件 *.s, 即汇编代码 *. s 

 3、Assembly(汇编) 
      第三个阶段,使用汇编器将汇编指令翻译成目标代码。输出包含要由所在cpu处理器运行的实际指令。
     要保存结果的话,请在参数选项中传递  -c 
      1) 从源文件代码 *.c 直接输出为目标文件 *.o。 即机器代码*.o
      2) 从 *.i   输出为目标文件 *.o。 即机器代码 *.o
      3) 从汇编代码.s 输出为目标文件 *.o。 即机器代码*.o

 4、Linking(链接)
      在汇编阶段生成的目标代码由处理器理解的机器指令组成,但程序的某些部分出现了乱序或丢失。要生成可执行程序,必须重新排列现有的部分并填补缺失的部分。这个过程称为链接。链接器将安排目标代码片段,以便某些片段中的函数可以成功调用其他片段中的函数。它还将添加包含程序使用的库函数指令的部分

    作用:将机器代码.o和库文件链接成一个可执行程序(文件)

 

二、常用命令行选项(包括了编译的整个生命周期)

参数阶段说明英文
-c 编译和汇编 只编译到汇编阶段不链接,输出*.o文件 https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#Overall-Options

Compile or assemble the source files, but do not link.
 编译或汇编源文件,但是不会Link(链接)
The linking stage simply is not done.
仅仅链接阶段没有完成
The ultimate output is in the form of an object file for each source file.
最终输出为每个源文件的对象文件形式

By default, the object file name for a source file is made by replacing
the suffix ‘.c’, ‘.i’, ‘.s’, etc., with ‘.o’.

默认情况下,源文件的对象文件名是通过’.o’替换后缀'.c’,”.i','.s’等。

Unrecognized input files, not requiring compilation or assembly, are ignored.
忽略不需要编译或汇编的无法识别的输入文件

举例:
C:\Program Files\Go\src\democgo\pointerfunc\std>gcc main.c -c 

C:\Program Files\Go\src\democgo\pointerfunc\std

2022-06-06 19:24 100 main.c
2022-06-06 20:22 914 main.o
2 个文件

这时只需要
C:\Program Files\Go\src\democgo\pointerfunc\std>gcc main.o -o main.exe

-S(大写s) 汇编 从*.c文件或*.i文件,输出汇编文件*.s  

Stop after the stage of compilation proper; do not assemble.
在适当的编译阶段后停止;不要汇编。(即不生成.o)
The output is in the form of an assembler code file for each non-assembler input file specified.
对于指定的每个非汇编程序输入文件,输出以汇编程序代码文件的形式。

By default, the assembler file name for a source file is made by replacing the suffix
.c’, ‘.i’, etc., with ‘.s’. Input files that don’t require compilation are ignored.

默认情况下,源文件的汇编程序文件名是通过 '.s' 替换后缀来实现的'.c','.i' 等。忽略不需要编译的输入文件。

 -C(大写c) 预处理  

Do not discard comments. All comments are passed through to the output file,
不要丢弃注释。所有注释都传递到输出文件
except for comments in processed directives, which are deleted along with the directive.
已处理指令中的注释除外,这些注释随指令一起删除。
You should be prepared for side effects when using -C
使用-C时,你应该做好副作用的准备;

it causes the preprocessor to treat comments as tokens in their own right.
它使预处理器将注释本身视为标记
For example, comments appearing at the start of
what would be a directive line have the effect of turning that line into
an ordinary source line,  since the first token on the line is no longer a ‘#’.
举例:

#include <stdio.h>
int main(){
  for(int i=0;i<10;i++){ 
     printf("i=%d ",i);
  }
   //这是一个注释
}

C:\Program Files\Go\src\democgo\pointerfunc\std>gcc main.c -E -o main.i -C

C:\Program Files\Go\src\democgo\pointerfunc\std>gcc main.c -E -o main.i
查看main.i ,加了-C选项编译输出的main.i 文件会看到我们加的注释。

小结预处理过程:

(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。

-E(大写e) 预处理  输出*.i文件  
Stop after the preprocessing stage; 
预处理阶段结束后停止.
do not run the compiler proper(cc1和cc1plus,他们被称作compiler proper,负责真正的编译工作). 
不会运行c11和cc1plus
The output is in the form of preprocessed source code, 
which is sent to the standard output. Input files that don’t require preprocessing are ignored.
举例:
C:\Program Files\Go\src\democgo\pointerfunc\std>gcc main.c -E -o main.i
-g   在可执行程序里包含了调试信息,可用 gdb 调试  

https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html#Debugging-Options

-o   把输出文件内容输出到指定文件中 所有阶段都能使用该选项,把目标文件输出到指定文件中

Place the primary output in file file. This applies to whatever sort of output is being produced,
whether it be an executable file, an object file, an assembler file or preprocessed C code.

If -o is not specified, the default is to put an executable file in a.out,
如果-o没有制定,linux默认是输出为a.out,windows默认是a.exe
the object file for source.suffix in source.o,
its assembler file in source.s, a precompiled header file in source.suffix.gch,
and all preprocessed C source on standard output.

Though -o names only the primary output, it also affects the naming of auxiliary and dump outputs.
See the examples below. Unless overridden,

虽然-o只命名主输出,但它也会影响辅助输出和转储输出的命名。请参见下面的示例。除非被覆盖,

both auxiliary outputs and dump outputs are placed in the same directory as the primary output.
In auxiliary outputs, the suffix of the input file is replaced with that of the auxiliary output file type;

辅助输出和转储输出与主输出放在同一目录中。

在辅助输出中,输入文件的后缀替换为辅助输出文件类型的后缀;

in dump outputs, the suffix of the dump file is appended to the input file suffix.
在转储输出中,转储文件的后缀将附加到输入文件后缀。
In compilation commands,
在编译命令中
the base name of both auxiliary and dump outputs is that of the primary output;
辅助输出和转储输出的基本名称都是主输出的基本名称;
in compile and link commands,
在编译和链接命令中
the primary output name, minus the executable suffix,
主输出名称,减去可执行文件后缀,
is combined with the input file name.
与输入文件名组合
If both share the same base name, disregarding the suffix,
the result of the combination is that base name, otherwise,
they are concatenated, separated by a dash.

-static 链接 链接静态链接库 https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#Link-Options 
On systems that support dynamic linking, this overrides -pie and prevents linking
with the shared libraries.
On other systems, this option has no effect.
-s 链接   https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#Link-Options
Remove all symbol table and relocation information from the executable.
-shared     Produce a shared object which can then be linked with other objects to form an executable.
Not all systems support this option. For predictable results,
you must also specify the same set of options used for compilation
(-fpic-fPIC, or model suboptions) when you specify this linker option.1
-library 链接 链接名为library的链接库  
-std     -std=c89 遵循C89标准
-std=c99 遵循C99标准
-std=traditional  使用原始C
-pedantic     关闭所有的GNU扩展


以下几个选项注意:
-c 、 
-S 、-E
If any of these options is used, then the linker(链接器) is not run, and object file names should not be used as arguments. See Overall Options.


三、警告信息

选项 阶段 解释
-Wall   显示所有常用的编译警告信息
-W   显示更多的常用编译警告,如:变量未使用、一些逻辑错误
-Wconversion   警告隐式类型转换
-Wint-conversion    
-Wshadow   警告影子变量(在代码块中再次声明已声明的变量)
-Wcast-qual    警告指针修改了变量的修饰符。如:指针修改const变量
-Wcast-qual    警告修改const字符串
-Wtraditional   警告ANSI编译器与传统C编译器有不同的解释
-Werror   即使只有警告信息,也不编译。(gcc默认:若只有警告信息,则进行编译,若有错误信
     

 

四、具体编译示例 

源码文件 main.c

 

#include <stdio.h> int main() { printf("Hello Wolrd!"); return 0; }

一步到位,直接输出为可执行程序

  gcc main.c 
//默认生成a.ext

  显示中间执行的编译信息:

 gcc  -v main.c -o main.exe


下面分析四个阶段

1) 预处理阶段
  (1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif 等。
  (2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
  (3) 删除所有注释“//”和“/* */”。
  (4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
  (5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。
  下面是从*.c文件输出预处理后的文件*.i 的相关命令

 C:\Program Files\Go\src\democgo\pointerfunc\std> gcc -E  main.c -o main.i

  //保留源文件中的注释 -C

  C:\Program Files\Go\src\democgo\pointerfunc\std>gcc -E -C main.c -o main.i

  main.i 文件内容如下:

# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.c"
# 1 "E:/mingw64/x86_64-w64-mingw32/include/stdio.h" 1 3
# 9 "E:/mingw64/x86_64-w64-mingw32/include/stdio.h" 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/crtdefs.h" 1 3
# 10 "E:/mingw64/x86_64-w64-mingw32/include/crtdefs.h" 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 1 3
# 12 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/_mingw_mac.h" 1 3
# 98 "E:/mingw64/x86_64-w64-mingw32/include/_mingw_mac.h" 3
# 107 "E:/mingw64/x86_64-w64-mingw32/include/_mingw_mac.h" 3
# 13 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/_mingw_secapi.h" 1 3
# 14 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 282 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/vadefs.h" 1 3
# 9 "E:/mingw64/x86_64-w64-mingw32/include/vadefs.h" 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 1 3
# 578 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/sdks/_mingw_directx.h" 1 3
# 579 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 1 "E:/mingw64/x86_64-w64-mingw32/include/sdks/_mingw_ddk.h" 1 3
# 580 "E:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 10 "E:/mingw64/x86_64-w64-mingw32/include/vadefs.h" 2 3

.......

2) 编译阶段
   编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。

   下面是从*.c文件或i文件,输出汇编文件*.s的相关命令

 C:\Program Files\Go\src\democgo\pointerfunc\std> gcc -S main.c -o main.s  //从.c文件输出.s 阶段

  C:\Program Files\Go\src\democgo\pointerfunc\std>gcc -S main.i -o main.s  //从.i 文件输出到.s阶段

   main.s

    //本机x86架构,利用的windows gcc来生成的x86汇编程序如下:
.file "main.c" .text .def __main; .scl 2; .type 32; .endef .section .rdata,"dr" .LC0: .ascii "Hello Wolrd!\0" .text .globl main .def main; .scl 2; .type 32; .endef .seh_proc main main: pushq %rbp .seh_pushreg %rbp movq %rsp, %rbp .seh_setframe %rbp, 0 subq $32, %rsp .seh_stackalloc 32 .seh_endprologue call __main leaq .LC0(%rip), %rcx call printf movl $0, %eax addq $32, %rsp popq %rbp ret .seh_endproc .ident "GCC: (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 7.3.0" .def printf; .scl 2; .type 32; .endef

 

3) 汇编阶段(可跨阶段输出)

 (1)汇编过程是对汇编代码进行处理,生成x86处理器能识别的指令,保存在后缀为.o的目标文件中。
 (2)调用汇编器工具 as根据汇编指令和处理器指令的对照表一翻译。
 (3)当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标文件后,才能进入下一步的链接工作。

  下面是从*.c文件、*.i文件或*.s文件,输出目标文件*.o的相关命令

 1.从.c文件直接输出.o目标文件 (-c 保存结果)
  C:\Program Files\Go\src\democgo\pointerfunc\std>gcc -c main.c -o main.o

  2.从.i文件直接输出.o目标文件
  C:\Program Files\Go\src\democgo\pointerfunc\std>gcc -c main.i -o main.o
  3.从.s文件直接输出.o目标文件

  C:\Program Files\Go\src\democgo\pointerfunc\std>gcc -c main.s -o main.o

4) 链接阶段(可跨阶段输出)
   从*.c文件、*.i文件、*.s文件或*.o文件,生成目标文件exe

  1.从.c 直接输出 .exe
  C:\Program Files\Go\src\democgo\pointerfunc\std>gcc main.c -o main.exe

 C:\Program Files\Go\src\democgo\pointerfunc\std>gcc .i  -o main.exe

C:\Program Files\Go\src\democgo\pointerfunc\std> gcc main.s -o main.exe

 C:\Program Files\Go\src\democgo\pointerfunc\std>gcc main.o -o main.exe

posted @ 2022-06-06 19:51  jinzi  阅读(65)  评论(0编辑  收藏  举报