基於C++全域變數的聲明與定義的詳解

來源:互聯網
上載者:User

(1)編譯單元(模組)
在VC或VS上編寫完代碼,點擊編譯按鈕準備產生exe檔案時,編譯器做了兩步工作:
第一步,將每個.cpp(.c)和相應的.h檔案編譯成obj檔案;
第二步,將工程中所有的obj檔案進行LINK,產生最終.exe檔案。
那麼,錯誤可能在兩個地方產生:
一個,編譯時間的錯誤,這個主要是語法錯誤;
一個,連結時的錯誤,主要是重複定義變數等。
編譯單元指在編譯階段產生的每個obj檔案。
一個obj檔案就是一個編譯單元。
一個.cpp(.c)和它相應的.h檔案共同組成了一個編譯單元。
一個工程由很多編譯單元組成,每個obj檔案裡包含了變數儲存的相對位址等。
(2)聲明與定義
函數或變數在聲明時,並沒有給它實際的實體記憶體空間,它有時候可保證你的程式編譯通過;
函數或變數在定義時,它就在記憶體中有了實際的物理空間。

如果你在編譯單元中引用的外部變數沒有在整個工程中任何一個地方定義的話,那麼即使它在編譯時間可以通過,在串連時也會報錯,因為程式在記憶體中找不到這個變數。
函數或變數可以聲明多次,但定義只能有一次。
(3) extern作用
作用一:
當它與"C"一起連用時,如extern "C" void fun(int a, int b);,則編譯器在編譯fun這個函數名時按C的規則去翻譯相應的函數名而不是C++的。
作用二:當它不與"C"在一起修飾變數或函數時,如在標頭檔中,extern int g_nNum;,它的作用就是聲明函數或變數的作用範圍的關鍵字,其聲明的函數和變數可以在本編譯單元或其他編譯單元中使用。
即B編譯單元要引用A編譯單元中定義的全域變數或函數時,B編譯單元只要包含A編譯單元的標頭檔即可,在編譯階段,B編譯單元雖然找不到該函數或變數,但它不會報錯,它會在連結時從A編譯單元產生的目標代碼中找到此函數。
(4)全域變數(extern)
有兩個類都需要使用共同的變數,我們將這些變數定義為全域變數。比如,res.h和res.cpp分別來聲明和定義全域變數,類ProducerThread和ConsumerThread來使用全域變數。(以下是QT工程代碼)複製代碼 代碼如下:/**********res.h聲明全域變數************/
#pragma once

#include <QSemaphore>

const int g_nDataSize = 1000; // 生產者生產的總資料量
const int g_nBufferSize = 500; // 環形緩衝區的大小

extern char g_szBuffer[]; // 環形緩衝區
extern QSemaphore g_qsemFreeBytes; // 控制環形緩衝區的空閑區(指生產者還沒填充資料的地區,或者消費者已經讀取過的地區)
extern QSemaphore g_qsemUsedBytes; // 控制環形緩衝區中的使用區(指生產者已填充資料,但消費者沒有讀取的地區)
/**************************/

上述代碼中g_nDataSize、g_nBufferSize為全域常量,其他為全域變數。複製代碼 代碼如下:/**********res.cpp定義全域變數************/
#pragma once
#include "res.h"

// 定義全域變數
char g_szBuffer[g_nBufferSize];
QSemaphore g_qsemFreeBytes(g_nBufferSize);
QSemaphore g_qsemUsedBytes;
/**************************/

在其他編譯單元中使用全域變數時只要包含其所在標頭檔即可。複製代碼 代碼如下:/**********類ConsumerThread使用全域變數************/
#include "consumerthread.h"
#include "res.h"
#include <QDebug>
ConsumerThread::ConsumerThread(QObject* parent)
: QThread(parent) {
}
ConsumerThread::ConsumerThread() {

}
ConsumerThread::~ConsumerThread() {
}
void ConsumerThread::run() {
for (int i = 0; i < g_nDataSize; i++) {
g_qsemUsedBytes.acquire();
qDebug()<<"Consumer "<<g_szBuffer[i % g_nBufferSize];
g_szBuffer[i % g_nBufferSize] = ' ';
g_qsemFreeBytes.release();
}
qDebug()<<"&&Consumer Over";
}
/**************************/

也可以把全域變數的聲明和定義放在一起,這樣可以防止忘記了定義,如上面的extern char g_szBuffer[g_nBufferSize]; 然後把引用它的檔案中的#include "res.h"換成extern char g_szBuffer[];。
但是這樣做很不好,因為你無法使用#include "res.h"(使用它,若達到兩次及以上,就出現重定義錯誤;註:即使在res.h中加#pragma once,或#ifndef也會出現重複定義,因為每個編譯單元是單獨的,都會對它各自進行定義),那麼res.h聲明的其他函數或變數,你也就無法使用了,除非也都用extern修飾,這樣太麻煩,所以還是推薦使用.h中聲明,.cpp中定義的做法。
(5)靜態全域變數(static)
注意使用static修飾變數,就不能使用extern來修飾,即staticextern不可同時出現。
static修飾的全域變數的聲明與定義同時進行,即當你在標頭檔中使用static聲明了全域變數,同時它也被定義了。
static修飾的全域變數的範圍只能是本身的編譯單元。在其他編譯單元使用它時,只是簡單的把其值複製給了其他編譯單元,其他編譯單元會另外開個記憶體儲存它,在其他編譯單元對它的修改並不影響本身在定義時的值。即在其他編譯單元A使用它時,它所在的物理地址,和其他編譯單元B使用它時,它所在的物理地址不一樣,A和B對它所做的修改都不能傳遞給對方。
多個地方引用靜態全域變數所在的標頭檔,不會出現重定義錯誤,因為在每個編譯單元都對它開闢了額外的空間進行儲存。
以下是Windows控制台應用程式程式碼範例:複製代碼 代碼如下:/***********res.h**********/
static char g_szBuffer[6] = "12345";
void fun();
/************************/

複製代碼 代碼如下:/***********res.cpp**********/
#include "res.h"
#include <iostream>
using namespace std;

void fun() {
for (int i = 0; i < 6; i++) {
g_szBuffer[i] = 'A' + i;
}
cout<<g_szBuffer<<endl;
}
/************************/

複製代碼 代碼如下:/***********test1.h**********/
void fun1();
/************************/

複製代碼 代碼如下:/***********test1.cpp**********/
#include "test1.h"
#include "res.h"
#include <iostream>
using namespace std;

void fun1() {
fun();

for (int i = 0; i < 6; i++) {
g_szBuffer[i] = 'a' + i;
}
cout<<g_szBuffer<<endl;
}
/************************/

複製代碼 代碼如下:/***********test2.h**********/
void fun2();
/************************/

複製代碼 代碼如下:/***********test2.cpp**********/
#include "test2.h"
#include "res.h"
#include <iostream>
using namespace std;

void fun2() {
cout<<g_szBuffer<<endl;
}
/************************/

複製代碼 代碼如下:/***********main.cpp**********/
#include "test1.h"
#include "test2.h"

int main() {
fun1();
fun2();

system("PAUSE");
return 0;
}
/************************/

運行結果如下:

按我們的直觀印象,認為fun1()和fun2()輸出的結果都為abcdef,可實際上fun2()輸出的確是初始值。然後我們再跟蹤調試,發現res、test1、test2中g_szBuffer的地址都不一樣,分別為0x0041a020、0x0041a084、0x0041a040,這就解釋了為什麼不一樣。
註:一般定義static 全域變數時,都把它放在.cpp檔案中而不是.h檔案中,這樣就不會給其他編譯單元造成不必要的資訊汙染。
(6)全域常量(const)
const單獨使用時,其特性與static一樣(每個編譯單元中地址都不一樣,不過因為是常量,也不能修改,所以就沒有多大關係)。
const與extern一起使用時,其特性與extern一樣。
[code]
extern const char g_szBuffer[]; //寫入 .h中
const char g_szBuffer[] = "123456"; // 寫入.cpp中
[/code

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.