1、编译和链接
编译器对每一个源文件进行编译,生成对应的obj文件
链接器将它们进行合并,生成一个exe文件
1、#符号之后的都是预处理语句,编译器收到源文件之后先处理这个预处理语句。它叫预处理的原因就是,在编译之前就被处理了。比如#include语句,就是找到iostream文件,然后将它的所有内容拷贝到现在的文件内。
2、如何将源文件转换为可运行的二进制文件:编译器先预处理, 然后进行编译,生成object文件,后缀是.obj,然后用连接器将这些.obj文件合并成一个可执行文件即.exe文件。
3、debug比release慢是因为很多优化被关掉了,关掉优化的好处是让我们可以调试代码。
4、项目里的所有cpp文件都会被编译,但头文件不会被编译,仅会编译cpp文件,因为在预处理的时候,头文件的内容已经被复制进来了。
5、如std::cout << “hello world” << std::endl;如果我不想使用cout进行打印,可以再写一个函数:
//main.cpp
void Log(const char* message)
{
std::cout << message << std::endl;
}
int main()
{
Log("hello world");
}
但我不喜欢把很多函数都包含在main.cpp文件中,我希望把代码分到很多文件当中,这样可以保证文件的干净整洁。 所以我们创建一个log.cpp文件,再里面写Log函数,在main.cpp里进行调用:
//log.cpp
#include<iostream>
void Log(const char* message)
{
std::cout << message << std::endl;
}
//main.cpp
int main()
{
Log("hello world");
}
发现报错,一个问题是main没有包含iostream头文件,另一个是找不到Log文件,因为main()函数不知道Log函数,我们可以通过声明Log函数来修复问题:
//log.cpp
#include<iostream>
void Log(const char* message)
{
std::cout << message << std::endl;
}
//main.cpp
#include<iostream>
//声明Log函数
void Log()(const char* message);
int main()
{
Log("hello world");
}
声明就像告诉编译器,Log函数是存在的,编译器会相信,而且它不关心Log函数是否真存在,在哪里定义,它不管,因为这是链接器的工作,如果把log.cpp中的Log函数名改为Logr,点击编译兵没有报错,但点击运行,就会出现链接错误。因为编译器不管函数的定义,链接器会在源文件中寻找Log函数的定义,他的工作是连接函数,当它发现没有Log函数的定义,则出现报错。
2、编译器是如何工作的
总的来说,编译器可以将文本文件转换成目标文件 .obj,obj可以传递到连接器,目前先讨论编译器。
编译器在生成obj时,实际上做了几件事:
- 预处理我们的代码,即先处理所有的【预处理语句】,即#include、#define、#if、#endif语句等
- 再进行编译,即词法分析、语法分析、语义分析等,确定所有语句都合法后,将它们翻译成中间文件.s
- 然后进行汇编,将中间文件翻译成目标机器指令.o
2、链接器
将所有.obj文件链接成.exe文件,.exe文件为全局可见文件
链接步骤:
- 合并段、符号表
- 符号解析:在符号引用地找到符号定义地
- 分配地址空间
- 符号重定位
静态链接和动态链接:
1、静态链接,在链接阶段,把需要的所有函数二进制代码包含到可执行文件中去,完成所有符号引用的一种链接方式。
2、动态链接,在链接的时候不直接拷贝相应依赖库的代码,而是记录一系列的符号和参数,才程序运行或加载时,将这些信息传递给操作系统,操作系统将需要的依赖库加载到内存中。
区别:
静态链接比动态链接要快
动态链接比静态链接省内存