存儲級別關鍵字共有六個:auto register volatile static extern const。
基本介紹
- 中文名:存儲級別關鍵字
- 外文名:無
- 共有:六個
- 如:auto registe
簡介,Auto,Register,Const,Volatile,Static,Extern,
簡介
下邊分別介紹:
1. auto :聲明自動變數 一般不使用
2. register:聲明暫存器變數
3. const :聲明唯讀變數
4. volatile:說明變數在程式執行中可被隱含地改變
5. static :聲明靜態變數
6. extern:聲明變數是在其他檔案正聲明(也可以看做是引用變數)
下邊詳細介紹各關鍵字用法以及例子程式
Auto
auto是預設的存儲類型,當你定義了變數以後系統就會為它分配記憶體。無論你是否使用它都是存在於記憶體中的
Register
register是暫存器變數,在CPU里有“暫存器”這個東西,這個東西比記憶體更靠近CPU,所以速度更快。但是它是很小的,通常只有幾個位元組,所以只有在數據量很小,而且使用頻繁的情況下才使用。既然是暫存器變數,所以它存放在暫存器中,不占用記憶體單元
Const
使用方法是:
const int a=1;//這裡定義了一個int類型的const常數變數a;
但就於指針來說const仍然是起作用的,以下有兩點要十分注意,因為下面的兩個問題很容易混淆!
我們來看一個如下的例子:
#include <iostream>
using namespace std;
void main(void)
{
const int a=10;
int b=20;
const int *pi;
pi=a;
cout <<*pi << "|" << a <<endl;
pi=b;
cout <<*pi << "|" <<b <<endl;
cin.get();
}
上面的代碼中最重要的一句是 const int *pi
這句從右向座讀作:pi是一個指向int類型的,被定義成const的對象的指針;
如果你在代碼後加上*pi=10;這樣的賦值操作是不被允許編譯的!
好,看了上面的兩個例子你對const有了一個基本的認識了,那么我們接下來看一個很容易混淆的用法!
請看如下的代碼
#include <iostream>
using namespace std;
void main(void)
{
int a=10;
const int *const pi=a;
cout <<*pi << "|" <<a <<endl;
cin.get();
}
上面的代碼中最重要的一句是 const int *const pi
這句從右向座讀作:pi是一個指向int類型對象的const指針;
這樣的一種聲明方式的作用是你既不可以修改pi所指向對象的記憶體地址也不能利用指針的解引用方式修改對象的值,也就是用*pi=10這樣的方式;
所以你如果在最後加上*pi=20,想試圖通過這樣的方式修改對象a的值是不被允許編譯的!
#include <iostream>
using namespace std;
void main(void)
{
const int a=10;//這句和上面不同,請注意!
const int *const pi=a;
cout <<*pi << "|" <<a <<endl;
cin.get();
}
Volatile
volatile 影響編譯器編譯的結果,指出,volatile 變數是隨時可能發生變化的,與volatile變數有關的運算,不要進行編譯最佳化,以免出錯,(VC++ 在產生release版可執行碼時會進行編譯最佳化,加volatile關鍵字的變數有關的運算,將不進行編譯最佳化。)。
例如:
volatile inti=10;
int j = i;
...
int k = i;
volatile 告訴編譯器i是隨時可能發生變化的,每次使用它的時候必須從i的地址中讀取,因而編譯器生成的可執行碼會重新從i的地址讀取數據放在k中。
而最佳化做法是,由於編譯器發現兩次從i讀數據的代碼之間的代碼沒有對i進行過操作,它會自動把上次讀的數據放在k中。而不是重新從i裡面讀。這樣以來,如果i是一個暫存器變數或者表示一個連線埠數據就容易出錯,所以說volatile可以保證對特殊地址的穩定訪問,不會出錯。
Static
面向過程程式設計中的static1. 全局靜態變數
2)初始化:未經初始化的全局靜態變數會被程式自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)
看下面關於作用域的程式:
//teststatic1.c
void display();
extern int n;
int main()
{
n = 20;
printf("%dn",n);
display();
return 0;
}
//teststatic2.c
static int n; //定義全局靜態變數,自動初始化為0,僅在本檔案中可見
void display()
{
n++;
printf("%dn",n);
}
檔案分別編譯通過,但link的時候teststatic2.c中的變數n找不到定義,產生錯誤。
定義全局靜態變數的好處:
<1>不會被其他檔案所訪問,修改
<2>其他檔案中可以使用相同名字的變數,不會發生衝突。
2. 局部靜態變數
1)記憶體中的位置:靜態存儲區
2)初始化:未經初始化的全局靜態變數會被程式自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)
3)作用域:作用域仍為局部作用域,當定義它的函式或者語句塊結束的時候,作用域隨之結束。
註:當static用來修飾局部變數的時候,它就改變了局部變數的存儲位置,從原來的棧中存放改為靜態存儲區。但是局部靜態變數在離開作用域之後,並沒有被銷毀,而是仍然駐留在記憶體當中,直到程式結束,只不過我們不能再對他進行訪問。
3. 靜態函式
在函式的返回類型前加上關鍵字static,函式就被定義成為靜態函式。
函式的定義和聲明默認情況下是extern的,但靜態函式只是在聲明他的檔案當中可見,不能被其他檔案所用。
例如:
//teststatic1.c
void display();
static void staticdis();
int main()
{
display();
staticdis();
renturn 0;
}
//teststatic2.c
void display()
{
staticdis();
printf("display() has been called n");
}
static void staticdis()
{
printf("staticDis() has been calledn");
}
檔案分別編譯通過,但是連線的時候找不到函式staticdis()的定義,產生錯誤。
實際上編譯也未過,vc2003報告teststatic1.c中靜態函式staticdis已聲明但未定義 ;by imjacob
定義靜態函式的好處:
<1> 其他檔案中可以定義相同名字的函式,不會發生衝突
<2> 靜態函式不能被其他檔案所用。
存儲說明符auto,register,extern,static,對應兩種存儲期:自動存儲期和靜態存儲期。
auto和register對應自動存儲期。具有自動存儲期的變數在進入聲明該變數的程式塊時被建立,它在該程式塊活動時存在,退出該程式塊時撤銷。
關鍵字extern和static用來說明具有靜態存儲期的變數和函式。用static聲明的局部變數具有靜態存儲持續期(static storageduration),或靜態範圍(static extent)。雖然他的值在函式調用之間保持有效,但是其名字的可視性仍限制在其局部域內。靜態局部對象在程式執行到該對象的聲明處時被首次初始化。
由於static變數的以上特性,可實現一些特定功能。
1. 統計次數功能
聲明函式的一個局部變數,並設為static類型,作為一個計數器,這樣函式每次被調用的時候就可以進行計數。這是統計函式被調用次數的最好的辦法,因為這個變數是和函式息息相關的,而函式可能在多個不同的地方被調用,所以從調用者的角度來統計比較困難。代碼如下:
void count();
int main()
{
int i;
for (i = 1; i <= 3; i++)
count();
return 0;
}
void count()
{
static num = 0;
num++;
printf(" I have been called %d",num,"timesn");
}
輸出結果為:
I have been called 1 times.
I have been called 2 times.
I have been called 3 times.
慘痛教訓:
假設在test.h中定義了一個static bool g_test=false;
若test1.c和test2.c都包含test.h,則test1.c和test2.c分別生成兩份g_test,在test1.c 中置g_test=true,而test2.c中仍然為false並未改變!shit!!
C程式一直由下列部分組成: 1)正文段——CPU執行的機器指令部分;一個程式只有一個副本;唯讀,防止程式由於意外事故而修改自身指令;
2)初始化數據段(數據段)——在程式中所有賦了初值的全局變數,存放在這裡。
5)堆——動態存儲分。
|-----------|
| |
|-----------|
| 棧 |
|-----------|
| | |
| |/ |
| |
| |
| /| |
| | |
|-----------|
| 堆 |
|-----------|
| 未初始化 |
|-----------|
| 初始化 |
|-----------|
| 正文段 |
|-----------|
Extern
另外,extern也可用來進行連結指定。
問題:extern 變數
char a[6]; |
在另外一個檔案里用下列語句進行了聲明:
extern char *a; |
請問,這樣可以嗎?
答案與分析:
1)、不可以,程式運行時會告訴你非法訪問。原因在於,指向類型T的指針並不等價於類型T的數組。extern char *a聲明的是一個指針變數而不是字元數組,因此與實際的定義不同,從而造成運行時非法訪問。應該將聲明改為extern char a[ ]。
2)、例子分析如下,如果a[] = "abcd",則外部變數a=0x61626364 (abcd的ASCII碼值),*a顯然沒有意義,如下圖:
顯然a指向的空間(0x61626364)沒有意義,易出現非法記憶體訪問。
3)、在使用extern時候要嚴格對應聲明時的格式,在實際編程中,這樣的錯誤屢見不鮮。
4)、extern用在變數聲明中常常有這樣一個作用,你在*.c檔案中聲明了一個全局的變數,這個全局的變數如果要被引用,就放在*.h中並用extern來聲明。
問題:extern 函式1
常常見extern放在函式的前面成為函式聲明的一部分,那么,C語言的關鍵字extern在函式的聲明中起什麼作用?
答案與分析:
如果函式的聲明中帶有關鍵字extern,僅僅是暗示這個函式可能在別的源檔案里定義,沒有其它作用。即下述兩個函式聲明沒有明顯的區別:
extern int f(); 和int f(); |
當然,這樣的用處還是有的,就是在程式中取代include “*.h”來聲明函式,在一些複雜的項目中,我比較習慣在所有的函式聲明前添加extern修飾。
問題:extern函式2
當函式提供方單方面修改函式原型時,如果使用方不知情繼續沿用原來的extern申明,這樣編譯時編譯器不會報錯。但是在運行過程中,因為少了或者多了輸入參數,往往會照成系統錯誤,這種情況應該如何解決?
答案與分析:
如今業界針對這種情況的處理沒有一個很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供對外部接口的聲明,然後調用方include該頭檔案,從而省去extern這一步。以避免這種錯誤。
寶劍有雙鋒,對extern的套用,不同的場合應該選擇不同的做法。
問題:extern“C”
在C++環境下使用C函式的時候,常常會出現編譯器無法找到obj模組中的C函式定義,從而導致連結失敗的情況,應該如何解決這種情況呢?
答案與分析:
C++語言在編譯的時候為了解決函式的多態問題,會將函式名和參數聯合起來生成一個中間的函式名稱,而C語言則不會,因此會造成連結時找不到對應函式的情況,此時C函式就需要用extern “C”進行連結指定,這告訴編譯器,請保持我的名稱,不要給我生成用於連結的中間函式名。
下面是一個標準的寫法:
//在.h檔案的頭上
#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus */
…
…
//.h檔案結束的地方
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */