基本上每種語言都要討論這個話題,C語言也不例外,由於只有你徹底瞭解每一個變量或函數存儲方式、做用範圍和銷燬時間纔可能正確的使用這門語言。今天將着重介紹C語言中變量做用範圍、存儲方式、生命週期、做用域和可訪問性。數據結構
在C語言中變量從做用範圍包括全局變量和局部變量。全局變量在定義以後全部的函數中都可以使用,只要前面的代碼修改了,那麼後面的代碼中再使用就是修改後的值;局部變量的做用範圍通常在一個函數內部(一般在一對大括號{}內),外面的程序沒法訪問它,它卻能夠訪問外面的變量。函數
// // main.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int a=1; void changeValue(){ a=2; printf("a=%d\n",a); } int main(int argc, const char * argv[]) { int b=1; changeValue(); //結果:a=2 printf("a=%d,b=%d\n",a,b); //結果:a=2,b=1 ,由於changeValue修改了這個全局變量 return 0; }
C語言的強大之處在於它能直接操做內存(指針),可是要徹底熟悉它的操做方式咱們必需要弄清它的存儲方式。存儲變量的位置分爲:普通內存(靜態存儲區)、運行時堆棧(動態存儲區)、硬件寄存器(動態存儲區),固然這幾種存儲的效率是從低到高的。而根據存儲位置的不一樣在C語言中又能夠將變量依次分爲:靜態變量、自動變量、寄存器變量。ui
首先說一下存儲在普通內存中的靜態變量,全局變量和使用static聲明的局部變量都是靜態變量,在系統運行過程當中只初始化一次(在下面的例子中雖然變量b是局部變量,在外部沒法訪問,可是他的生命週期一直延續到程序結束,而變量c則在第一次執行完就釋放,第二次執行時從新建立)。編碼
// // 2.1.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int a=1; //全局變量存儲在靜態內存中,只初始化一次 void showMessage(){ static int b=1; //靜態變量存儲在靜態內存中,第二次調用不會再進行初始化 int c=1; ++b; a+=2; printf("a=%d,b=%d,c=%d\n",a,b,c); } int main(int argc, const char * argv[]) { showMessage(); //結果:a=3,b=2,c=1 showMessage(); //結果:a=5,b=3,c=1 return 0; }
被關鍵字auto修飾的局部變量是自動變量,可是auto關鍵字能夠省略,所以能夠得出結論:全部沒有被static修飾的局部變量都是自動變量。spa
// // 1.3.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> #include <stdlib.h> int main(int argc, const char * argv[]) { int a=1; auto int b=2; printf("a=%d,b=%d\n",a,b); //結果:a=1,b=2 ,a和b都是自動變量,auto能夠省略 //須要注意的是,上面的自動變量是存儲在棧中,其實還能夠存儲到堆中 char c[]="hello,world!"; long len=strlen(c)*sizeof(char)+1;//之因此加1是由於字符串後面默認有一個\0空操做符不計算在長度內 char *p=NULL;//能夠直接寫成:char *p; p=(char *)malloc(len);//分配指定的字節存放c中字符串,注意因爲malloc默認返回「void *」須要轉化 memset(p,0,len);//清空指向內存中的存儲內容,由於分配的內存是隨機的,若是不清空可能會由於垃圾數據產生沒必要要的麻煩 strcpy(p,c); printf("p=%s\n",p);//結果:p=hello,world! free(p);//釋放分配的空間 p=NULL;//注意讓p指向空,不然p將會是一個存儲一個無用地址的野指針 return 0; }
固然存儲自動變量的棧和堆實際上是兩個徹底不一樣的空間(雖然都在運行時有效的空間內):棧通常是程序自動分配,其存儲結果相似於數據結構中的棧,先進後出,程序結束時由編譯器自動釋放;而堆則是開發人員手動編碼分配,若是不進行手動釋放就只有等到程序運行完操做系統回收,其存儲結構相似於鏈表。在上面的代碼中p變量一樣是一個自動變量,一樣可使用auto修飾,只是它所指向的內容放在堆上(p自己存放在棧上)。操作系統
這裏說明幾點:malloc分配的空間在邏輯上連續,物理上未必連續;p必須手動釋放,不然直到程序運行結束它佔用的內存將一直被佔用;釋放p的過程只是把p指向的空間釋放掉,p中存放的地址並未釋放,須要手動設置爲NULL,不然這將是一個無用的野指針;.net
默認狀況下不管是自動變量仍是靜態變量它們都在內存中,不一樣之處就是自動變量放在一塊運行時分配的特殊內存中。可是寄存器變量倒是在硬件寄存器中,從物理上來講它和內存處在兩個徹底不一樣的硬件中。你們都是知道寄存器存儲空間很小,可是它的效率很高,那麼合理使用寄存器變量就至關重要了。什麼是寄存器變量呢?使用register修飾的int或char類型的非靜態局部變量是寄存器變量。沒錯,須要三個條件支撐:register修飾、必須是int或char類型、必須是非靜態局部變量。指針
除了存儲位置不一樣外,寄存器變量徹底符合自動變量的條件,所以它的生命週期實際上是和自動變量徹底同樣的,當函數運行結束後它就會被自動釋放。因爲寄存器空間珍貴,所以咱們須要合理使用寄存器變量,只有訪問度很高的變量咱們才考慮使用寄存器變量,若是過多的定義寄存器變量,當寄存器空間不夠用時會自動轉化爲自動變量。code
// // 1.3.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int main(int argc, const char * argv[]) { register int a=1; printf("a=%d\n",a); return 0; }
上面咱們說到變量的存儲類型,其實在C語言中還有兩種存儲類型:常量存儲區和代碼存儲區,分別用於存儲字符串常量、使用const修飾的全局變量以及二進制函數代碼。blog
在C語言中沒有其餘高級語言public、private等修飾符,來限定變量和函數的有效範圍,可是卻有兩個相似的關鍵字能達到相似的效果:extern和static。
咱們知道在C語言中變量的定義順序是有嚴格要求的,要使用變量則必須在使用以前定義,extern用於聲明一個已經存在的變量,這樣一來即便在後面定義一個變量只要前面聲明瞭,也一樣可使用。具體的細節經過下面的代碼相信你們均可以看明白:
// // 2.1.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> //若是在main函數下面定義了一個變量a,若是在main上面不進行聲明是沒法在main中使用a的; //一樣若是隻進行了extern聲明不進行定義同樣會報錯,由於extern並不負責定義變量a而僅僅是聲明一個已經定義過的變量; //固然若是說在main上面定義int a;去掉main下面的定義一樣是能夠的,至關於在上面定義,但若是兩個地方都定義a的話(main上面的extern去掉),則程序認爲上面的定義是聲明,只是省略了extern關鍵字; //第一種狀況,在下面定義,不進行聲明,報錯 int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; //第二種狀況,在上面定義,正確 int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } //第三種狀況,在下面定義在上面聲明,正確 extern int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; //第四種狀況,只在上面聲明(編譯時沒有問題,由於上面的聲明騙過了編譯器,但運行時報錯,由於extern只能聲明一個已經定義的變量),錯誤 extern int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } //第五種狀況,上下同時定義(這種方式是正確的,由於上面的定義會被認爲是省略了extern的聲明),正確 int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; //其實下面的狀況也是不會出錯的 int a; int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; int a; //第六種狀況,將全局變量聲明爲局部變量,可是它的實質仍是全局變量,正確 int a; int main(int argc, const char * argv[]) { extern int a; printf("a=%d\n",a); return 0; } int a; //第七種狀況,在函數內部從新定義一個變量a,雖然不會報錯,可是兩個a不是同一個 int a; int main(int argc, const char * argv[]) { int a; printf("a=%d\n",a);//注意這裏輸出的a實際上是內部定義的a,和函數外定義的a沒有關係 return 0; } int a;
若是兩個文件同時定義一個全局變量,那實質上他們指的是同一個變量。從下面的例子能夠看出,在main.c中修改了變量a以後message.c中的變量a值也修改了。
須要注意,在上面的代碼中不管在message.h中將a定義前加上extern,仍是在main.h中的a定之前加上extern結果都是同樣的,extern一樣適用。和在單文件中同樣,不能兩個定義都添加extern,不然就沒有定義了。若是把message.c中a的定義(或聲明)去掉呢,那麼它可否訪問main.c中的全局變量a呢,答案是否認的(這和在一個文件中定義了一個函數在另外一個文件不聲明就直接用是相似的)。
extern做用於函數就再也不是簡單的聲明函數了,而是將這個函數做爲外部函數(固然還有內部函數,下面會說到),在其餘文件中也能夠訪問。可是你們應該已經注意到,在上面的代碼中message.c中的showMessage前面並無添加extern關鍵字,在main.c中不是照樣訪問嗎?那是由於這個關鍵字是能夠省略的,默認狀況下全部的函數都是外部函數。
和做用於變量不一樣,上面main.c和message.c中的extern均可以省略,在這裏extern的做用就是定義或聲明一個外部函數。從上面能夠看到在不一樣的文件中能夠定義同一個變量,它們被視爲同一個變量,可是須要指出的是外部函數在一個程序中是不能重名的,不然會報錯。
其實在前面的例子中咱們已經看到static關鍵字在變量中的使用了,在例子中使用static定了一個局部變量,並且咱們強調static局部變量在函數中只被初始化一次。那麼若是static做用於全局變量是什麼效果呢?若是static做用於全局變量它的做用就是定義一個只能在當前文件訪問的全局變量,相等於私有全局變量。
從上面的輸出結果能夠看出message.c中的變量a和main.c中的變量a並非同一個變量,事實上message.c中的變量a只能在message.c中使用,雖然main.c中的變量a是全局變量可是就近原則,message.c會使用本身內部的變量a。固然,上面例子中main.c中的變量a定義成靜態全局變量結果也是同樣的,只是這樣若是還有其餘源文件就不能使用a變量了。可是main.c中的a不能聲明成extern,由於main.c不能訪問message.c中的變量a,這樣在main.c中就沒變量a的定義了。
static做用於函數和做用於變量實際上是相似的,若是static做用於函數則這個函數就是內部函數,其餘文件中的代碼不能夠訪問。下面的代碼在運行時會報錯,由於mesage.c中的showMessage()函數是私有的,在main.c中儘管進行了聲明,能夠在編譯階段經過,可是在連接階段會報錯。
最後作一下簡單總結一下: