不妨先看一个例子:
//A.h #pragma once #include "B.h" class A { public: A(void); ~A(void); }; //B.h #pragma once int a; class B { public: B(void); ~B(void); }; //main.cpp #include <iostream> #include "A.h" #include "B.h" using namespace std; int main() { }
这时候会在链接的时候报错
1>B.obj : error LNK2005: "int a" (?a@@3HA) 已经在 A.obj 中定义
1>main.obj : error LNK2005: "int a" (?a@@3HA) 已经在 A.obj 中定义
错误基本原因:
因为A.h在#include”b.h”后会产生一个int a的定义语句。程序在编译的时候只会对单个文件进行语法等要素的编译生成obj文件,在链接的时候将这些obj文件整合起来,这时候由于a.obj和b.obj都有int a的定义,就出现了上述所谓的重定义。
深入探究原因:
因为:1.定义和声明(定义就是说程序一定知道会为其分配多少内存,比如int a就会知道分配4bytes的内存,声明是不会知道分配多少内存的。定义分为3种:int a 对象定义;void func(){…}函数定义;Class A{…}类型定义;声明分为2种:void func();函数声明, Class A;类型声明)。最重要的点:在一个文件中同一个声明可以多次声明,但是定义只能定义一次。2.在编译的时候,include”B.h”会将B.h中的所有东西拷贝到a.h中,这时候的a.h就应该是这样的:
//A.h #pragma once int a; class B { public: B(void); ~B(void); }; class A { public: A(void); ~A(void); };
我突然又想到个问题:为什么只有int a类型重定义,不会有class B类型重定义呢?
不妨再来看个例子。
//A.h #pragma once #include "B.h" //int tempA; //void Func2(); //class Cattle //{ //public: // Cattle(); // ~Cattle(); //}; class Apple { public: Apple(void); ~Apple(void); BadBoy bad; Cattle catt; void Func1(); }; //B.h #pragma once int tempB; void func(){} class Cattle { public: Cattle(); ~Cattle(); }; class BadBoy { public: BadBoy(void); ~BadBoy(void); }; #include <iostream> #include "A.h" #include "B.h" using namespace std; //main.cpp int main() { }
错误输出:
1>B.obj : error LNK2005: "void __cdecl func(void)" (?func@@YAXXZ) 已经在 A.obj 中定义
1>B.obj : error LNK2005: "int tempB" (?tempB@@3HA) 已经在 A.obj 中定义
1>main.obj : error LNK2005: "void __cdecl func(void)" (?func@@YAXXZ) 已经在 A.obj 中定义
1>main.obj : error LNK2005: "int tempB" (?tempB@@3HA) 已经在 A.obj 中定义
我打开a.obj找到这样一段:
$T__________`_______________________________________?tempB@@3HA_?func@@YAXXZ___RTC_Shutdown.rtc$TMZ___RTC_Shutdown___RTC_InitBase.rtc$IMZ___RTC_InitBase_??0Apple@@QAE@XZ_??1BadBoy@@QAE@XZ___unwindfunclet$??0Apple@@QAE@XZ$0_??0Cattle@@QAE@XZ_??0BadBoy@@QAE@XZ____security_cookie___ehhandler$??0Apple@@QAE@XZ____CxxFrameHandler3_@__security_check_cookie@4___ehfuncinfo$??0Apple@@QAE@XZ___unwindtable$??0Apple@@QAE@XZ___RTC_CheckEsp_??1Apple@@QAE@XZ___unwindfunclet$??1Apple@@QAE@XZ$0_??1Cattle@@QAE@XZ___ehhandler$??1Apple@@QAE@XZ___ehfuncinfo$??1Apple@@QAE@XZ___unwindtable$??1Apple@@QAE@XZ_
这
在b.obj找到这样一段:
$T__________`___________________________________?___?tempB@@3HA_?func@@YAXXZ___RTC_Shutdown.rtc$TMZ___RTC_Shutdown___RTC_InitBase.rtc$IMZ___RTC_InitBase_??0Cattle@@QAE@XZ_??1Cattle@@QAE@XZ_??0BadBoy@@QAE@XZ_??1BadBoy@@QAE@XZ_
通过这样一段,我个人所认为的是对a.obj和b.obj进行链接时,其实就是字符串比较,遇到$T之后的如果a.obj和b.obj同时出现相同的?***@@***的字符串时候就会进行产生重定义的错误。在遇到$ IMZ___RTC_InitBase之后就不会进行字符串比较了。这只是我个人猜测,但是VS绝对有某种方式将类型定义隐藏起来,从而不会出现重定义的错误。
这2段信息都是表示这个obj中的出现的定义和函数引用信息。实际就是h文件向下转换的更底层的代码语言。
我在后面测试的BUG中发现了红色标注的内容。
>A.obj : error LNK2019: 无法解析的外部符号 "public: __thiscall BadBoy::~BadBoy(void)" (??1BadBoy@@QAE@XZ),该符号在函数 __unwindfunclet$??0Apple@@QAE@XZ$0 中被引用
1>A.obj : error LNK2019: 无法解析的外部符号 "public: __thiscall Cattle::Cattle(void)" (??0Cattle@@QAE@XZ),该符号在函数 "public: __thiscall Apple::Apple(void)" (??0Apple@@QAE@XZ) 中被引用
1>A.obj : error LNK2019: 无法解析的外部符号 "public: __thiscall BadBoy::BadBoy(void)" (??0BadBoy@@QAE@XZ),该符号在函数 "public: __thiscall Apple::Apple(void)" (??0Apple@@QAE@XZ) 中被引用
1>A.obj : error LNK2019: 无法解析的外部符号 "public: __thiscall Cattle::~Cattle(void)" (??1Cattle@@QAE@XZ),该符号在函数 "public: __thiscall Apple::~Apple(void)" (??1Apple@@QAE@XZ) 中被引用
可以推断:@表示冒号,??0Apple@@QAE@XZ == XZ:QAE::Apple() == public:thiscall::apple()
这几个错误的原因都是A.obj文件没有在其他obj文件中找到这些函数的实现体。
另外,我在debug的过程中有个重要发现:在A.obj找不到Apple类中的属性bad和catt。这说明了obj文件不保存属性的名称,只保存了该属性的序列长度(也就是多少比特数),同时如果只声明Func1(),在CPP文件中不实现,也找不到方法Func();这为后期的Func1()编译可以通过(因为已经声明),链接不能通过埋下了祸害(找不到实现体)。
结论:
番外话:1、如果你的A.h的声明,在C.cpp中实现,那么生成的是C.obj文件。
2、如果你只有声明的h文件,没有实现的cpp文件,那么不会生成obj文件,且不会有任何报错。
http://www.cnblogs.com/chhlong/p/3192013.html