已关闭,此问题需要details or clarity。它目前不接受回答。
**想改善这个问题吗?**通过editing this post添加详细信息并澄清问题。
上个月关门了。
这篇文章上个月被编辑并提交审查,未能重新打开帖子:
原始关闭原因未解决
Improve this question
当我学习C编程语言的编译过程步骤时,我试图根据输入/输出来分解每个步骤(预处理,编译,组装和链接),以便掌握幕后发生的事情。
不幸的是,由于我对处理gcc
编译器的选项知之甚少,我未能做到这一点(请让我们坚持使用gcc
,因为它是我的Linux机器上可用的编译器)。我甚至管理了预处理步骤的输出,但其他步骤都失败了。
我真的很感激在这个意义上的任何帮助。
我不想对每一个步骤都做全面的解释。它已经在互联网上可用。我想看看他们的输入/输出,以及我需要传递哪个gcc
选项才能完成这项工作。
2条答案
按热度按时间nzkunb0c1#
让我们采用简单的“hello world!“C程序名为
main.c
预处理器
*.c
文件。\
):反斜杠\
用于行拼接,允许您将长代码行拆分为多行以提高可读性,并连接成一行。#include <stdio.h>
在程序中可用,则预处理器解释该指令并将该指令替换为/usr/include/stdio.h
文件的内容。#define
指令,它定义了一个宏。它们可以是常量宏,例如#define BUFFER_SIZE 1024
,也可以是类似函数的宏,例如#define MAX(a, b) ((a) > (b) ? (a) : (b))
。#ifdef DEBUG printf("Debugging is enabled.\n"); #endif
。其他条件编译是#ifndef
,#if
,#elif
,#else
。预处理器评估这些条件,并确定是否应将所包含的代码包含在预处理的输出文件中。C
预处理器通知C
编译器源代码中每个标记的来源位置。C语言中的标记可以定义为C编程语言中对编译器有意义的最小单个元素。它是C程序的基本组成部分。它们可以是关键字(double
,if
,while
,return
,.);标识符(变量和函数名); Constants(const int c_var = 20;
);字符串.特殊符号([]
,()
,{}
,,
,#
,.)运算符(一元、二元和三元运算符);*.i
或预处理文件。这个预处理的文件就是所谓的translation unit(或者更随便地说是编译单元),它是C
或C++
编译器的最终输入,编译器将从中生成目标文件。我们的静态函数只在这个文件中可见。-E
在预处理阶段后停止;不要正确运行编译器。输出是以预处理的源代码的形式,它被发送到标准输出(或发送到带有-o
选项的文件)。也可以直接使用
C
(C
预处理器)获取预处理后的文件:请注意,这两个文件完全相同
输出是
编译
*.i
文件。MOV
表示“移动”,ADD
表示“添加”)来表示CPU操作(所谓的操作码),从而使代码可读。*.s
或*.asm
文件。-S
:编译完成后停止;严格地说,这是编译过程中最关键的部分,除了
gcc
编译器之外,没有人可以完成。当我们使用gcc
直接生成可执行文件时(不分解所有步骤),幕后发生的事情是:gcc
将所有其他编译步骤(预处理、汇编和链接)分别委托给cpp
、as
和ld
,只执行编译步骤。通过这样做,我们C
开发人员可以不必担心调用这些工具,因为gcc
已经完成了。因此,我们可以使用一个独特的命令接口(gcc
)来执行所有的编译过程。输出是
组装
此阶段的输入文件是
*.asm
或*.s
文件。如果您使用
gcc
组装文件,则必须将扩展名命名为.s
。否则,gcc
的-c
选项将尝试链接文件,而不是组装文件。每个汇编指令通常代表一条机器码指令,它是一组
0
和1
。这个二进制序列的一部分是操作码,而其余部分通常是变量或数值的内存地址。虽然汇编语言是一种独立于CPU架构的编程语言,但它所代表的机器码是特定于目标CPU的。换句话说,机器代码是高度平台特定的,这意味着它是针对特定的计算机体系结构和操作系统定制的。一种类型的CPU的机器指令可能不会在不修改的情况下在不同的CPU架构上运行。将汇编代码转换为机器代码的工具称为 * 汇编程序 *,其任务称为 * 汇编 *。
在低级微控制器编程中,汇编程序将汇编代码转换为二进制序列(通常以十六进制表示)并将其嵌入微控制器。微处理器现在可以运行了。
另一方面,在
C
/C++
中,汇编程序将汇编代码转换为.o
中间文件,称为目标文件。此中间表示包含机器级代码,即表示代码中定义的函数和变量的非人类可读指令。不过,.o
文件并不是最终的可执行代码。也就是说,它包含机器可读的指令,但不能直接由操作系统执行。这是因为缺少了操作系统可执行文件所需的一些信息,例如函数和变量的内存地址,这些信息在链接阶段解决。输出文件为
*.o
或*.obj
文件。或者,您可以运行
其中
as
是GNU汇编程序。同样,您可以检查这两种方法是否产生相同的输出:链接
*.o
文件。C
标准库通常与gcc
编译器捆绑在一起。当您安装gcc
或任何其他C
编译器时,它包含编译和链接C
程序所需的必要标准库。这些库是编译器发行版的组成部分。因此,它们并不作为一个单独的共享库文件存在于您的系统中,您不会通过查找.a
(静态库)文件来找到它。共享库(.so
文件)在编译时不链接,而仅在运行时链接。C
标准库之外的库,必须手动链接。其中
myprogram
是可执行文件。或者,您可以直接使用ld
命令,但猜猜看会发生什么?虽然这不是最关键的部分,手动将目标文件链接到可执行文件是复杂得要命。在这一点上,您实际上是在玩弄比特,以完美地匹配您的机器的硬件和操作系统规范。你必须了解的东西,如计算机组织和体系结构,endianness,文件类型,动态链接器/加载器路径,BuildID,操作系统兼容性,幻数,剥离和非剥离可执行文件,等等。幸运的是,gcc
already knows all these stuffs and wonderfully does this hard work for us通过内部调用ld
并设置所有必要的选项。我想ld
选项超出了这个问题的范围。同样重要的是,在实践中,强烈建议不要直接使用ld
。您应该使用gcc
(或其他编译器)。最后
gab6jxml2#
在实际编译过程中传递的信息要多得多,因为它比仅仅预处理编译和链接更复杂。编译是一个多阶段的过程,Godbolt给了你一个简单的机会来看看那里发生了什么(也可以看到中间结果):
https://godbolt.org/z/b6vdzb8d9