基于C++全局變量的聲明與定義的詳解
更新時(shí)間:2013年05月30日 17:40:23 作者:
本篇文章是對(duì)C++全局變量的聲明與定義進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
(1)編譯單元(模塊)
在VC或VS上編寫(xiě)完代碼,點(diǎn)擊編譯按鈕準(zhǔn)備生成exe文件時(shí),編譯器做了兩步工作:
第一步,將每個(gè).cpp(.c)和相應(yīng)的.h文件編譯成obj文件;
第二步,將工程中所有的obj文件進(jìn)行LINK,生成最終.exe文件。
那么,錯(cuò)誤可能在兩個(gè)地方產(chǎn)生:
一個(gè),編譯時(shí)的錯(cuò)誤,這個(gè)主要是語(yǔ)法錯(cuò)誤;
一個(gè),鏈接時(shí)的錯(cuò)誤,主要是重復(fù)定義變量等。
編譯單元指在編譯階段生成的每個(gè)obj文件。
一個(gè)obj文件就是一個(gè)編譯單元。
一個(gè).cpp(.c)和它相應(yīng)的.h文件共同組成了一個(gè)編譯單元。
一個(gè)工程由很多編譯單元組成,每個(gè)obj文件里包含了變量存儲(chǔ)的相對(duì)地址等。
(2)聲明與定義
函數(shù)或變量在聲明時(shí),并沒(méi)有給它實(shí)際的物理內(nèi)存空間,它有時(shí)候可保證你的程序編譯通過(guò);
函數(shù)或變量在定義時(shí),它就在內(nèi)存中有了實(shí)際的物理空間。
如果你在編譯單元中引用的外部變量沒(méi)有在整個(gè)工程中任何一個(gè)地方定義的話,那么即使它在編譯時(shí)可以通過(guò),在連接時(shí)也會(huì)報(bào)錯(cuò),因?yàn)槌绦蛟趦?nèi)存中找不到這個(gè)變量。
函數(shù)或變量可以聲明多次,但定義只能有一次。
(3) extern作用
作用一:當(dāng)它與"C"一起連用時(shí),如extern "C" void fun(int a, int b);,則編譯器在編譯fun這個(gè)函數(shù)名時(shí)按C的規(guī)則去翻譯相應(yīng)的函數(shù)名而不是C++的。
作用二:當(dāng)它不與"C"在一起修飾變量或函數(shù)時(shí),如在頭文件中,extern int g_nNum;,它的作用就是聲明函數(shù)或變量的作用范圍的關(guān)鍵字,其聲明的函數(shù)和變量可以在本編譯單元或其他編譯單元中使用。
即B編譯單元要引用A編譯單元中定義的全局變量或函數(shù)時(shí),B編譯單元只要包含A編譯單元的頭文件即可,在編譯階段,B編譯單元雖然找不到該函數(shù)或變量,但它不會(huì)報(bào)錯(cuò),它會(huì)在鏈接時(shí)從A編譯單元生成的目標(biāo)代碼中找到此函數(shù)。
(4)全局變量(extern)
有兩個(gè)類都需要使用共同的變量,我們將這些變量定義為全局變量。比如,res.h和res.cpp分別來(lái)聲明和定義全局變量,類ProducerThread和ConsumerThread來(lái)使用全局變量。(以下是QT工程代碼)
/**********res.h聲明全局變量************/
#pragma once
#include <QSemaphore>
const int g_nDataSize = 1000; // 生產(chǎn)者生產(chǎn)的總數(shù)據(jù)量
const int g_nBufferSize = 500; // 環(huán)形緩沖區(qū)的大小
extern char g_szBuffer[]; // 環(huán)形緩沖區(qū)
extern QSemaphore g_qsemFreeBytes; // 控制環(huán)形緩沖區(qū)的空閑區(qū)(指生產(chǎn)者還沒(méi)填充數(shù)據(jù)的區(qū)域,或者消費(fèi)者已經(jīng)讀取過(guò)的區(qū)域)
extern QSemaphore g_qsemUsedBytes; // 控制環(huán)形緩沖區(qū)中的使用區(qū)(指生產(chǎn)者已填充數(shù)據(jù),但消費(fèi)者沒(méi)有讀取的區(qū)域)
/**************************/
上述代碼中g(shù)_nDataSize、g_nBufferSize為全局常量,其他為全局變量。
/**********res.cpp定義全局變量************/
#pragma once
#include "res.h"
// 定義全局變量
char g_szBuffer[g_nBufferSize];
QSemaphore g_qsemFreeBytes(g_nBufferSize);
QSemaphore g_qsemUsedBytes;
/**************************/
在其他編譯單元中使用全局變量時(shí)只要包含其所在頭文件即可。
/**********類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[];。
但是這樣做很不好,因?yàn)槟銦o(wú)法使用#include "res.h"(使用它,若達(dá)到兩次及以上,就出現(xiàn)重定義錯(cuò)誤;注:即使在res.h中加#pragma once,或#ifndef也會(huì)出現(xiàn)重復(fù)定義,因?yàn)槊總€(gè)編譯單元是單獨(dú)的,都會(huì)對(duì)它各自進(jìn)行定義),那么res.h聲明的其他函數(shù)或變量,你也就無(wú)法使用了,除非也都用extern修飾,這樣太麻煩,所以還是推薦使用.h中聲明,.cpp中定義的做法。
(5)靜態(tài)全局變量(static)
注意使用static修飾變量,就不能使用extern來(lái)修飾,即static和extern不可同時(shí)出現(xiàn)。
static修飾的全局變量的聲明與定義同時(shí)進(jìn)行,即當(dāng)你在頭文件中使用static聲明了全局變量,同時(shí)它也被定義了。
static修飾的全局變量的作用域只能是本身的編譯單元。在其他編譯單元使用它時(shí),只是簡(jiǎn)單的把其值復(fù)制給了其他編譯單元,其他編譯單元會(huì)另外開(kāi)個(gè)內(nèi)存保存它,在其他編譯單元對(duì)它的修改并不影響本身在定義時(shí)的值。即在其他編譯單元A使用它時(shí),它所在的物理地址,和其他編譯單元B使用它時(shí),它所在的物理地址不一樣,A和B對(duì)它所做的修改都不能傳遞給對(duì)方。
多個(gè)地方引用靜態(tài)全局變量所在的頭文件,不會(huì)出現(xiàn)重定義錯(cuò)誤,因?yàn)樵诿總€(gè)編譯單元都對(duì)它開(kāi)辟了額外的空間進(jìn)行存儲(chǔ)。
以下是Windows控制臺(tái)應(yīng)用程序代碼示例:
/***********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;
}
/************************/
運(yùn)行結(jié)果如下:

按我們的直觀印象,認(rèn)為fun1()和fun2()輸出的結(jié)果都為abcdef,可實(shí)際上fun2()輸出的確是初始值。然后我們?cè)俑櫿{(diào)試,發(fā)現(xiàn)res、test1、test2中g(shù)_szBuffer的地址都不一樣,分別為0x0041a020、0x0041a084、0x0041a040,這就解釋了為什么不一樣。
注:一般定義static 全局變量時(shí),都把它放在.cpp文件中而不是.h文件中,這樣就不會(huì)給其他編譯單元造成不必要的信息污染。
(6)全局常量(const)
const單獨(dú)使用時(shí),其特性與static一樣(每個(gè)編譯單元中地址都不一樣,不過(guò)因?yàn)槭浅A?,也不能修改,所以就沒(méi)有多大關(guān)系)。
const與extern一起使用時(shí),其特性與extern一樣。
[code]
extern const char g_szBuffer[]; //寫(xiě)入 .h中
const char g_szBuffer[] = "123456"; // 寫(xiě)入.cpp中
[/code
在VC或VS上編寫(xiě)完代碼,點(diǎn)擊編譯按鈕準(zhǔn)備生成exe文件時(shí),編譯器做了兩步工作:
第一步,將每個(gè).cpp(.c)和相應(yīng)的.h文件編譯成obj文件;
第二步,將工程中所有的obj文件進(jìn)行LINK,生成最終.exe文件。
那么,錯(cuò)誤可能在兩個(gè)地方產(chǎn)生:
一個(gè),編譯時(shí)的錯(cuò)誤,這個(gè)主要是語(yǔ)法錯(cuò)誤;
一個(gè),鏈接時(shí)的錯(cuò)誤,主要是重復(fù)定義變量等。
編譯單元指在編譯階段生成的每個(gè)obj文件。
一個(gè)obj文件就是一個(gè)編譯單元。
一個(gè).cpp(.c)和它相應(yīng)的.h文件共同組成了一個(gè)編譯單元。
一個(gè)工程由很多編譯單元組成,每個(gè)obj文件里包含了變量存儲(chǔ)的相對(duì)地址等。
(2)聲明與定義
函數(shù)或變量在聲明時(shí),并沒(méi)有給它實(shí)際的物理內(nèi)存空間,它有時(shí)候可保證你的程序編譯通過(guò);
函數(shù)或變量在定義時(shí),它就在內(nèi)存中有了實(shí)際的物理空間。
如果你在編譯單元中引用的外部變量沒(méi)有在整個(gè)工程中任何一個(gè)地方定義的話,那么即使它在編譯時(shí)可以通過(guò),在連接時(shí)也會(huì)報(bào)錯(cuò),因?yàn)槌绦蛟趦?nèi)存中找不到這個(gè)變量。
函數(shù)或變量可以聲明多次,但定義只能有一次。
(3) extern作用
作用一:當(dāng)它與"C"一起連用時(shí),如extern "C" void fun(int a, int b);,則編譯器在編譯fun這個(gè)函數(shù)名時(shí)按C的規(guī)則去翻譯相應(yīng)的函數(shù)名而不是C++的。
作用二:當(dāng)它不與"C"在一起修飾變量或函數(shù)時(shí),如在頭文件中,extern int g_nNum;,它的作用就是聲明函數(shù)或變量的作用范圍的關(guān)鍵字,其聲明的函數(shù)和變量可以在本編譯單元或其他編譯單元中使用。
即B編譯單元要引用A編譯單元中定義的全局變量或函數(shù)時(shí),B編譯單元只要包含A編譯單元的頭文件即可,在編譯階段,B編譯單元雖然找不到該函數(shù)或變量,但它不會(huì)報(bào)錯(cuò),它會(huì)在鏈接時(shí)從A編譯單元生成的目標(biāo)代碼中找到此函數(shù)。
(4)全局變量(extern)
有兩個(gè)類都需要使用共同的變量,我們將這些變量定義為全局變量。比如,res.h和res.cpp分別來(lái)聲明和定義全局變量,類ProducerThread和ConsumerThread來(lái)使用全局變量。(以下是QT工程代碼)
復(fù)制代碼 代碼如下:
/**********res.h聲明全局變量************/
#pragma once
#include <QSemaphore>
const int g_nDataSize = 1000; // 生產(chǎn)者生產(chǎn)的總數(shù)據(jù)量
const int g_nBufferSize = 500; // 環(huán)形緩沖區(qū)的大小
extern char g_szBuffer[]; // 環(huán)形緩沖區(qū)
extern QSemaphore g_qsemFreeBytes; // 控制環(huán)形緩沖區(qū)的空閑區(qū)(指生產(chǎn)者還沒(méi)填充數(shù)據(jù)的區(qū)域,或者消費(fèi)者已經(jīng)讀取過(guò)的區(qū)域)
extern QSemaphore g_qsemUsedBytes; // 控制環(huán)形緩沖區(qū)中的使用區(qū)(指生產(chǎn)者已填充數(shù)據(jù),但消費(fèi)者沒(méi)有讀取的區(qū)域)
/**************************/
上述代碼中g(shù)_nDataSize、g_nBufferSize為全局常量,其他為全局變量。
復(fù)制代碼 代碼如下:
/**********res.cpp定義全局變量************/
#pragma once
#include "res.h"
// 定義全局變量
char g_szBuffer[g_nBufferSize];
QSemaphore g_qsemFreeBytes(g_nBufferSize);
QSemaphore g_qsemUsedBytes;
/**************************/
在其他編譯單元中使用全局變量時(shí)只要包含其所在頭文件即可。
復(fù)制代碼 代碼如下:
/**********類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[];。
但是這樣做很不好,因?yàn)槟銦o(wú)法使用#include "res.h"(使用它,若達(dá)到兩次及以上,就出現(xiàn)重定義錯(cuò)誤;注:即使在res.h中加#pragma once,或#ifndef也會(huì)出現(xiàn)重復(fù)定義,因?yàn)槊總€(gè)編譯單元是單獨(dú)的,都會(huì)對(duì)它各自進(jìn)行定義),那么res.h聲明的其他函數(shù)或變量,你也就無(wú)法使用了,除非也都用extern修飾,這樣太麻煩,所以還是推薦使用.h中聲明,.cpp中定義的做法。
(5)靜態(tài)全局變量(static)
注意使用static修飾變量,就不能使用extern來(lái)修飾,即static和extern不可同時(shí)出現(xiàn)。
static修飾的全局變量的聲明與定義同時(shí)進(jìn)行,即當(dāng)你在頭文件中使用static聲明了全局變量,同時(shí)它也被定義了。
static修飾的全局變量的作用域只能是本身的編譯單元。在其他編譯單元使用它時(shí),只是簡(jiǎn)單的把其值復(fù)制給了其他編譯單元,其他編譯單元會(huì)另外開(kāi)個(gè)內(nèi)存保存它,在其他編譯單元對(duì)它的修改并不影響本身在定義時(shí)的值。即在其他編譯單元A使用它時(shí),它所在的物理地址,和其他編譯單元B使用它時(shí),它所在的物理地址不一樣,A和B對(duì)它所做的修改都不能傳遞給對(duì)方。
多個(gè)地方引用靜態(tài)全局變量所在的頭文件,不會(huì)出現(xiàn)重定義錯(cuò)誤,因?yàn)樵诿總€(gè)編譯單元都對(duì)它開(kāi)辟了額外的空間進(jìn)行存儲(chǔ)。
以下是Windows控制臺(tái)應(yīng)用程序代碼示例:
復(fù)制代碼 代碼如下:
/***********res.h**********/
static char g_szBuffer[6] = "12345";
void fun();
/************************/
復(fù)制代碼 代碼如下:
/***********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;
}
/************************/
復(fù)制代碼 代碼如下:
/***********test1.h**********/
void fun1();
/************************/
復(fù)制代碼 代碼如下:
/***********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;
}
/************************/
復(fù)制代碼 代碼如下:
/***********test2.h**********/
void fun2();
/************************/
復(fù)制代碼 代碼如下:
/***********test2.cpp**********/
#include "test2.h"
#include "res.h"
#include <iostream>
using namespace std;
void fun2() {
cout<<g_szBuffer<<endl;
}
/************************/
復(fù)制代碼 代碼如下:
/***********main.cpp**********/
#include "test1.h"
#include "test2.h"
int main() {
fun1();
fun2();
system("PAUSE");
return 0;
}
/************************/
運(yùn)行結(jié)果如下:

按我們的直觀印象,認(rèn)為fun1()和fun2()輸出的結(jié)果都為abcdef,可實(shí)際上fun2()輸出的確是初始值。然后我們?cè)俑櫿{(diào)試,發(fā)現(xiàn)res、test1、test2中g(shù)_szBuffer的地址都不一樣,分別為0x0041a020、0x0041a084、0x0041a040,這就解釋了為什么不一樣。
注:一般定義static 全局變量時(shí),都把它放在.cpp文件中而不是.h文件中,這樣就不會(huì)給其他編譯單元造成不必要的信息污染。
(6)全局常量(const)
const單獨(dú)使用時(shí),其特性與static一樣(每個(gè)編譯單元中地址都不一樣,不過(guò)因?yàn)槭浅A?,也不能修改,所以就沒(méi)有多大關(guān)系)。
const與extern一起使用時(shí),其特性與extern一樣。
[code]
extern const char g_szBuffer[]; //寫(xiě)入 .h中
const char g_szBuffer[] = "123456"; // 寫(xiě)入.cpp中
[/code
相關(guān)文章
C++ GDI實(shí)現(xiàn)圖片格式轉(zhuǎn)換
GDI+(Graphics Device Interface Plus)是一種用于圖形繪制和圖像處理的應(yīng)用程序編程接口(API),在Windows平臺(tái)上廣泛使用,本文就來(lái)介紹一下如何使用GDI實(shí)現(xiàn)圖片格式轉(zhuǎn)換吧2023-12-12深入解析C++編程中__alignof 與__uuidof運(yùn)算符的使用
這篇文章主要介紹了C++編程中__alignof 與__uuidof運(yùn)算符的使用,是C++入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-01-01深度解析三個(gè)常見(jiàn)的C語(yǔ)言內(nèi)存函數(shù)
這篇文章主要深度解析了三個(gè)常見(jiàn)的C語(yǔ)言內(nèi)存函數(shù)memcpy,memmove,memcmp,所以本文將對(duì)memcpy,memmove,memcmp 三個(gè)函數(shù)進(jìn)行詳解和模擬實(shí)現(xiàn),需要的朋友可以參考下2023-07-07詳解C++ 多態(tài)的兩種形式(靜態(tài)、動(dòng)態(tài))
這篇文章主要介紹了C++ 多態(tài)的兩種形式,幫助大家更好的理解和學(xué)習(xí)c++,感興趣的朋友可以了解下2020-08-08淺析C語(yǔ)言調(diào)試器GDB和LLDB的使用方法
這篇文章主要介紹了C語(yǔ)言調(diào)試器GDB和LLDB的使用方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12