程序编译过程,头文件,库文件

三种关键文件

  • 头文件(.h): 工程– 属性—配置属性–c/c++—常规—附加包含目录:加上头文件存放目录
  • 静态库(.lib): 1:工程—属性—配置属性—链接器—常规—附加库目录:加上lib文件存放目录
    2:工程—属性—配置属性—链接器—输入—附加依赖项 :加上lib文件名

  • 动态库(.dll): 把引用的dll放到工程的可执行文件所在的目录下

预处理:

包含头文件的 #include 和定义宏的 #define,同时还有规范性的头文件保护宏,在这个环节出现最多的问题是找不到头文件,然后编译器停止工作。

这里我们首先要区分 3 种头文件,
- 第一种是标准库的头文件,
- 第二种是代码中引用到的第三方库的头文件,
- 第三种是自己代码编写的头文件。

编译器在工作时是有一个头文件目录列表的,根据目录列表去寻找头文件,第一个目录便是当前代码的所在的目录,其次是编译器自行定义的目录(一般是标准库头文件所在的目录),最后是我们自己在编译时加上的目录列表(附加包含目录),可以有多个,包括需要引用的第三方库的头文件目录和自己代码的头文件目录(可能你自己写的头文件跟源文件不在一个目录下)

编译与汇编:

链接

归根结底主要是两种,第一是链接时找不到符号,第二是链接时找到了多个符号。一个符号可以指代一块内存或者一段代码。代码中与符号相关的几处地方如下。
- 变量的声明,告诉编译器有这么一个变量指代一块内存。
- 变量的定义,告诉编译器需要为这个变量分配一块内存。
- 函数的声明,告诉编译器有这么一段代码可以使用,输入输出规范如何,应该怎么调用。
- 函数的定义,告诉编译器这段代码的逻辑实现。
- 引用变量或函数,代码中使用某个变量或者调用某个函数。

编译器会给每个变量和每个函数分配一个符号,这样做的好处是方便符号的重用(函数的重用),也利于项目代码的模块化,多个目标文件的链接。由于每个源文件代码都是独立编译的,并生成目标文件,编译器在处理这个源文件时,最后会在目标文件中指出它所需要的符号和它能够提供的符号,这样,链接器在链接一堆目标文件时(库所提供的目标文件和自己代码的目标文件)就能够为每个待确定的符号找到对应的符号,从而成功生成可执行文件或者库文件

找不到符号:undefined reference to xxx
这个问题估计大部分同学在自己编译代码的时候都碰到过,绝大多数情况下都是编译时配置出错,没有告诉链接器应该去链接某个文件,而导致找不到符号。然而在有时候已经完全配置好了,还是会出现这种情况,即我知道这个库的符号全在这个目标文件中或者这个静态库或者这个动态库中,但是编译器还是报错说找不到符号。这种情况带出了一些更深层次的问题。

例如:C和 C++的不和谐:C 中是不允许函数重名的,但是 C++ 中可以通过不同的输入参数类型和类型次序来重载同名函数,暂且不论重载带来的好处和坑,C++ 能这么做是因为 C++ 编译器会重写每个函数最后生成的符号,上面两个函数在编译完后会生成不同的符号,这样一来,对链接器来说其实函数名相同已经没有什么意义了。 gcc 编译出来的符号已经跟函数名不一样了,符号包含了更多的信息,比如符号的类型(这个符号是个函数),和函数对应参数的类型,相当复杂。相对来说 C 代码编译出来的符号是和函数名是一致的,同时符号中也不区分变量和函数。以上的代码只是很简单的函数,如果加上命名空间,类函数等,编译器产生的符号会更加复杂,更加吓人,这也是为什么我们看到的链接出错中会有一长串的字符,因为 C++ 中的符号异常复杂,包含的信息太多。

所有我们可以看到好多 C 语言库的头文件里会写下面这种代码。

#ifdef __cplusplus
extern "C" {
#endif

......
......

#ifdef __cplusplus
}
#endif

通过这种方式告诉编译器,这个头文件的所有符号请按照 C 语言的规则进行生成,不要采用 C++ 那套符号重写机制。如果不采取这种措施,就会导致原本在库中是 foo 的符号被改写成 _Z3fooi 类似的形式而造成链接失败。

C++ 编译器中符号的兼容性:

多数情况下,我们使用的第三方库都是库的提供者事先编译好的,这就带来了一个很大的隐患。同一个函数在库中的符号和我们编译器要寻找的符号可能不一致,这个问题在 MSVC 上尤为突出。除去动态库静态库的差异,针对相同编译器的不同版本,同一个函数可能生成的符号会不一样,这是最最坑爹的地方。看看 OpenCV 里 VC10,VC11,VC12 的各个目录就知道这个差异是非常大的。


原文:http://blog.luoyetx.com/2015/12/compiler/

你可能感兴趣的:(c++)