C++數組維度與C99的變長數組(VLA)

問題起因

前幾天在一個C++學習交流羣裏邊有羣友問了一個代碼問題,其中它的代碼包含了這樣的語句

int n;
scanf("%d",&n);
std::string strs[n];

看到這樣的操作,我沒有繼續看其他的代碼,就直接指出了他的這個問題,我說這種靜態數組在編譯期就要確定內存大小,你這樣肯定有問題,他表示不信,後來他的程序問題找到了並且告訴我,他的這種寫法沒問題,我當時就奇怪了,怎麼與我一貫的理解不同呢,工作了這麼多年,凡是使用數組的地方都是固定大小的。然後我在devC++這個IDE上測試了這個問題,當然編譯器是GCC的,結果令我喫驚,還真是沒問題。看來涉及到基礎的問題,我還是不牢固啊,趕緊翻開《C++Primer》來看

C++Primer關於數組的說法

C++primer中文版第五版101頁說到:

數組中元素的個數也屬於數組類型的一部分,編譯的時候維度應該是已知的,也就是說維度必須是一個常量表達式

unsigned cnt = 42;         //不是常量表達式
constexpr unsigned sz = 42;//常量表達式
int arr[10];               //含有10個整數的數組
int *parr[sz];             //含有42個整型指針的數組
string bad[cnt];           //錯誤:cnt不是常量表達式
string strs[get_size()];   //當get_size是constexpr時正確;否則錯誤

這麼說來我原來的理解是沒錯的。那麼爲什麼出現上邊的問題呢,這就涉及到C99標準了

C99標準的變長數組(VLA)

參見維基百科的解釋 https://en.wikipedia.org/wiki/Variable-length_array#C99

The following C99 function allocates a variable-length array of a specified size, fills it with floating-point values, and then passes it to another function for processing. Because the array is declared as an automatic variable, its lifetime ends when read_and_process() returns.

float read_and_process(int n)
{
    float vals[n];

    for (int i = 0; i < n; ++i)
        vals[i] = read_val();

    return process(n, vals);
}

In C99, the length parameter must come before the variable-length array parameter in function calls.[1]

Linus Torvalds has expressed his displeasure in the past over VLA usage for arrays with predetermined small sizes, with comments like "USING VLA'S IS ACTIVELY STUPID! It generates much more code, and much slower code (and more fragile code), than just using a fixed key size would have done." [6] With the Linux 4.20 kernel, Linux kernel is effectively VLA-free.

注意紅字的部分,另外顯然Linus不喜歡VLA

編譯器對VLA的支持

GCC

還是上邊的維基百科

那麼我的疑問也得到了解答了,顯然GCC是支持VLA的,而且是在棧上分配的內存

Visual C++

我自己在visual studio 2017上嘗試了一下,要求數組維度必須是常量表達式

參見該鏈接

https://docs.microsoft.com/en-us/cpp/c-language/c-language-reference?view=vs-2019

The C Language Reference describes the C programming language as implemented in Microsoft C. The book's organization is based on the ANSI C standard (sometimes referred to as C89) with additional material on the Microsoft extensions to the ANSI C standard.

以及維基百科

https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#C

Although the product originated as an IDE for the C programming language, for many years the compiler's support for that language conformed only to the original edition of the C standard, dating from 1989, but not the C99 revision of the standard. There had been no plans to support C99 even in 2011, more than a decade after its publication

也就是visualC++雖然最初是爲了C編程開發的,但是對C標準的支持並不夠,C99並不怎麼支持,後邊還有內容會說到,其實後來的visual IDE 對C99的支持是不完備的

VLA的實現原理

既然數組是變長的,編譯的時候又不能確定維度,那麼我對怎麼實現的很感興趣,查了一些東西,感覺論壇裏這個解釋很好。

當有多個變長數組分配時,也就是編譯器不能用僅有的幾個寄存器保存當前的esp時,編譯器就會劃分一塊區域(這塊區域也在棧中,而且是先於變長數組分配劃分好的)來記錄每個數組的首地址。

例如,我昨晚試驗的程序有9個變長數組,前三個數組的首地址存在三個通用寄存器中,而後面的6個的首地址則放在比如說ebp-40,ebp-44,ebp-48...的位置。然後如果引用第四個數組的元素,比如源代碼是ar4[1] = 1;編譯器會先取ebp-40的內容到一個臨時寄存器,再用該值索引數組。也就是有類似如下的彙編代碼:
movl (%ebp-40), %eax
movl $1,        4(%eax)

也就是說,從一個單一的概念模型上來說,對於碰到變長數組的情形,編譯器可以按一個指針的大小爲其預留一個slot,然後到運行的時候esp-eax分配了空間以後,把當前esp,即數組的首地址放入到這個slot中。以後對數組的引用,就要進行兩次訪存,一次取到數組的首地址,一次訪問真正的數組元素。這與以前的數組訪問的開銷是不同的,以前的數組元素訪問之需要一次訪存操作,而變長數組的下標訪問有點類似於指針的下標訪問了。
變長數組的存儲分配是在運行時,並且訪問也需要兩次訪存,比原來的數組訪問開銷要大,但它與動態分配malloc還是有區別的。由於變長數組分配在棧中,只需要改變esp的值就能達到分配的目的,而malloc分配則需要runtime system來進行heap management,也就是說分配的時候需要一定的search operation來得到一塊連續的存儲,而釋放的時候也要執行相應的代碼來使得這塊存儲available for future use。

所以,變長數組的開銷還是小於malloc的。

建議

根據個人的工作經驗,還是不太建議使用變長數組,另外這裏要注意的是 變長數組而不是動態數組,通常我們談到動態數組就想到 malloc分配內存,或者是vector,如果遇到使用不確定長度的數組,還是使用vector吧。再者不是所有的編譯器都支持VLA。

參考資料

https://www.cnblogs.com/hazir/p/variable_length_array.html

https://blog.csdn.net/qq_41024819/article/details/80113111

https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#C

https://en.wikipedia.org/wiki/Variable-length_array#C99

https://docs.microsoft.com/en-us/cpp/c-language/c-language-reference?view=vs-2019

《C++ Primer 中文版》第五版

https://bbs.csdn.net/topics/90350681

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章