C++ 通过 #include 了解 Compiler 和 Linker

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.cppMath.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();
    }
    
    

猜你喜欢

转载自blog.csdn.net/qq_31362767/article/details/126971415