0
点赞
收藏
分享

微信扫一扫

C++学习笔记:文件的编译和链接【Cherno】

cwq聖泉寒江2020 2022-04-14 阅读 59
C++

首先,我们要有一个可运行的程序,那么要经过两步,第一步是将C++文件编译成obj,第二部是将obj文件链接成翻译单元,最后我们得到的就是可执行的程序;

先说编译;

编译的第一步是扫描C++文件中的预处理语句【#include、#define、#if...等等】;

这里说一下#include,其实#include干的活很简单,就是将指定的C++文件中的内容粘贴复制到指定的位置,我们举一个例子:

#include<iostream>

int main(){
    std::cout<<"Hello Wrold";

很明显,这个程序会报错,因为少了个分号;

然后我们开一个文件,叫endbrace好了,里面仅仅只有一个分号:

}//Endbrace.cpp

然后我们在需要}的地方#include"Endbrace",所以就有:

#include<iostream>

int main(){
    std::cout<<"Hello Wrold";
#include"Endbrace"

这个时候你就会发现,编译器通过了,这是因为,Endbrace内的内容被换到#include“Endbrace”的位置了,相当于:

#include<iostream>

int main(){
    std::cout<<"Hello Wrold";
}

看,#include干的活是不是很简单,如果我们#inlcude<iostream>,也不会发生什么特别复杂的事情,仅仅是将一个叫iostream的文件里的内容粘贴到对应的位置;

然后说一下什么是翻译单元:要注意翻译单元和C++文件是两个术语,这说明这两个东西并不是等价的,因为如果一个文件A声明了一个定义不在A中的函数,且该函数的定义在B中,那么这两个文件算一个翻译单元;

预处理完了之后,编译器就就将其翻译成机械语言,最后生成一个obj文件;

然后就是链接了,链接中发生了什么呢?

我们先明确一下事件发生的先后顺序:先是将每一个C++文件翻译成obj文件,然后就是将每个obj文件链接成翻译单元;

要知道什么是链接,本菜觉得从链接错误开始看比较好:

我们写一个.cpp文件:

#include<iostream>

int mul(int a, int b) {
	return a * b;
}

void show(const char * a) {
	std::cout << a;
}

我们点build【请记住这个名词】,然后系统会报错,因为我们没有写程序入口【一般程序入口为main函数】,这很正常,但是如果我们CTRL+F7,却没有报错,这是为什么呢?因为CTRL是编译指令,是将.cpp文件编译成obj文件的,在编译是不会要求文件要有程序入口【这很好理解,因为有时要编译多个文件,不可能所有文件都有main函数】,所以我们可以知道,寻找程序入口是链接的一部分;

那么我们把main函数补上:

#include<iostream>

int mul(int a, int b) {
	return a * b;
}

void show(const char * a) {
	std::cout << a;
}

int main(){

}

程序可以通过,那么我们再看看另外一个错误:

#include<iostream>

int mul(int a, int b) {
	return a * b;
}

int main(){
     
     show("Hellow world");

}

我们在另外一个.cpp文件中写show函数,我们姑且叫呀show.cpp:

#include<iostream>

void show(const char * a) {
	std::cout << a;
}

运行,发现报错:

 这因为虽然我有show函数,但源.cpp【main函数在的文件】不知道有这个函数存在,即使show函数就在隔壁,那我们就得让源.cpp记住有这个函数,并它在别的文件当中,要去别的文件当中寻找;

这个其实很容易做到,我们可以直接在开头声明有这个函数即可:

#include<iostream>

void show(const char *);

int mul(int a, int b) {
	return a * b;
}

int main() {

	show("Hellow World");
}

show.cpp不变,这个时候就可以成功运行了;

所以我们知道,寻找函数声明也是链接的一部分【函数定义在别的文件】

但如果我们改变show.cpp中的函数:
 

#include<iostream>

int show(const char * a) {
	std::cout << a;
    return 0;
}

这也会报错,因为源.cpp里的show函数的函数特征标于show.cpp中的show函数的函数特征标不同,故不能匹配;

下面再来看一个错误:

我们将show.cpp中的show函数恢复:

#include<iostream>

void show(const char * a) {
	std::cout << a;
}

然后,我们将show函数复制成两份:

#include<iostream>

void show(const char * a) {
	std::cout << a;
}

void show(const char * a) {
	std::cout << a;
}

这个时候链接器就会不乐意了,因为有两个一样的函数,链接器就会不知道该链接哪个,但是你点击build,它会出现一个编译错误:

 这是因为这种情况在编译这一步就不允许,还没到链接就报错了,这也说明了编译是发生在链接之前的。

如果我们直接在源.cpp中实现show函数,且show.cpp恢复,那么这就会发生链接错误:

#include<iostream>

void show(const char * a) {
	std::cout << a;
}
#include<iostream>

void show(const char *);

void show(const char * a) {
	std::cout << a;
}

int mul(int a, int b) {
	return a * b;
}

int main() {

	show("Hellow World");
}

 这是因为单单看每个.cpp文件都可以过编译【因为编译只考虑单个文件是否合法】,然后在链接的时候,因为源.cpp中的声明会链接到show.cpp的show函数,而源.cpp中有一个一模一样的show函数,所以会出现这种情况;

这种错误在以下情况更容易被触发:

我们定义一个head.h头文件,里面存储show函数,而show函数中的show函数该为Initshow函数,各文件内容如下:

head.h:

#pragma once

void show(const char * a) {
	std::cout << a;
}

show.cpp:

#include"head.h"

void Initshow() {
	show("Initshow");
}

源.cpp:

#include<iostream>
#include"head.h"

int main() {

	show("Hellow World");
}

很不信,系统会报错:

 还记得#include预处理语句会干什么嘛?对,它会将head.h里的内容粘贴复制到show.cpp和源.cpp中,这个时候链接器将三个文件链接起来时,show函数被定义了两次,所以报错;

这里我们该怎么解决这种情况呢?

我们有两个方法,第一个是将head.cpp中的show函数的作用域局限于本文件中,用static关键字:

#pragma once

static void show(const char * a) {
	std::cout << a;
}

然后是将show函数定义为内联函数:

#pragma once

inline void show(const char * a) {
	std::cout << a;
}

这是个什么原理呢?

实现是static关键字,它会规定一个函数只在本文件中作用,不可被其他文件调用【这里要注意,因为是#include粘贴复制过去的,所以源.cpp和show.cpp中都有一个其中static show函数】,因为只在本文件在作用,所以不会于其他文件在函数特征标相同的函数冲突,所以可以解决这个问题;

然后是将函数定义为内联函数:

内联函数的解决方案其实更暴力,就是那里调用内联函数,哪里就将调用语句替换成函数内容【注意:是函数内容不是函数定义;】,因为是函数内容,而不是函数定义,所以更谈不上什么函数冲突了;

要注意:内联函数的关键字:inline只是向系统推荐将这个函数定义为内联函数,但系统会自动判断这个函数是否适合做内联,不行的话……不可强求;

总结一下:

第一步编译:将每个文件翻译成obj文件,其步骤是先处理预处理语句,然后编译成机械码,其特性是将每个文件独立看待,一个文件的编译不会影响另外一个文件的编译;

第二步链接:将每个文件链接成一个翻译单元,其容易受到干扰,尤其是各种重定义,可用static语句和inline语句修复;

举报

相关推荐

0 条评论