C/C++ include 条件编译 extern及编译连接浅析

问题描述:

源于在尝试extern的使用范围时,遇到的bug,代码如下:

1 main.cpp
 2 
 3 #include "stdafx.h"
 4 #include "ExternExample.h"
 5 
 6 
 7 int _tmain(int argc, _TCHAR* argv[])
 8 {
 9     using namespace std;
10     cout << temp << endl;
11     
12     system("pause");
13     return 0;
14 }
15 
16 ExternExample.h
17 
18 int temp;

错误结果 error LNK2005: "int temp" (?temp@@3HA) already defined in ExternExample.obj

以为是ExternExample.h文件中没有加条件编译,把ExternExample.h变成

ifndef _EXTERN_EXAMPLE

#define _EXTERN_EXAMPLE

int temp;

endif

结果还是错,所以才有了以下的调查。

一切都要从c++的编译开始:

概念:

1、定义和声明

C++的定义和声明概念是非常重要的,也是最容易被新手忽视的,介绍一下区分方法。

对变量:

  1. 声明:extern int a;//extern是外部的意思,就是说这个变量不在这定义,只是声明有这个变量,在外部定义,这样编译的时候会认为变量时外部变量

  2. 定义:extern int a = 0;int a;int a =0;//只要分配了内存的,都是定义,就算声明了extern也一样。

所以对变量来说,只要分配了内存,就是定义。

对函数:

  1. 声明:extern int function(); int funciton();//函数有没有extern都一样,不管你是不是extern了,只要没有定义函数体,就是声明

  2. 定义:extern int function(){return 1} int function(){return 1} //只要定义了函数体,就是声明

所以对函数来说,只要定义了函数体,就是定义。

2、编译单元

首先要理解c++的一个编译单元是cpp文件,所以每一个cpp文件都会被编译成一个.obj文件,而cpp文件要联系它的头文件

3、.h文件和.cpp文件

在最开始写c的时候,是没有.h文件的,所有的内容都写在.c文件里面,后来人们发现,每一次引用其他文件内容的时候,都要进行声明,很麻烦,

尤其是当一个文件中的变量名改了之后,需要改所有文件中用到的地方,简直灾难。所以后来加入了.h文件,把所有的声明写在.h文件里面,

使用的人只要include就行,并且变量名改变之后,因为直接include进来后展开,所以也不需要使用者改变代码。

编译器编译过程

1、预编译

所谓预编译,其实就是#include展开和宏展开,上述代码被展开后,main.cpp里面就了int temp这个定义,注意是定义。

而之前的条件编译语句#ifndef _EXTERN_EXAMPLE,在这里起作用,如果XX.h包含了ExternExample.h,maip.cpp既包含了XX.h也包含了ExternExample.h

就会出问题,这就是重定义,如果ExternExample.h里的内容是声明extern int a,实际测试,包含两次是没有问题的,估计是忽略了吧。

2、编译

编译就是将刚刚展开之后的.h中的内容和.cpp中的内容作为一个编译单元来进行编译。

上述展开后的main.cpp代码可能被编译成

符号 地址

temp 0x00??

_main 0x00??

这些全局的定义,都会被记录到一张表中,称为符号导出表。

如果编译单元之中有extern外部变量怎么办么?这些变量也会被记录到一张表中,称为未知符号表。

然后每个.cpp文件都会按这种方式被编译成一个.obj文件

3、连接

编译好之后的obj是独立的,需要连接在一起,才能成为一个程序。

因为之前生成的.obj文件都是独立的,所以里面的地址都是相对的,所以需要地址重定向,比如将A.obj定向到0x00002000~0x00003000之间,B.obj定向到0x00005000~0x00006000之间

这样就会生成另一张表,地址重定向表。

而未知符号表里面的地址怎么办?就从其他.obj文件中的导出符号表中找,找到地址之后,填上ok了。

本文程序的错误就出在这里,前面我们知道main.cpp导出符号里有int temp,而ExternExample.h的导出符号里也有int temp。

所以错误为 error LNK2005: "int temp" (?temp@@3HA) already defined in ExternExample.obj。

结论:

1、.h头文件中一定不要有定义,只能有声明,定义全部卸载.cpp文件里面

2、在头文件中,需要使用条件编译(windows中可以用#pragma once代替),防止重定义
原文链接: https://www.cnblogs.com/chjtao/p/4673224.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/219558

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 上午10:36
下一篇 2023年2月13日 上午10:36

相关推荐