编译完全攻略:编译器处理及LNK错误的解决方法[good!!]

  最近调试遇到N多LNK错误,遂总结各解决方案如下, 以做系统学习之用。

编译器处理相关

.预处理器-编译器-汇编器-链接器
预处理器会处理相关的预处理指令,一般是以"#"开头的指令。如:#include "xx.h" #define等。
编译器把对应的*.cpp翻译成*.s文件(汇编语言)。 汇编器则处理*.s生成对应的*.o文件(obj目标文件)
最后链接器把所有的*.o文件链接成一个可执行文件(?.exe)
1.部件:
首先要知道部件(可以暂且狭义地理解为一个类)一般分为头文件(我喜欢称为接口,如:*.h)及实现文件(如:*.cpp)。
一般头文件会是放一些用来作声明的东东作为接口而存在的。而实现文件主要是实现的具体代码。
2.编译单个文件:
记住IDE在bulid文件时只编译实现文件(如*.cpp)来产生obj,在vc下你可以对某个?.cpp按下ctrl+f7单独编译它
生成对应一个?.obj文件。在编译?.cpp时IDE会在?.cpp中按顺序处理用#include包括进来的头文件
(如果该头文件中又#include有文件,同样会按顺序跟进处理各个头文件,如此递归。。)
3.内部链接与外部链接:
内、外链接是比较基础的东东,但是也是新手最容易错的地方,所以这里有必要祥细讨论一下。
内部链接产生的符号只在本地?.obj中可见,而外部链接的符号是所有*.obj之间可见的。
如:用inline的是内部链接,在文件头中直接声明的变量、不带inline的全局函数都是外部链接。
在文件头中类的内部声明的函数(不带函数体)是外部链接,而带函数体一般会是内部链接(因为IDE会尽量把它作为内联函数)
认识内部链接与外部链接有什么作用呢?下面用vc6举个例子:
// 文件main.cpp内容:
void main(){}
// 文件t1.cpp内容:
#include "a.h"
void Test1(){ Foo(); }
// 文件t2.cpp内容:
#include "a.h"
void Test2(){ Foo(); }
// 文件a.h内容:
void Foo( ){ }
好,用vc生成一个空的console程序(File - new - projects - win32 console application),并关掉预编译选项开关
(project - setting - Cagegoryrecompiled Headers - Not using precompiled headers)
现在你打开t1.cpp按ctrl+f7编译生成t1.obj通过   打开t2.cpp按ctrl+f7编译生成t2.obj通过
而当你链接时会发现:
Linking...
t2.obj : error LNK2005: "void __cdecl Foo(void)" (?Foo@@YAXXZ) already defined in t1.obj
这是因为:
1. 编译t1.cpp在处理到#include "a.h"中的Foo时看到的Foo函数原型定义是外部链接的,所以在t1.obj中记录Foo符号是外部的。
2. 编译t2.cpp在处理到#include "a.h"中的Foo时看到的Foo函数原型定义是外部链接的,所以在t2.obj中记录Foo符号是外部的。
3. 最后在链接 t1.obj 及 t2.obj 时, vc发现有两处地方(t1.obj和t2.obj中)定义了相同的外部符号(注意:是定义,外部符号可以多处声明但不可多处定义,因为外部符号是全局可见的,假设这时有t3.cpp声明用到了这个符号就不知道应该调用t1.obj中的还是t2.obj中的了),所以会报错。
解决的办法有几种:
a.将a.h中的定义改写为声明,而用另一个文件a.cpp来存放函数体。(提示:把上述程序改来试试)
(函数体放在其它任何一个cpp中如t1.cpp也可以,不过良好的习惯是用对应cpp文件来存放)。
这时包括a.h的文件除了a.obj中有函数体代码外,其它包括a.h的cpp生成的obj文件都只有对应的符号而没有函数体,如t1.obj、t2.obj就只有符号,当最后链接时IDE会把a.obj的Foo()函数体链接进exe文件中,并把t1.obj、t2.obj中的Foo符号转换成对应在函数体exe文件中的地址。
另外:当变量放在a.h中会变成全局变量的定义,如何让它变为声明呢?
例如: 我们在a.h中加入:class CFoo{};CFoo* obj;
这时按f7进行build时出现:
Linking...
t2.obj : error LNK2005: "class CFoo * obj" (?obj@@3PAVCFoo@@A) already defined in t1.obj
一个好办法就是在a.cpp中定义此变量( CFoo* obj,然后拷贝此定义到a.h文件中并在前面加上extern(extern CFoo* obj如此就可通过了。当然extern也可以在任何调用此变量的位置之前声明,不过强烈建议不要这么作,因为到处作用extern,会导致接口不统一。良好的习惯是接口一般就放到对应的头文件。
b. 将a.h中的定义修改成内部链接,即加上inline关键字,这时每个t1.obj和t2.obj都存放有一份Foo函数体,但它们不是外部符号,所以不会被别的obj文件引用到,故不存在冲突。(提示:把上述程序改来试试)
另外我作了个实验来验证”vc是把是否是外部符号的标志记录在obj文件中的“(有点绕口)。可以看看,如下:
(1)文件内容:
// 文件main.cpp内容:
void main(){}
// 文件t1.cpp内容:
#include "a.h"
void Test1(){ Foo(); }
// 文件t2.cpp内容:
#include "a.h"
void Test2(){ Foo(); }
// 文件a.h内容:
inline void Foo( ){ }
(2) 选t1.cpp按ctrl+f7单独编译,并把编译后的t1.obj修改成t1.obj_inline
(3) 选t2.cpp按ctrl+f7单独编译,并把编译后的t2.obj修改成t2.obj_inline
(4) 把除了t1.obj_inline及t2.obj_inline外的其它编译生成的文件删除。
(5) 修改a.h内容为:void Foo( ){ },使之变为非内联函数作测试
(6) rebuild all所有文件。这时提示:
Linking...
t2.obj : error LNK2005: "void __cdecl Foo(void)" (?Foo@@YAXXZ) already defined in t1.obj
Debug/cle.exe : fatal error LNK1169: one or more multiply defined symbols found
(7) 好,看看工程目录下的debug目录中会看到新生成的obj文件。
下面我们来手工链接看看,
打开菜单中的project - setting - Link,拷贝Project options下的所有内容,如下:
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept 把它修改成:
Link.exe kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept Debug/t1.obj Debug/t2.obj Debu

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值