C/C++的sizeof在動態分配記憶體時經常用到,但之前一直沒怎麼關注它的具體機制。今天在為一個複雜宣告的指標分配記憶體時,想起來要了解一下sizeof到底是什麼?
先拋個問題:
程式執行過程中對空指標解引用,程式會直接core。那麼sizeof對一個空指標“解引用”行不行?(int p=nullptr; size_t iSize=sizeof(*p))。比如如下應用場景:
int (*p)[10] = nullptr;/*p是一個指向有10個int型元素的陣列的指標*/
如果要分配5個陣列的空間,用malloc(5 * 10 * 4)嗎?還是用malloc(5 * 10 * sizeof(int))?從結果來看,都能實現。但如果上述陣列的型別、大小可能會變化,比如陣列大小變成11,就得在宣告和分配處分別修改程式碼,難免會改漏。一種可行的方式是用一個巨集來表示陣列大小。這裡,我想換個方式,指標宣告時明確了陣列大小,所以能不能在malloc的時候用sizeof計算出指標的元素大小(即陣列大小,注意此時指標是空的),從而只需要在宣告時說明陣列大小,避免在malloc的時候再關心陣列多大?
答案是可以。這條語句是合法的:p = (int (*)[10])malloc(5 * sizeof(*p))。這裡sizeof的引數p是空指標,下面解釋為什麼這裡的*p是合法的。
什麼是sizeof,以及它的機制?
sizeof的一般使用形式都是sizeof( xx ),所以sizeof是不是一個函式呢?答案是看情況。有些程式語言裡,sizeof是函式,這個不細講(因為我也不會幾種程式語言)。在C/C++裡,sizeof不是函式,是一個內建的運算子或操作符,跟“ + – * / ”一樣的運算子。sizeof可以返回一個型別或者物件所佔用的記憶體空間位元組數。
而且關鍵的,sizeof的引數是型別(如sizeof(int)),或者是具體某個變數的型別(如int* p, sizeof(*p),這裡的引數是int*)。sizeof是編譯器在編譯階段解析並完成計算的,執行階段並沒有sizeof。所以對一個空指標執行sizeof(*p),實際上等價於對p指向的物件的型別作sizeof,而且是在編譯階段就完成了,沒解引用,所以是合法的。
這裡分享個《C專家程式設計》第2章的例子:
a = N * sizeof * q;/*這裡有幾個乘號?答案是1個。sizeof操作符把指標q指向的東西作為運算元(即*q)*/
有意思的是上面這個例子,sizeof的引數沒用括號,在函式呼叫裡這顯然不合法,但sizeof是操作符就是任性。當sizeof的運算元是型別名時,兩邊必須加括號,但運算元如果是變數則可以不加括號。
apple = sizeof(int) * a;/*這個例子書上沒給答案。事實上,這裡先計算sizeof(int),然後再乘以a的值*/
最後回到開篇的問題,給p分配記憶體的另一種方式:
typedef int pPtr[10];/*為了方便我們用自定義型別*/
pPtr* p=nullptr;/*相當於int (*p)[10] = nullptr;*/
p = (int (*)[10])malloc(5 * sizeof(*p))。
這裡sizeof對空指標操作了*p,實際並未解引用,而是在編譯階段計算出指標p指向的型別的大小,所以是合法的。