解析C语言与C++的编译模型

2020-01-06 15:15:06王冬梅
C++继承了C的编译模型,C语言的编译链接模型相对简洁,但C++继承了这些机制之后变得更加复杂难以理解,这里就来带大家简要解析C语言与C++的编译模型  

首先简要介绍一下C的编译模型:
限于当时的硬件条件,C编译器不能够在内存里一次性地装载所有程序代码,而需要将代码分为多个源文件,并且分别编译。并且由于内存限制,编译器本身也不能太大,因此需要分为多个可执行文件,进行分阶段的编译。在早期一共包括7个可执行文件:cc(调用其它可执行文件),cpp(预处理器),c0(生成中间文件),c1(生成汇编文件),c2(优化,可选),as(汇编器,生成目标文件),ld(链接器)。
1. 隐式函数声明
为了在减少内存使用的情况下实现分离编译,C语言还支持”隐式函数声明”,即代码在使用前文未定义的函数时,编译器不会检查函数原型,编译器假定该函数存在并且被正确调用,还假定该函数返回int,并且为该函数生成汇编代码。此时唯一不确定的,只是该函数的函数地址。这由链接器来完成。如:


int main()
{
 printf("okn");
 return 0;
}

在gcc上会给出隐式函数声明的警告,但能编译运行通过。因为在链接时,链接器在libc中找到了printf符号的定义,并将其地址填到编译阶段留下的空白中。PS:用g++编译则会生成错误:use of undeclared identifier 'printf'。而如果使用的是未经定义的函数,如上面的printf函数改为print,得到的将是链接错误,而不是编译错误。
2. 头文件
有了隐式函数声明,编译器在编译时应该就不需要头文件了,编译器可以按函数调用时的代码生成汇编代码,并且假定函数返回int。而C头文件的最初目的是用于方便文件之间共享数据结构定义,外部变量,常量宏。早期的头文件里,也只包含这三样东西。注意,没有提到函数声明。
而如今在引入将函数声明放入头文件这一做法后,带来了哪些便利和缺陷:
优点:
项目不同的文件之间共享接口。
头文件为第三方库提供了接口说明。
缺点:
效率性:为了使用一个简单的库函数,编译器可能要parse成千上万行预处理之后的头文件源码。
传递性:头文件具有传递性。在头文件传递链中任一头文件变动,都将导致包含该头文件的所有源文件重新编译。哪怕改动无关紧要(没有源文件使用被改动的接口)。
差异性:头文件在编译时使用,动态库在运行时使用,二者有可能因为版本不一致造成二进制兼容问题。
一致性:头文件函数声明和源文件函数实现的参数名无需一致。这将可能导致函数声明的意思,和函数具体实现不一致。如声明为 void draw(int height, int width) 实现为 void draw(int width, int height)。