C/C++工程项目
C/C++工程项目
通常一个项目难以通过单个文件完成,使用多个文件进行代码编写可以帮助组织起复杂的代码结构,组织一个工程项目是一个困难的过程。
编译过程
首先回顾C语言中可执行文件的编译过程,
预处理
源代码首先通过预处理操作,将那些预处理指令(以#开头的指令)进行文本替换,并将注释去掉,最终生成一个不包含预处理指令的代码文件(#pragma将会保留),这个文件在Windows上是一个.i文件。
编译
随后将调用编译器对.i文件进行编译,将代码转换为汇编指令,该文件通常是.s后缀,注意编译的对象编译单元,即那些经过预处理后的.c/.cpp文件,而头文件是不是一个编译单元。
汇编
汇编指令随后会由汇编器(而不是编译器)进行汇编,转换为二进制(/十六进制)的机器指令,生成.o(.obj)文件,汇编的过程和目标机器的指令集和操作系统有关。
链接
此时生成的机器指令还包括一些未知的函数,也就是写代码时引用的那些库函数,这些函数的具体执行过程存放在链接库中,链接器将会把链接库和和汇编成的.o文件进行链接,生成可执行文件,链接的类型分为了静态链接和动态链接。
工程分类与链接
C/C++工程大致可以分为两类:应用程序和链接库
应用程序是那些可以直接运行的软件,比如初学C语言时用到的控制台应用程序,在Windows平台中是那些.exe文件。
链接库是那些封装起来的,提供给应用程序使用的东西,比如.lib,.dll文件,在编译过程中将会链接到应用程序上,或静态链接或动态链接。
大部分工程的目的是编写一个应用程序,其编译过程中的许多部分在C语言学习中会比较熟悉,而链接的部分很少接触到,接下来将会给出一个链接的例子。
在最简单的HelloWorld程序中使用到了printf()函数,这个函数需要引用标准库stdio.h,在Visual Studio中可以右键查找该函数的定义,此时会找到这串代码
而printf()函数的实现却没有,因为其实现被放到了链接库ucrtbased.dll(windows平台)中,在Visual Studio中运行如下代码
1 |
|
在输出窗口中选择输出来源为 调试 将会出现如下提示
其中就有ucrtbased.dll被加载到exe程序中。
这是exe程序动态链接到了ucrtbased.dll文件,如果修改项目配置,在Visual Studio中选择解决方案右键进入属性,在\配置属性\链接器\输入中找到“忽略所有默认库”,选择为是(/NODEFAULTLIB),此时模拟了链接器未工作的情况,然后重新生成,将会输出以下链接错误
此时最简单的HelloWorld程序也无法生成成功。
工程配置
对于一个工程,以下配置几乎是必须的(名称为Visual Studio中的说法):
目标CPU架构
-
x86
-
x64
-
arm
包含目录
即头文件目录,在Visual Studio中分为了包含目录,外部包含目录,附加包含目录,含义类似,第三方库通常会包含一些头文件,使用这个以便于在项目中#include头文件
库文件目录
即链接库的目录,大部分第三方库除了头文件还需要配置lib文件目录才可以正常使用。
附加依赖项
链接库的名字,Visual Studio将会在库文件目录中自动查找这些文件。
预处理宏
编译开始时为项目添加的全局宏,如Visual Studio自动生成的项目中,会在Debug模式,生成应用程序时,默认添加_DEBUG,_CONSOLE两个宏,在Release模式中添加NDEBUG,_CONSOLE两个宏,在解决方案属性中\配置属性\C/C++\预处理器 中可以找到,结合#ifdef可以提供一些特定功能。
编译选项
提供一些编译功能,如编译优化,调试运行等。
工程调试
任何人编写工程代码都不可能一次编译运行成功,调试对于工程是不可或缺的,对于现代IDE而言,语法错误无需编译即可报出,但是更为常见的错误是那些逻辑错误,学会查看错误报告并使用调试工具进行调试是十分重要的,对于新手而言最常见的是恐惧查看错误报告。
链接错误和语法错误
链接错误从名字就可以看出来,是在链接过程发生的错误,发生这种错误的时候还来不及进入调试工具,程序无法运行。
最为常见的链接错误是error LINK 2019 函数“function*”中引用的未解析的外部符号“*symbol”,发生原因一般是链接库没有正确链接上,需要先查看报错的函数或变量,也就是 *symbol,找到其对应的第三方库,将其重新链接。
语法错误通常很好解决,但在C++中有一类错误很难寻找,即当使用C++标准库的容器时,会发生模板错误,发生时会报一串就像乱码的错误,并在命令行输出很长的一段话,明明自己的代码只有不到十行,报错行数却在几百行,就像下图一样,
这种错误是由于C++标准容器使用了模板的技术,该技术简而言之是通过代码生成代码,即预先写出生成代码的规则,在编译时根据规则自动生成代码,然后再进行编译,由于在编译前会生成额外的代码,导致编译器看到的代码比我们写的更多,报错行数是根据模板技术生成后的代码显示的,自然报错位置不多,并且由于生成的代码并不存在于我们编写的代码上,也不会对其进行显示,此时编译器虽然知道生成的代码发生了错误,但这是模板代码发生的错误,并不知道发生的具体原因,只能将模板生成的步骤全部显示出来,即上面第二张图表示的,
此时查找错误可以通过报错文件,找到发生错误的容器,再寻找最近编写的关于这个容器的代码,如下图报错的代码图中的std::unordered_map
部分
报错原因是unordered_map需要计算哈希值,但并不支持计算std::vector
调试工具
C/C++项目一般使用GDB程序进行调试,在IDE中也集成了GDB的调试功能,可以方便的可视化调试,调试时首先需要在Debug模式下,Release模式下会对代码进行优化,使生成的代码并不是原来编写的代码。
对于Visual Studio的调试工具使用,推荐学习Microsotf的官方文档调试器文档 - Visual Studio (Windows) | Microsoft Learn,对于不同的IDE,调试工具的位置不尽相同,但调试方法都类似,无外乎中断程序运行,查看当前内存状况(如变量和调用堆栈),检查运行情况。
编写代码中遇到的错误千奇百怪,通过调试工具也无法完美去除所有错误,调试过程也十分枯燥,常常一个bug改大半天,但成功修复bug的成就感也无与伦比,调试工具和累积的Debug经验将是你调试的重要依靠,但也需要他人的帮助,在StackOverflow上搜索并提问可以帮助你尽快解决问题,但耐心永远是最重要的。