能不能在頭文件中定義全局變量?


編譯器驅動程序

大多數編譯系統提供編譯器驅動程序(compiler driver),它代表用戶在需要時調用語言預處理器編譯器匯編器、和鏈接器

我們所常說的 “編譯生成可執行文件” 實際包括以下過程:

  1. 預處理器 (某些編譯系統,預處理器被集成到 編譯器 中)

    cpp [other arguments] main.c /tmp/main.i
    

    處理預處理指令,生成中間文件,所有的預處理器命令都是以井號(#)開頭。主要任務包括:

    • 刪除注釋;
    • 插入被 #include 指令所包含的的文件內容;
    • 定義和替換由#define指令定義的符號;
    • 確定代碼的部分內容是否應該根據一些條件編譯指令進行編譯;
  2. 編譯器

    cc1 /tmp/main.i -Og [other arguments] -o /tmp/main.s
    

    將預處理后的中間文件翻譯成一個ASCII匯編語言文件

  3. 匯編器

    as [other arguments] -o /tmp/main.o /tmp/main.s
    

    將ASCII匯編語言文件翻譯成一個可重定位目標文件

  4. 鏈接器

    ld -o prog [system object files and args] /tmp/main.o /tmp/sum.o
    

    將一個或多個可重定位目標文件,以及必要的系統目標文件組合起來,創建一個可執行文件

編譯生成可執行文件過程.png

非靜態全局變量

先個下結論:可以,但非常非常非常不建議!!

  • 若頭文件僅被一個源文件使用到,可以正常生成可執行文件。
  • 若頭文件被多個源文件包含,可正常執行完 cpp、cc1、as,但在鏈接(ld)時便會報錯(重復定義)。

由上面的圖可以分析出,源程序代碼在生成可執行文件的過程中,前三步均可看做獨立完成的,僅在最后一步將多個源文件生成的目標文件鏈接起來。

如下代碼:

a.cpp 、 b.cpp 在經過預處理器、編譯器、匯編器時均認為是沒有定義變量 A 的,於是都有變量定義。在鏈接時,便出現了二義性(重復定義)。

[root@localhost val]# g++ a.cpp b.cpp

/tmp/ccVzRqVG.o:(.bss+0x0): multiple definition of `A'

/tmp/ccjI4zgp.o:(.bss+0x0): first defined here

collect2: error: ld returned 1 exit status

// a.h
#ifndef A_H
#define A_H
int A;
void funcA();
#endif
// a.cpp
#include<iostream>
#include "a.h"
                                              
void funcA(){
	A = 10;
    std::cout << "a.cpp : " << A << std::endl;
}
// b.cpp
#include<iostream>
#include "a.h"

int main(){
	funcA();
	A =20;
    std::cout << "b.cpp : " << A << std::endl;
    return 0;
}

靜態全局變量

上面的代碼如若將變量 A定義為 static,編譯執行沒有問題。但是,靜態變量的作用域僅在 ”當前源文件“ ,即兩處的變量 A 不是同一個變量,是不同的文件作用域內的靜態變量。

“靜態全局變量” 這個稱謂其實就有點怪異,靜態變量在文件作用域內就是全局的,且僅在文件作用域內。

使用建議

全局變量在某一個源文件中定義,其余源文件若要使用,將外部聲明 extern 寫在頭文件中,源文件包含這個頭文件。如下:

// a.cpp
int nums;  // 全局變量定義
// out.h
extern int nums;	//外部變量聲明
// b.cpp
#include "out.h" // 相當於聲明了外部變量
void set_nums(int val){
	nums = val;
}

g++ a.cpp b.cpp

更加深入-全局變量

如果在 a.h 中變量定義時,定義為 “弱定義”,那么是能達到預期的目標。

int  A __attribute__((weak));

Linux gcc

注意:僅在 gcc 下正確,換做 g++ 同樣報錯(重復定義)。

在編譯時,編譯器向匯編器輸出每個全局符號,或者是強(strong)或者是弱(weak),而編譯器把這個信息隱含地編碼在可重定位目標文件的符號表里。函數已初始化的全局變量強(strong)符號未初始化的全局變量弱(weak)符號

根據弱符號的定義,Linux鏈接器使用下面的規則來處理多重定義的符號名:

  • 規則1:不允許有多個同名的強符號。
  • 規則2:如果有一個強符號和多個弱符號同名,那么選擇強符號。
  • 規則3:若干有多個弱符號同名,那么從這些弱符號中任意選擇一個。
// bar.c
#include<stdio.h>
int x;
void f(){
//      std::cout << "begin f() x = " << x << std::endl;      
        printf("begin f() x = %d\n", x);
        x = 654321;
}
// foo.c
#include<stdio.h>
int x  = 12345; // 已初始化 強符號
void f(); 
int main(){
        f();
//      std::cout << "after f() x = " << x << std::endl;
        printf("after f() x = %d\n", x);
        return 0;
}
[root@localhost 7]# gcc foo.c bar.c 
[root@localhost 7]# ./a.out 
begin f() x = 12345
after f() x = 654321


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM