How Compiler work
原始Math.cpp
:
int Multiply(int a, int b) {
int result = a * b;
return result;
}
现在演示需要,拆成两个文件:
EndBrance.h
}
Math.cpp
int Multiply(int a, int b) {
int result = a * b;
return result;
#include "EndBrance.h"
编译 Math.cpp
,并没有报错。
compiler 的作用:打开 EndBrance.h
文件,把里面的所有内容拷贝粘贴到 #include "EndBrance.h"
所在位置。
通过预处理输出文件查看原因
修改工程属性配置(配置测试后,要记得取消设置):
重新编译 Math.cpp
,会在 Debug 目录下生成 Math.i
文件,打开查看内容:
可以看出 compiler 已经生成的内容,源码 Math.cpp
中有 #include "EndBrance.h"
,compiler 将其文件中的 )
嵌入到 #include "EndBrance.h"
所在的位置。
类似的,修改一下代码,再次查看
比如使用 #define
#define INTERGER int
INTERGER Multiply(int a, int b) {
INTERGER result = a * b;
return result;
}
重新编译后:
比如使用 #if
,编译查看 Math.i
#if 0
int Multiply(int a, int b) {
int result = a * b;
return result;
}
#endif
比如使用 #include <iostream>
,编译后查看,里面有5万多行,最后才是我自己写的代码。这是因为 iostream
中包含了其他文件代码。
How Linker work
LNK1561
Math.cpp
#include <iostream>
void Log(const char* message) {
std::cout << message << std::endl;
}
int Multiply(int a, int b) {
Log("Multiply");
return a * b;
}
直接编译,会出现报错:LNK1561,entry point must be defined。 LNKxxx 表示在链接报错,Cxxx 表示在编译报错。
通过工程属性,可以自定义一个入口。入口不一定是 main 函数,通常是 main 函数。
添加上 main 函数,重新编译就没问题了。
int main() {
std::cout << Multiply(3, 5) << std::endl;
std::cin.get();
}
LNK2019
如果改变 Log.cpp
文件中的 Log 函数名为 Logger,然后只编译(Crtl+F7) Math.cpp
,是不会报错的,此过程还没用到链接。
接着,编译整个工程(Ctrl+F5),就会出现 LN2019 的报错
1>------ 已启动生成: 项目: demo_include, 配置: Debug Win32 ------
1>Math.obj : error LNK2019: 无法解析的外部符号 "void __cdecl Log(char const *)" (?Log@@YAXPBD@Z),该符号在函数 "int __cdecl Multiply(int,int)" (?Multiply@@YAHHH@Z) 中被引用
1>E:\keeplearning\CppCode\VS2017\demo_include\Debug\demo_include.exe : fatal error LNK1120: 1 个无法解析的外部命令
1>已完成生成项目“demo_include.vcxproj”的操作 - 失败。
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
尝试以下操作:
-
注释掉
Log("Multiply");
后,编译整个工程,编译成功#include <iostream> void Log(const char *); int Multiply(int a, int b) { // Log("Multiply"); return a * b; } int main() { std::cout << Multiply(3, 5) << std::endl; std::cin.get(); }
-
注释掉
main
函数中的调用后,编译整个工程,编译失败#include <iostream> void Log(const char *); int Multiply(int a, int b) { Log("Multiply"); return a * b; } int main() { // std::cout << Multiply(3, 5) << std::endl; std::cin.get(); }
1>------ 已启动生成: 项目: demo_include, 配置: Debug Win32 ------ 1>Math.obj : error LNK2019: 无法解析的外部符号 "void __cdecl Log(char const *)" (?Log@@YAXPBD@Z),该符号在函数 "int __cdecl Multiply(int,int)" (?Multiply@@YAHHH@Z) 中被引用 1>E:\keeplearning\CppCode\VS2017\demo_include\Debug\demo_include.exe : fatal error LNK1120: 1 个无法解析的外部命令 1>已完成生成项目“demo_include.vcxproj”的操作 - 失败。 ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
原因是,即使没有在这个文件中实际调用这个 Multiply 函数,但是一定程度上说,可能在其他文件中用到,所以链接器需要去链接。
-
如果和链接器说对于 Multiply 函数,我只用在
Math.cpp
中,不会用到其他文件里,你可不可以不要链接?当然也是可以的,此时把 Multiply 改为 static(意味着只在这个文件中用到),重新编译整个工程,就没链接报错的问题了。#include <iostream> void Log(const char *); static int Multiply(int a, int b) { Log("Multiply"); return a * b; } int main() { // std::cout << Multiply(3, 5) << std::endl; std::cin.get(); }
当然,如果在此基础上,再把 main 函数中的
// std::cout << Multiply(3, 5) << std::endl;
打开注释,再编译工程,也是会有报错的。
除了改变 Log.cpp
文件中的 Log 函数名为 Logger,如果进行其他改变,也是会出现链接报错而编译不过:
#include <iostream>
int Log(const char* message) {
// 改变返回值类型
std::cout << message << std::endl;
return 0;
}
#include <iostream>
void Log(const char* message, int level) {
// 改变入参个数
std::cout << message << std::endl;
}
LNK2005
查看是否有函数或者变量,它们是相同、重复的名字或者特征,比如两个相同名字的函数有着同样的返回值、入参,此时链接器不知道该链接哪个。
-
场景1
Log.cpp
#include <iostream> void Log(const char* message) { std::cout << message << std::endl; }
Math.cpp
#include <iostream> void Log(const char *); void Log(const char* message) { std::cout << message << std::endl; } int Multiply(int a, int b) { Log("Multiply"); return a * b; } int main() { std::cout << Multiply(3, 5) << std::endl; std::cin.get(); }
编程工程,报错:
1>------ 已启动全部重新生成: 项目: demo_include, 配置: Debug Win32 ------ 1>Log.cpp 1>Math.cpp 1>正在生成代码... 1>Math.obj : error LNK2005: "void __cdecl Log(char const *)" (?Log@@YAXPBD@Z) 已经在 Log.obj 中定义 1>E:\keeplearning\CppCode\VS2017\demo_include\Debug\demo_include.exe : fatal error LNK1169: 找到一个或多个多重定义的符号 1>已完成生成项目“demo_include.vcxproj”的操作 - 失败。 ========== 全部重新生成: 成功 0 个,失败 1 个,跳过 0 个 ==========
-
场景2
Log.h
#pragma once void Log(const char* message) { std::cout << message << std::endl; }
Log.cpp
#include <iostream> #include "Log.h" void InitLog() { Log("Initialized Log"); }
Math.cpp
#include <iostream> #include "Log.h" static int Multiply(int a, int b) { Log("Multiply"); return a * b; } int main() { std::cout << Multiply(3, 5) << std::endl; std::cin.get(); }
编译工程,报错
1>------ 已启动生成: 项目: demo_include, 配置: Debug Win32 ------ 1>Log.cpp 1>Math.obj : error LNK2005: "void __cdecl Log(char const *)" (?Log@@YAXPBD@Z) 已经在 Log.obj 中定义 1>E:\keeplearning\CppCode\VS2017\demo_include\Debug\demo_include.exe : fatal error LNK1169: 找到一个或多个多重定义的符号 1>已完成生成项目“demo_include.vcxproj”的操作 - 失败。 ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
#include
语句的工作原理:当include一个header文件,我们仅仅是将这个header文件中的内容放到这条语句的存在的位置。因此,在
Log.cpp
和Math.cpp
中都写了#include "Log.h"
,就会导致 Log 函数的重复定义,从而出现和场景1相同的问题。
解决该问题的方法:
-
方法1
将
Log.h
中的方法换成 static,其他文件不变,编译工程通过#pragma once static void Log(const char* message) { std::cout << message << std::endl; }
-
方法2
将
Log.h
中的方法换成 inline,其他文件不变#pragma once inline void Log(const char* message) { std::cout << message << std::endl; }
-
方法3
Log.h
#pragma once void Log(const char* message);
Log.cpp
#include <iostream> #include "Log.h" void Log(const char* message) { std::cout << message << std::endl; } void InitLog() { Log("Initialized Log"); }
Math.cpp
#include <iostream> #include "Log.h" int Multiply(int a, int b) { Log("Multiply"); return a * b; } int main() { std::cout << Multiply(3, 5) << std::endl; std::cin.get(); }