记录一次C++项目改造中定义全局变量的操作.

 

我对C/c++不太熟悉,在修改别人项目的时候,想弄个文件,专门存放全局变量.

然后各种不对.

xxx previously defined here错误 或者 error: redefinition of xxx或者initialized and declared 'extern'

反正各种问题.

 

其实根本原因就是重复引用导致的.

 

 

include引用

 

include 包含一个.h文件,简单理解就是:

将.h文件的内容直接插入到当前位置.

 

如果

main.c 引入 a.h 和  b.h

而b.h 因为需要调用a.h一些变量,必须也要引用a.h

那么最后,合并的mian.c大致内容:

 

a.h内容 (main.c引入)

a.h内容(b.h引入)

b.h内容 (main.c引入)

main.c (除了include外的内容)

 

这样a.h的内容出现了两次.就会出现编译错误.

因为正常情况下,变量和函数都不能重复定义.

 

对于这种问题,解决比较简单.

 

 

 

宏条件语句

 

比如a.h文件大致内容:

#ifndef HEADER__A_H
     #define HEADER__A_H

     //代码

#endif

 

 

宏条件语句会在编译前进行预处理.

如果没有定义"HEADER__A_H"的时候,

定义下"HEADER__A_H",并插入代码.

如果"HEADER__A_H"已经定义,将不满足宏条件, 所以#ifndef  到 #endif内容全部会忽略.

所以,只会在第一次引入的时候满足宏条件.

 

 

再回到上面的main.c 预处理后 :

 

a.h内容 (main.c引入,并定义了"HEADER__A_H")

a.h内容(b.h引入,内容是空的.)

b.h内容 (main.c引入)

main.c (除了include外的内容)

因为第一次引入a.h的时候,定义了"HEADER__A_H"宏,所以,之后再调用.a.h都不会触发宏条件语句为"true"的情况.

所以只有第一次引入a.h的时候,会插入a.h内容.

之后再引用a.h的时候因为宏条件语句无法满足,所以后面插入的内容是空的.

 

 

即使这样还是会出现变量,或者函数重复定义的情况.然后我再补充下C/c++项目编译过程.

 

编译过程

 

一个项目多有多个.c/.cpp文件.

编译过程是

gcc 分别编译每个.c/.cpp文件.编译成.o文件

然后

ld 链接这些.o文件.编译成最终可执行文件.

 

重点是分别两个字.

假设

main.c 引入 a.h

m2.c 也引入a.h

因为main.c和m2.c两个文件时分别编译的.

所以编译出来的

main.o 和m2.o 两个文件都会完整的引入并一起编译a.h内容.

ld链接这些.o文件的时候,.就有相同的标签(汇编的全局标签),导致链接失败.

 

 

C语言当初设计了一个关键词专门来解决这种问题.

 

extern

 

extern 后面跟着函数原型,或者变量定义.

例子

extern int abc;
extern void test(int code);

 

 

作用就是骗编译器.告诉编译器,这个变量,或者函数在其他地方已经定义了.

让编译器不再重新定义.这样避免最后ld链接的时候,找到相同的标签导致链接失败.

 

extern 的变量和函数可以多次声明.但是一定要有一个原始声明.

 

extern 是"假声明",你必须要有个"真声明".才能ld链接成功.

 

搞懂这些,就搞懂了为啥会重复引用了.

 

推荐操作

也就是项目全局变量定义的方法,规避重复引用的办法.

 

 

config.c

 

我们将全局变量声明放这里.这里是"真声明" .

 

// config.c


//引入它同名的头文件
#include "config.h"

//定义变量,并给初始值
int abc=123;

//仅定义变量,不给初始值.
bool test;

//定义函数
int mAdd(int a,int b) {
  return a+b;
}

 

config.h

 

config.h文件然后将c声明的变量复制一份.然后分别加上extern标识符即可.

切记. extern后跟着的变量不要用等号进行赋值.

 

#ifndef CONFIG_H
#define CONFIG_H


// config.h


//严禁对变量初始化
// 错误 → extern int abc=123;

//正确 ↓
extern int abc;

extern bool test;


//类似函数原型.
extern int mAdd(int a, int b);

#endif  // CONFIG_H

 

其他.c/.cpp/.h文件只要大胆的引用config.h 注意是H文件.即可完美的处理.

 

分析

 

举例5个文件

main.c   主函数

m2.c   自写的逻辑算法库

m2.h   逻辑算法库函数的声明(函数原型)

config.c  全局变量声明

config.h   给每个全局变量加上extern关键词

 

 

main.c 引用 m2.h 和config.h   ,其中m2.h中也引入了config.h

m2.c 引用了 m2.h 和config.h

config.c 引用了 config.h

 

 

编译流程

 

实际上就是gcc编译三个.c文件.然后将编译出来三个.文件链接成可执行文件.

 

 

推导

假设,先gcc编译main.c

那么预处理后

config.h内容 (main.c引入)

config.h内容(由m2.h引入,内容是空的.)

m2.h内容 (main.c引入)

main.c (除了include外的内容)

所以它能成功编译成main.o文件.

 

同理

gcc编译 m2.c

它的h没有互相引用,这个不用解释,只要没有语法错误,直接编译出m2.o文件

 

再同理可以推

gcc编译 config.c  也是直接编译成config.o文件

 

链接

重点是链接

main.o 引用的config.h用的extern ,没有分配标签.没有原始地址,但是链接器忽略错误,

m2.o 也引用了config.h,也是extern ,没有分配标签.没有原始地址,但是链接器忽略错误,

然后是config.o 也引用了config.h 这里extern所以也是忽略的.重点是.config.c是原始分配了标签.有变量原始地址.

所以ld链接器让main.o 和m2.o(变量)标签在config.o找到了原始地址.

链接成功.编译出可执行文件.

 

 

总结

config.c 文件放变量

config.h 用宏条件指令防止重复包含. 然后对config.c变量做extern 额外声明.

 

其他的c或者h文件只需要#include "config.h" 就可以调用全局变量.

 

这样就不会出现重复定义,或者重复引用

 

 

遗留问题

extern 和 inline 关键词组合描述函数的时候,

正常库内,或者自己内部调用没问题.

 

在跨库的时候有问题.

 

参考

https://blog.csdn.net/zhangla1220/article/details/38636521

https://blog.csdn.net/u013015629/article/details/52911398

https://www.cnblogs.com/chengmin/archive/2011/09/26/2192008.html