目录
1 程序的编译过程
1.1 编写源代码
1.2 预处理(Preprocessing)
1.3 编译(Compilation)
1.4 汇编(Assembly)
1.5 链接(Linking)
1.6 执行
2 编译过程的输入输出文件概览
3 断点及调试窗口设置
4 什么是调试
1 程序的编译过程
程序的编译过程是一个从人类可读的源代码转换为计算机可直接执行的机器代码的过程。这个过程大致可以分为以下几个步骤,以 C 语言程序(如 main.c )为例来说明:
1.1 编写源代码
程序员使用文本编辑器(如 Notepad++、Vim、Sublime Text 或专门的 IDE 如 CLion、Visual Studio、Visual Studio Code、Xcode、Eclipse 等)编写源代码,这些源代码文件通常具有特定的扩展名,如 .c 对于 C 语言。
1.2 预处理(Preprocessing)
编译器在编译之前,首先会进行预处理。这一步会处理源代码中的预处理指令,如包含头文件(#include)、宏定义(#define)、条件编译(#ifdef、#ifndef、#endif)等。预处理后,生成一个包含了所有必要信息的新文件,但通常这个步骤的输出文件对程序员不可见。
1.3 编译(Compilation)
经过预处理后的代码会被编译器进一步处理,即编译过程。编译器将源代码转换成汇编语言代码(Assembler code),这一步是源代码到机器码的中间步骤。汇编语言是一种低级语言,比机器码稍高级,但仍然是针对特定 CPU 架构的。
1.4 汇编(Assembly)
汇编器将汇编语言代码转换成机器码(Machine Code),即 0 和 1 的序列,这是 CPU 能直接执行的指令集。这一步生成的机器码是特定于目标平台(如 x86、ARM 等)的。
1.5 链接(Linking)
如果程序中使用了外部库或定义了多个源文件( .c 文件),则需要进行链接。链接器将多个目标文件(Object Files,即 .o 或 .obj文件,它们是编译和汇编过程的结果)以及所需的库合并成一个可执行文件(在 Windows 下通常是 .exe,在 Unix/Linux/Mac 下通常是没有扩展名的)。链接过程中还会解决程序中的函数调用、变量引用等依赖关系。
1.6 执行
生成的可执行文件可以被加载到内存中,由 CPU 执行。在操作系统中,这通常通过双击文件图标、在命令行中输入文件名并回车或使用操作系统的其他机制来完成。
2 编译过程的输入输出文件概览
步骤 | 输入文件(含后缀名) | 输出文件(含后缀名) |
---|---|---|
1. 预处理(Preprocessing) | hello.c(C语言源代码文件) | hello.i(预处理后的源代码文件,有时直接跳过此步骤的显式输出,但内部处理是存在的) |
注意:在某些编译环境中,预处理后的文件可能不会被显式地保存为 .i 文件,而是直接在内存中处理。但为了说明过程,这里假设它存在。 | ||
2. 编译(Compilation) | hello.i(预处理后的源代码文件,或直接从 hello.c 进入此步骤) | hello.s(汇编语言源代码文件) |
3. 汇编(Assembly) | hello.s(汇编语言源代码文件) | hello.o(目标代码文件,二进制形式) |
4. 链接(Linking) | hello.o(目标代码文件) + 其他目标代码文件/库文件 | hello(或 hello.exe,在 Windows 系统下)可执行文件(二进制形式) |
注意:
-
在某些编译环境中,特别是当使用集成开发环境(IDE)时,预处理后的文件(.i)和汇编语言源代码文件(.s)可能不会被显式地生成和保存。这些步骤通常在内部完成,并且用户只能看到最终的输出(即可执行文件)。
-
在 Windows 系统下,最终的可执行文件通常具有 .exe 扩展名,而在 Unix/Linux 系统下,则可能没有特定的扩展名,或者使用如 .out 的扩展名(这取决于具体的编译器和链接器设置)。
-
链接阶段还可能涉及生成其他类型的文件,如符号表、重定位信息等,但这些文件通常不是最终用户直接关心的内容。
-
上述过程是一个简化的描述,实际的编译过程可能更加复杂,包括优化、调试信息生成等多个子步骤。然而,从文件输入输出的角度来看,上述归纳已经涵盖了主要的内容。
在 Linux 终端中使用 gcc 编译 C 语言程序的不同方法:
1.偷懒法
gcc hello .c
默认生成 a.out
起不到见名知意的作用 不建议!
2.分步法
预处理:gcc -E hello.c -o hello.i
编译: gcc -S hello.i -o hello.s
汇编: gcc -C hello.s -o hello.o
链接: gcc hello.o -o hello
比较细致,但实际使用不建议,比较麻烦!
3.一步到位法
gcc hello.c -o hello
编译并生成对应的可执行程序,见名知意,建议使用!!!
通过这些命令也可以看出 C 语言程序的编译过程。
3 断点及调试窗口设置
程序在我们点击绿色三角形按钮时,一下子就运行到最后一步了,速度非常快,如果最终输出的结果和我们预期的不一样,我们如何查找代码的哪一步出了问题呢?
秘诀就是让它运行慢下来(类似于魔术慢放),一次只让程序运行一步,如何让程序运行慢下来,那就是断点调试!
如下图所示,在位置 1 处添加断点(断点位置代表从哪一步我们开始慢下来),点击位置 2 启动调试。
当点击虫子按钮后,调试就启动了,出现蓝色长条代表调试启动,如下图所示:
注意:蓝条所在行,表示此语句还未得到执行。
点击了上图的单步运行按钮(上图 2 指向的折弯箭头),得到如下图:
4 什么是调试
调试(Debugging)是软件开发过程中一个至关重要的环节,它涉及到查找、识别并修正程序中的错误(或称为“bug”)。这些错误可能导致程序无法正确执行其预期的功能,或者产生非预期的结果。
在调试过程中,程序员会仔细分析程序的运行过程,特别是那些可能导致问题的部分。由于程序在执行时会进行各种计算,这些计算会改变程序中变量的值,因此,监视这些变量的变化是调试过程中的一个重要手段。
变量监视窗口(或称为“调试窗口”、“观察窗口”等)是许多集成开发环境(IDE)和调试工具提供的一个功能,它允许程序员在程序运行时实时查看变量的值。当程序执行到某个特定点时,程序员可以暂停程序的执行(这通常通过设置断点来实现),然后查看此时程序中各个变量的值。如果发现某个变量的值不符合预期,那么很可能就是这里出了问题,也就是找到了一个 bug。