“separate compilation” & “Linkage”

本文通过具体的C++代码示例,详细解释了编译器如何处理包含多个源文件的项目的编译过程,以及不同文件间如何进行正确的声明与定义。
1. 先来看段代码

//file1.cpp
#include <iostream>
using std::cout;
int x=1;
void f(){

     cout<<x;
}




//file2.cpp
void f(); // extern void f(); the same.
int main(int argc, char const *argv[])
{
     f();
     return 0;
}



如果单独编译两个文件,能够得到结果。
如果,再把编译得到的两个object链接,也是没有问题的。

对于file2.cpp,如果按照我以前的想法,我觉得这里为什么不需要include 一个header呢?

2. compilation & Linkage

先说编译。
我们的编译器,只读取一个文件和它的header作为输入!!
然后进行预处理,预处理之后的结果叫做一个translation unit.


比如,我们来看编译file1.cpp的预处理。
会读到include了一个header。
然后,编译器会把header里面的内容,放在file1.cpp里面的文件内容的前面。
变成了一个新的文件。

但是,注意,这可能是一个recursive的过程。
因为在这个被include的header里面,可能还include了其他header。
那么,又要进行一次展开。

最后,会得到一个很长,很长的文件。

然后,编译器还要对一些macros进行替换。
最后替换结束后,得到的文件,就叫做translation unit。



现在假如来编译file2.cpp,预处理的阶段什么都不需要做。
没有include header,也没有任何macros。
那么也得到了一个translation unit。


接下来,我们要做的事情是开始对translation unit开始编译了。


编译是对每一个translation unit单独进行的。
编译的时候,编译器只看当前正在编译的translation unit里面的内容。


现在来看下编译file2.cpp的过程:

编译器,读到了一个函数的声明。
注意,在这里这个函数是在namespace scope(file scope)。
那么,它会被默认为external linkage.
意思是这个函数的可以被不同的translation unit里面的代码使用。
那么,这个函数的定义,可能在file2.cpp里面,也可能在其他,一会会一起链接的文件里面。
再往下面走,就没有什么特别了。



同理。
在file1.cpp里面。
x和f都有external linkage。

也因此,我们能够在file2.cpp的translation unit使用f。
但是,如果我们要在file2.cpp里面,使用x,该怎么办呢?

因为int的声明和定义是一起的。
如果,我们一旦有了声明,那么我们实际上在file2.cpp里面就重新定义了x。
所以,如果要在file2.cpp里面使用x,那么需要做的是:
extern int x;

实际上,我们在file2.cpp里面,声明函数f,也是可以加上extern的:

extern void f();

只是,因为它们在namespace scope里面,默认了是extern linkage的。
不需要特别指定。



另外,也注意到,如果我们要有一个file1.hpp
那么这个file1.hpp里面要有什么内容呢?
其实就很好想了:

//file1.hpp
void f();
extern int x;




于是,file2.cpp就可以写成:

//file2.cpp
#include "file1.hpp"
int main(int argc, char const *argv[])
{
     f();
     return 0;
}


那么,编译器在编译的时候,会把file1.hpp的内容,在file2.cpp中展开,得到file2对应的translation unit。



顺便提一下,f()对于c++和c来说是不同的。
一个是没有arguments,一个是可以有数量不限的arguments


ref: the c++ programming language   9.1&9.2



### 编程上下文中关于编译的概念 在编程环境中,编译是指将高级语言(如C、C++、Java等)转换成计算机可以直接执行的机器码的过程。这一过程由专门设计的软件&mdash;&mdash;编译器完成。编译器读取源文件中的代码并将其翻译为目标代码,通常是一个二进制文件,该文件可以在特定的操作系统上运行。 #### 编译阶段概述 编译可以分为几个主要阶段: - **预处理**:此阶段会处理宏定义、条件编译指令以及头文件包含操作。 - **词法分析与语法分析**:在这两个连续的过程中,输入的字符流被分解成语义上有意义的标记序列;接着通过构建抽象语法树来验证这些标记是否遵循给定语言的语法规则[^1]。 - **优化**:在此期间会对中间表示形式的应用程序进行各种改进措施以提高性能或减小程序大小。 - **代码生成**:最终一步是从优化后的内部表示创建目标平台上的汇编代码或者直接生成机器码。 ```cpp // C++ example showing basic compilation steps using g++ g++ -Wall source_file.cpp -o output_executable ``` #### 常见编译问题及其解决方案 当涉及到实际项目时,可能会遇到多种类型的编译错误。以下是几种常见情况及相应的解决办法: - **未声明变量/函数名错拼** 如果尝试访问尚未声明的对象,则会出现此类警告或错误消息。&ldquo;`undefined reference to &#39;function_name&#39;`&rdquo;意味着链接失败因为找不到指定符号的具体实现。确保所有使用的库都已正确安装并且路径设置无误。 - **类型不匹配** 类型系统的严格性有助于捕捉潜在逻辑缺陷。如果参数传递给了期望不同数据类型的函数调用处,那么就会触发相应提示。仔细检查API文档,并按照规定的方式传参即可解决问题。 - **缺少依赖项** 当某些外部资源对于成功编译至关重要但又不在当前环境内可用的时候会发生这种情况。这可能是因为忘记下载必要的第三方包或者是版本冲突所致。查阅官方指南确认所需组件清单,并采取适当行动更新本地配置。 ```bash # Python pip install command for installing missing packages pip install package-name==specific-version ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值