複製鏈接
請複製以下鏈接發送給好友

存儲級別關鍵字

鎖定
存儲級別關鍵字共有六個:auto register volatile static extern const。
中文名
存儲級別關鍵字
共    有
六個
auto registe

目錄

存儲級別關鍵字簡介

下邊分別介紹:
1. auto :聲明自動變量 一般不使用
2. register:聲明寄存器變量
3. const :聲明只讀變量
4. volatile:説明變量在程序執行中可被隱含地改變
5. static :聲明靜態變量
6. extern:聲明變量是在其他文件正聲明(也可以看做是引用變量)
下邊詳細介紹各關鍵字用法以及例子程序

存儲級別關鍵字Auto

auto是缺省的存儲類型,當你定義了變量以後系統就會為它分配內存。無論你是否使用它都是存在於內存中的

存儲級別關鍵字Register

register是寄存器變量,在CPU裏有“寄存器”這個東西,這個東西比內存更靠近CPU,所以速度更快。但是它是很小的,通常只有幾個字節,所以只有在數據量很小,而且使用頻繁的情況下才使用。既然是寄存器變量,所以它存放在寄存器中,不佔用內存單元

存儲級別關鍵字Const

const修飾符可以把對象轉變成常數對象,什麼意思呢?意思就就是説利用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這個指針所指向的內存地址卻不能修改指向對象的值。
如果你在代碼後加上*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的值是不被允許編譯的!
所以結合上面的兩點所説,把代碼修改成如下形式後就可以必然在程序的任意的地方修改對象a的值或者是指針pi的地址了,下面的這種寫法常被用語函數的形式參數,這樣可以保證對象不會在函數內被改變值!
#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. 全局靜態變量
全局變量之前加上關鍵字static,全局變量就被定義成為一個全局靜態變量
1、內存中的位置:靜態存儲區(靜態存儲區在整個程序運行期間都存在)
2、初始化:未經初始化的全局靜態變量會被程序自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)
3、作用域:全局靜態變量在聲明他的文件之外是不可見的。準確地講從定義之處開始到文件結尾。
看下面關於作用域的程序:
//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. 局部靜態變量
局部變量之前加上關鍵字static,局部變量就被定義成為一個局部靜態變量
1、內存中的位置:靜態存儲區
2、初始化:未經初始化的全局靜態變量會被程序自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)
3、作用域:作用域仍為局部作用域,當定義它的函數或者語句塊結束的時候,作用域隨之結束。
注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置,從原來的棧中存放改為靜態存儲區。但是局部靜態變量在離開作用域之後,並沒有被銷燬,而是仍然駐留在內存當中,直到程序結束,只不過我們不能再對他進行訪問。
當static用來修飾全局變量的時候,它就改變了全局變量的作用域(在聲明他的文件之外是不可見的),但是沒有改變它的存放位置,還是在靜態存儲區中。
在函數的返回類型前加上關鍵字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、初始化數據段(數據段)——在程序中所有賦了初值的全局變量,存放在這裏。
3、非初始化數據段(bss段)——在程序中沒有初始化的全局變量;內核將此段初始化為0。
4、棧——增長方向:自頂向下增長;自動變量以及每次函數調用時所需要保存的信息(返回地址;環境信息)。
5、堆——動態存儲分。
|-----------|
| |
|-----------|
| 棧 |
|-----------|
| | |
| |/ |
| |
| |
| /| |
| | |
|-----------|
| 堆 |
|-----------|
| 未初始化 |
|-----------|
| 初始化 |
|-----------|
| 正文段 |
|-----------|

存儲級別關鍵字Extern

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 */