gcc编译过程以及命令格式

文章目录

1. 编译过程

预处理(Preprocessing)

编译(Compilation)

汇编(Assembly)

链接(Linking)

2. 源文件种类

3. gcc命令

3.1 GCC命令格式

3.2 预处理选项(-E) 

3.3 编译选项(-S)

3.4 汇编选项(-c)

3.5 链接步骤(Linking)

3.6 运行可执行文件


1. 编译过程

GCC(GNU Compiler Collection)编译器是一个强大的编译工具链,能够将C/C++源代码转换为可执行的机器码。编译过程分为四个主要步骤:预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)。

预处理(Preprocessing)

在C/C++编译过程中,预处理是第一步。预处理器的主要任务是处理以#开头的命令,包括:

  • #include:插入头文件。预处理器将头文件的内容插入到包含它的源文件中。
  • #define:定义宏。预处理器将宏替换为其定义的值或代码片段。
  • #if / #ifdef / #endif:条件编译。根据条件选择性地编译代码。

预处理器将这些命令处理完后,生成一个带有扩展名为.i的中间文件,该文件包含展开后的完整源代码,准备进入下一步的编译阶段。

编译(Compilation)

在编译阶段,编译器(如gcc)将预处理后的源代码(.i文件)进行词法分析和语法分析,生成汇编代码。这个阶段的主要任务是:

  • 词法分析:将源代码分解成有意义的词法单元(tokens)。
  • 语法分析:根据语法规则检查词法单元的结构。

编译器会根据源代码的逻辑生成对应的汇编代码,扩展名为.s

汇编(Assembly)

在汇编阶段,汇编器(如as)将汇编代码(.s文件)转换成目标代码(机器代码),生成扩展名为.o的目标文件。这个阶段的主要任务是:

  • 指令转换:将汇编指令转换为机器码。
  • 符号解析:解析汇编代码中的符号并生成对应的机器地址。

目标文件包含了可执行代码的机器指令,但还不能单独运行,需要经过链接阶段生成最终的可执行文件。

链接(Linking)

在链接阶段,链接器(如ld)将一个或多个目标文件(.o文件)合并,并解析所有外部符号引用,生成最终的可执行文件。这个阶段的主要任务是:

  • 符号解析:解析和处理目标文件中的符号引用,确保每个符号都有定义。
  • 地址重定位:调整目标文件中的地址引用,使得它们在最终的可执行文件中正确指向。

链接器将所有目标文件中的代码和数据整合到一个可执行文件中,完成整个编译过程。

整个gcc编译过程包括以下四个步骤:

  1. 预处理:处理#开头的预处理命令,生成.i文件。
  2. 编译:对预处理后的源代码进行词法和语法分析,生成.s汇编代码文件。
  3. 汇编:对汇编代码进行优化,生成.o目标代码文件。
  4. 链接:解析目标代码中的外部引用,将多个目标代码文件连接为一个可执行文件。

通过这些步骤,源代码被逐步转换为可执行的机器代码,最终生成可以在目标硬件上运行的可执行文件。

2. 源文件种类

编译器利用上面4个步骤中的一个或多个来处理输入文件,源文件的后缀名表示源文件所用的语言,后缀名控制着编译器的缺省动作。

GCC编译器可以处理多种类型的源文件,每种源文件都有特定的后缀名,这些后缀名表示文件所用的编程语言以及编译器将如何处理它们。以下是各种后缀名及其对应的语言和处理步骤:

后缀名 语言种类 后期操作
.c C源程序 预处理、编译、汇编
.C C++源程序 预处理、编译、汇编
.cc C++源程序 预处理、编译、汇编
.cxx C++源程序 预处理、编译、汇编
.m Objective-C源程序 预处理、编译、汇编
.mm Objective-C++源程序 预处理、编译、汇编
.i 预处理后的C文件 编译、汇编
.ii 预处理后的C++文件 编译、汇编
.s 汇编语言源程序 汇编
.S 汇编语言源程序 预处理、汇编
.h 预处理器文件 通常不会单独出现

连接器处理其他后缀名文件

编译完成后,生成的中间文件或目标文件会被传递给链接器进行最终的链接操作,通常包括以下类型的文件:

  • .o:目标文件(Object file)
  • .a:归档库文件(Archive file)

这些文件包含编译后的代码和数据,链接器将它们组合起来,生成最终的可执行文件。

编译过程中的选项

在编译过程中,除非使用了 -c-S-E 选项(或者编译错误阻止了整个过程),否则最后的步骤总是链接。在链接阶段中,所有对应于源程序的 .o 文件、-l 选项指定的库文件、无法识别的文件名(包括指定的 .o 目标文件和 .a 库文件)按照命令行中的顺序传递给链接器。

以下是这些选项的具体功能:

  • -c:仅编译到目标文件,不进行链接。
  • -S:将源代码编译到汇编代码,生成 .s 文件,不进行汇编和链接。
  • -E:仅进行预处理,不进行编译、汇编和链接。

3. gcc命令

3.1 GCC命令格式
gcc [选项] 文件列表
  • 选项:用于定制GCC的行为,例如控制编译过程的各个阶段、优化级别、调试信息等。
  • 文件列表:指定要编译的源文件。

GCC命令用于实现C程序编译的全过程。文件列表参数指定了GCC的输入文件,选项用于定制GCC的行为。GCC根据选项的规则将输入文件编译生成适当的输出文件。GCC的选项非常多,常用的选项大致可以分为几类。

示例代码

假设一个C语言源文件 main.c

// main.c
#include <stdio.h>

#define HUNDRED 100

int main() {
    printf("%d abc\n", HUNDRED);
    return 0;
}

 

3.2 预处理选项(-E) 

在GCC编译过程中,预处理(preprocessing)是非常重要的一步。预处理是指在正式编译代码之前,先对源代码中的一些指令进行处理,生成中间文件。这些指令包括头文件的包含(#include)、宏定义(#define)、条件编译指令(#if#ifdef#endif)等。通过预处理,所有的宏定义都会被展开,头文件的内容也会被插入到源文件中。

使用以下命令来进行预处理:

gcc -E main.c -o main.i
  • -E:表示只进行预处理,不进行编译、汇编和链接。
  • main.c:源文件。
  • -o main.i:将预处理后的内容输出到 main.i 文件中。

执行上述命令后,可以得到 main.i 文件的内容。这里仅截取部分关键代码进行解释:

extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 942 "/usr/include/stdio.h" 3 4
# 2 "main.c" 2
# 5 "main.c"

int main() {
    printf("%d ask\n", 100);
    return 0;
}
  • 头文件展开

    • 预处理器将 #include <stdio.h> 指令展开,插入了标准输入输出库 stdio.h 的内容。
  • 宏定义展开

    • 预处理器将所有的宏定义展开,比如 #define HUNDRED 100 被替换为相应的值。在 printf 函数中,原来的 HUNDRED 被替换为 100

预处理阶段的任务是将所有预处理指令(以 # 开头的命令)进行处理,将头文件的内容插入到包含它们的源文件中,将宏定义展开,根据条件编译指令选择性的保留或删除代码。预处理的结果是一个纯C代码的中间文件(.i 文件),它不包含任何预处理指令,方便后续的编译阶段进行处理。

通过预处理,我们可以更好地理解代码的结构和内容,也可以用来调试和优化代码。这一步非常重要,因为它是编译器生成目标代码的基础。如果预处理阶段出现问题,后续的编译、汇编和链接阶段也无法顺利进行。

3.3 编译选项(-S)

在GCC编译过程中,编译(compilation)是将经过预处理的C/C++代码(如 .i 文件)翻译成汇编代码( .s 文件)。汇编代码是机器码的文本表示形式,每一条汇编指令对应一条机器码指令。通过编译生成汇编代码,可以帮助我们理解编译器生成的指令以及优化代码的效果。

使用以下命令来进行编译:

gcc -S main.c -o main.s
  • -S:表示只进行编译,不进行汇编和链接。
  • main.c:源文件。
  • -o main.s:将编译后的汇编代码输出到 main.s 文件中。

执行上述命令后,我们可以看到 main.s 文件的内容。以下是生成的汇编代码:

.file   "main.c"
.text
.section    .rodata
.LC0:
    .string "%d ask\n"
.text
.globl  main
.type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $100, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
    .section    .note.GNU-stack,"",@progbits
  1. 文件头信息

    • .file "main.c":指明了源文件名。
    • .ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0":GCC编译器的版本信息。
  2. 文本段

    • .text:定义代码段,存放程序的指令。
    • .section .rodata:定义只读数据段,存放常量和字符串。
    • .LC0::标识符,表示一个字符串常量的位置。
    • .string "%d ask\n":定义字符串常量 %d ask\n
  3. 全局变量与函数

    • .globl main:声明 main 函数为全局符号。
    • .type main, @function:定义 main 为函数类型。
    • main::函数入口。
  4. 汇编指令

    • .cfi_startproc.cfi_endproc:用于调试信息的指令,标记函数的开始和结束。
    • pushq %rbppopq %rbp:保存和恢复基指针寄存器。
    • movq %rsp, %rbp:将栈指针寄存器的值移动到基指针寄存器。
    • movl $100, %esi:将立即数 100 移动到 esi 寄存器。
    • leaq .LC0(%rip), %rdi:将字符串常量地址加载到 rdi 寄存器。
    • movl $0, %eax:将 0 移动到 eax 寄存器。
    • call printf@PLT:调用 printf 函数。
    • ret:函数返回。
  5. 其他信息

    • .size main, .-main:定义 main 函数的大小。

通过生成汇编代码,我们可以清晰地看到C语言代码被翻译成汇编语言的过程。汇编代码提供了更底层的视角,帮助我们理解程序的运行机制和性能优化。

3.4 汇编选项(-c)

在GCC编译过程中,汇编(assembly)是将经过编译生成的汇编代码( .s 文件)翻译成符合一定格式的机器码。在Linux系统上,这些机器码通常会被打包成目标文件(Object file,OBJ文件),例如 .o 文件。目标文件包含了机器代码和数据,准备链接成可执行程序。

使用以下命令来进行汇编:

gcc -c main.c -o main.o
  • -c:表示只进行编译和汇编,不进行链接。
  • main.c:源文件。
  • -o main.o:将编译和汇编后的目标文件输出到 main.o 文件中。

执行上述命令后生成了 main.o 文件。目标文件 main.o 包含了机器代码,可以被链接器(linker)用来生成最终的可执行文件。

通过汇编选项 -c,我们只进行前两个步骤,不进行最后的链接步骤。因此,输出的 main.o 文件是一个中间文件,包含了机器代码和数据,但还不能独立运行。

3.5 链接步骤(Linking)

链接(linking)是编译过程的最后一步,它将多个目标文件( .o 文件)和库文件结合在一起,生成一个可执行的二进制文件。链接的主要任务是解析符号引用,合并代码和数据段,并处理重定位信息。

假设我们有多个目标文件,需要链接成一个可执行文件。使用以下命令:

gcc main.o -o main
  • main.o:目标文件,包含编译和汇编后的机器代码。
  • -o main:指定输出文件的名称,这里生成可执行文件 main

如果有多个目标文件:

gcc file1.o file2.o -o program
  • file1.ofile2.o:多个目标文件。
  • -o program:生成的可执行文件名称为 program

链接过程

  1. 符号解析:链接器会检查所有目标文件和库文件中的符号(变量和函数)引用,确保每个符号都有定义。
  2. 重定位:链接器根据符号表和重定位信息,修正代码中的地址引用,使它们指向正确的内存位置。
  3. 合并段:将不同目标文件的代码段、数据段和其他段合并到一起,形成一个完整的可执行文件。
  4. 库文件处理:链接器会搜索并包含必要的库文件(例如标准库),以满足所有外部符号引用。

通过链接步骤,将独立编译的目标文件和库文件结合起来,生成一个可以在目标平台上运行的完整程序。这一步至关重要,确保所有的符号引用都能被正确解析,所有的代码和数据都能正确定位和访问。

3.6 运行可执行文件
$ ls
main.c  main
$ ./main
100 abc
  • 使用 ls 命令列出当前目录中的文件,可以看到 main.c 源文件和生成的 main 可执行文件。
  • 运行生成的 main 文件,可以看到输出 abc。

如果不使用 -o 选项,编译器会生成默认的输出文件名。各编译阶段有各自的默认文件名。可执行文件的默认名为 a.out。使用示例如下:

$ gcc main.c
$ ls
a.out  main.c
$ ./a.out
100 abc
  • 使用 ls 命令列出当前目录中的文件,可以看到 main.c 源文件和生成的 a.out 可执行文件。
  • 运行生成的 a.out 文件,可以看到输出 100 ask
  1. 使用 -o 选项

    • 当我们编译一个 C 源文件时,可以通过 -o 选项指定生成的输出文件名。
    • 示例中,gcc main.c -o main 命令告诉编译器生成一个名为 main 的可执行文件。
    • 使用 ls 命令,我们可以看到源文件 main.c 和生成的可执行文件 main
    • 执行生成的 main 文件后,程序输出 abc。
  2. 默认输出文件名

    • 如果不使用 -o 选项,编译器会使用默认的输出文件名 a.out
    • 示例中,gcc main.c 命令告诉编译器编译 main.c 文件并生成一个默认名为 a.out 的可执行文件。
    • 使用 ls 命令,我们可以看到源文件 main.c 和生成的默认可执行文件 a.out
    • 执行生成的 a.out 文件后,程序输出 abc。

通过这两个示例,可以看到使用 -o 选项和不使用 -o 选项时编译器的行为差异。指定输出文件名可以帮助我们更好地管理生成的可执行文件,避免默认文件名 a.out 带来的混淆。

相关推荐

  1. GUN编译器gcc/g++)- 编译过程

    2024-07-17 22:28:02       45 阅读
  2. Linux Ubuntu 24.04 C语言gcc编译过程详解

    2024-07-17 22:28:02       25 阅读
  3. GCC 安装编译linux

    2024-07-17 22:28:02       61 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 22:28:02       70 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 22:28:02       74 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 22:28:02       62 阅读
  4. Python语言-面向对象

    2024-07-17 22:28:02       72 阅读

热门阅读

  1. 2024/6/26 Stream流

    2024-07-17 22:28:02       22 阅读
  2. 常用的设计模式有哪些

    2024-07-17 22:28:02       23 阅读
  3. 2024.07.10校招 实习 内推 面经

    2024-07-17 22:28:02       21 阅读
  4. 常用网络术语或概念

    2024-07-17 22:28:02       20 阅读
  5. 作为ToB市场总监的你 被老板质疑过花销太大吗?

    2024-07-17 22:28:02       20 阅读
  6. 有效应对服务器遭受CC攻击的策略与实践

    2024-07-17 22:28:02       23 阅读
  7. 2024华为云数据库斯享会,扎根技术,向深向实

    2024-07-17 22:28:02       23 阅读
  8. ios CCSqlite3.m

    2024-07-17 22:28:02       24 阅读
  9. OpenLayers学习笔记-点位聚合

    2024-07-17 22:28:02       23 阅读
  10. c++邻接矩阵

    2024-07-17 22:28:02       24 阅读
  11. Mojo 编程语言简介

    2024-07-17 22:28:02       24 阅读