記錄一次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 關鍵詞組合描述函數的時候,
正常庫內,或者自己內部調用沒問題.
在跨庫的時候有問題.
參考