C-C++到底支不支持VLA以及兩種語言中const的區別
到底支不支持VLA
VLA就是variable-length array,也就是變長數組。
最近寫程序的時候無意間發現,gcc中竟然支持下面這種寫法:
int n = 10;
int a[n];
注意上面的語句是在函數內部寫的,也就是n和a都是自動變量。
當時十分疑惑,C語言中數組的長度不應該是常量或常量表達式嗎?為什么變量也可以。我將代碼在VC中跑了一下,發現編譯出錯,提示數組的大小未知,說明VC中是不支持VLA的。
那既然有的編譯器支持VLA,又有的編譯器不支持VLA,那么C標准到底是怎樣規定的呢?然后我看是看書、在網上查資料。
C Primer Plus一書中是這樣描述的:
C90標准中並不支持VLA,C99開始支持VLA,很大的一個原因:FORTRAN中支持這種寫法。C99中對對VLA有一些限制,比如變長數組必須是自動存儲類型,也就是說,如果我上面兩句放在函數外面就就不能通過編譯了,這是因為在函數外面定義的是全局變量,此外,使用VLA不能對數組進行初始化,因為它的長度在運行時才能確定。
此外VLA並不是真正的變長,它實際上只是將數組的長度推遲到運行時確定而已,也就是說C90標准中,數組的長度必須在編譯時期就知道,但C99支持VLA后,數組的長度可以推遲到運行時知道,但是,一旦長度確定,數組的長度就不能變了。
此外,網上的大神說,C++的標准中無論是C++90還是C++99還是C++11都不支持VLA的這種寫法。
鑒於以上原因,在C語言中,如果想用變長的數組,還是老老實實用malloc分配吧,在C++中當然有更好的選擇,就是vector,當然C++11中又推出了一個array,而且這兩種都是真正的變長,也就是數組的長度隨時都可以改變。
下面我還想說一下C和C++中const關鍵字的區別。
const關鍵字最早是C++中的產物,后來才引入到C語言中。const在C語言中和在C++中是相當不一樣的。
在C語言中const修飾的,被認為是一個只讀的、或者叫不可改變的變量,它實際上只是一個變量,只不過對這個變量做了一些限制。而C++中const修飾的才是真正的常量。當然這其中還有很多細節的地方,有些地方我也還有些模糊,下面,我就通過例子,把自己已經理解的東西寫出來。
先來一個例子:
const int a = 10; int array[a]; int main() { return 0; }
用gcc和g++編譯的結果分別如下:
可以看到gcc下不能通過編譯,但是g++下可以通過,說明C語言中有錯,在C++中沒錯。
原因解釋:
首先說C語言中:
首先說明,即使在支持VLA的編譯器下,(我的gcc是支持的),前面提到了VLA數組是有限制的,VLA必須是自動存儲類型,而上面的代碼中數組是全局變量,所以並不存在是否支持VLA的問題。上面提到,在C語言中const被認為是一個受到一定限制的變量,是變量就要被分配數據區(或者運行時的棧區等)內存空間,由於a是全局變量,全局變量位於數據區,空間在編譯時期就分配了。而,需要注意,編譯時期,編譯器是不能讀取數據區的內存的(它可以分配數據區的內存,並初始化內存,但是不能從數據區的牛叉女內存中讀取數據)。所以在編譯時期,編譯器其實並不知道a的值是什么,因為它不能讀數據區的內存而a的值是在內存中的。但是,對於數組array編譯器是一定要知道數組的長度才行的,也就是必須要知道a的值,這樣就矛盾了,所以編譯器就報錯了!
那在C++中有為什么能夠通過呢?
原因就是C++真的把const當成常量看待。
詳細解釋一下:
const int a = 10;這條語句中10是我們所說的字面量,無論是在C中還是在C++中字面量都是保存在代碼段中,編譯初期會將其保存在符號表中。C++盡量不對const分配數據區(或者運行時的棧區)的內存空間,只在必須分配內存時才分配(這個后面再說)。下面一條語句int array[a],編譯器一定要知道a的值的,C語言要想知道a的值,必須讀內存,但是C++卻不需要,直接讀取代碼段中的的符號表即可,編譯時期訪問符號表是沒有任何問題的,但是訪問數據區的內存是做不到的。所以上面的語句在C++中是沒有問題的。
再來說說,什么叫盡可能不為const分配內存。
如果代碼是這樣
const int a = 10; const int *p = &a; int array[a]; int main() { return 0; }
注意 const int *p = &a;這句,對a取地址操作,我們知道位於代碼段的數據是不取地址的,所以這個時候,只能給a在數據區分配空間了。
於是就出現了新的問題,既然給a分配了數據區的空間,那是不是編譯時期就不知道a的值了,因為畢竟編譯時期是不能讀取數據區的內存的,那么后面數組的定義也就不行了吧?但是答案卻相反,依然可以,這是因為當編譯器讀a的值的時候,不是從數據區的內存中,而是程序段的符號表中讀取的那個字面常量10。所以在編譯實際依然能夠確定數組的長度。
下面的例子應該更能說明這個問題:
#include <stdio.h> int main() { const int a = 10; int *pa = (int *)&a; printf("pa指向的地址為:%p a的地址為:%p\n",pa,&a); (*pa)++; printf("a = %d,*pa = %d\n",a,*pa); return 0; }
我們分別用gcc和g++編譯他,然后分別看結果,如下:
驚奇地發現,雖然都能順利通過編譯,但是C的執行和C++的執行竟然不一樣!
好吧,下面解釋原因。
還是要聲明一下,C語言中const就是一個值不能改變的變量,就是個受限制的變量,但是,我們雖然我們不能通過a修改那塊內存的值,但是我們可以通過指針間接去修改。這里要注意那個強制類型轉換,如果不寫強制類型轉換,編譯器就會報錯,是允許將const int *賦值給int*的。在C++中,這一點和C是一樣的,就是雖然我們不能通過a本身修改那塊內存的值,但是我們可以通過指針間接去修改。但是為什么C和C++中的輸出不一樣呢?原因就是C++在讀a的時候,其實是去代碼段中讀字面常量10去了,而C是讀a所標識的那塊棧區的內存。其實a所標識的內存的內容都已經變成11了,無論是C還是C++都是一樣,區別就在於C讀const數據和讀普通變量一樣,都是從數據段(如果是局部變量就是從棧區)讀取數組,而C++卻是讀取代碼段的字面常量!(間接修改const的時候,當然都是修改的數據區或棧區的內存,而不是代碼段,因為代碼段是只讀的)
所以C++中const修飾的可以認為就是常量!但是C語言中卻不能這么認為。
最后要小心C++中的const蛻變成C語言中的const。
其實通過上面的分析,我們應該可以得出一個結論:C++中的const之所以和C語言中的const不一樣,C++中的const之所以能夠看成常量,就是因為C++在讀取const的時候,實際上讀取的是代碼段的字面常量,而不是數據區(對於全局變量來說是靜態區,對於局部變量來說是棧區)的內存中的數值。
那么問題來了:如果const保存的不是一個字面常量呢?
看下面代碼:
#include <stdio.h> int main() { int i = 10; const int a = i; int *pa = (int *)&a; printf("pa指向的地址為:%p a的地址為:%p\n",pa,&a); (*pa)++; printf("a = %d,*pa = %d\n",a,*pa); return 0; }
幾乎還是同樣的代碼,只是先把字面常量10賦值給了變量i,然后用i初始化const int a,但是我們發現,在C++中,執行結果卻變了。
為什么呢?前面強調了,C++讀取const的值,實際上是讀取代碼段的字面常量,那么,如果我們初始化const的時候,給它的不是字面量(或者是常量表達式),那么他就沒有字面量可以讀啦!這時候就只能退而求其次,去讀數據區內存中的值啦!這個時候,C++中的const和C語言中的const就一樣了。
需要注意的是 sizeof是C和C++中的運算符,而且他的值通常都是在編譯時確定的,可以認為是一個字面常量。
比如:
#include <stdio.h> int main() { const int a = sizeof(int); int *pa = (int *)&a; printf("pa指向的地址為:%p a的地址為:%p\n",pa,&a); (*pa)++; printf("a = %d,*pa = %d\n",a,*pa); return 0; }
此外在類中使用const修飾類成員變量的時候也要小心,因為也會退化成C語言中的const
比如:
#include <stdio.h> class A{ public: const int a; int array[a]; A(int i):a(i) { } }; int main() { return 0; }
編譯器會報錯:
首先,類只是定義類型的地方,不能再類中初始化成員變量,所以在類定義中寫const int a = 10是不對的,const成員的初始化要放在初始化列表中。我們知道,在對象創建之前才會調用構造函數進行對象的初始化,所以在編譯時期我們根本就不知道a的值。所以把a當做數組的長度是有問題的。
我們可以這樣:
#include <stdio.h> class A{ public: static const int a = 10; int array[a]; A() { } }; int main() { return 0; }
這樣定義的a就可以當成一個常量來使用了。注意的是,這時候,a的語句不是聲明了,而是定義+初始化,而且C++只允許這種情況可以在類內初始化,就是當變量被 static 和const修飾,且為int類型時。(當然這種情況依然可以在類外定義和初始化,只不過后面就不能用a定義數組的長度了,也會提示數組的長度不確定)
簡單總結一下就是:
C語言中const就是一個受限制的變量,讀取const時是從數據區的內存讀取的(全局從靜態去,局部從棧區),可以用指針間接修改其標識的數據區的內存區域,在讀取const的值時,也是讀取它標識的數據區的內存中的值。
在C++中,const大多數情況下可以當成常量來使用,這是因為,雖然C++也會為const在數據區開辟內存(C++盡量不這樣左),我們也可以通過指針(或者非常量的引用)來簡介修改其標識的數據區的內存區域的值,但是,在讀取const時,不是讀取數據區的內存區域中的值(也很有可能根本就沒有分配內存),而是讀取代碼段的字面常量。所以可以達到常量的效果。
最后要警惕C++中const退化成C語言中的const,有兩種情況,一種是初始化的const的時候是用變量初始化的,而不是字面常量(或常量表達式)。第二種情況就是const修飾類中成員變量的時候。
給一個建議:如果我們想用const定義常量,那么我們就要用字面常量或常量表達式初始化const,而且要將const用static修飾(注意在C++中如果定義的是全局的const,默認為static修飾,但是在類中並不是這樣的,這也是為什么我們在類中定義常量要用static修飾)。在類中定義常量的時候要同時用cosnt和static修飾,而且盡量在類的內部進行初始化。
如果你覺得對你有用,請贊一個吧