對指向可變長度陣列型別的指標進行型別轉換是否有效?
int main() {
int n = 10;
int m = 20;
int (*p)[n][m] = malloc(sizeof(int[n][m]));
void *q = p;
int a = n;
int b = m;
(*(int (*)[a][b])q)[5][5] = 1;
printf("%zd %d\n", sizeof(*p), (*p)[5][5]);
return 0;
}
列印800 1。
和
int a = 1;
仍然列印800 1。
和
int a = 1;
int b = 1;
現在列印800 0。
到哪一點是明確定義的行為?
uj5u.com熱心網友回復:
這是未定義的行為,因為您訪問的陣列越界。
可能值得指出的是,C 型別系統在這里有點混亂。分配的記憶體malloc在訪問之前不會被賦予型別。我將參考 C17 6.5/6 并在兩者之間留下評論:
訪問其存盤值的物件的有效型別是該物件的宣告型別(如果有)。
從 malloc 回傳的這個“堆塊”沒有宣告型別。
如果一個值通過一個型別不是字符型別的左值存盤到一個沒有宣告型別的物件中,那么左值的型別將成為該訪問的物件的有效型別,并且對于不修改儲值。
這意味著如果我們在堆塊中的給定位置存盤一些東西,該地址將獲得有效型別并被視為一個物件(int在這種情況下為單個)。編譯器并不真正知道整個塊是否應被視為陣列、結構或其他東西。
對于沒有宣告型別的物件的所有其他訪問,物件的有效型別只是用于訪問的左值的型別。
這意味著如果我們在寫入之前讀取訪問此資料,它將被視為用于讀取的型別的變數。(雖然堆塊沒有被初始化malloc,所以在寫之前讀沒有多大意義。)
假設 32 位int然后 malloc(sizeof(int[n][m]));分配一個10*20*4= 800 位元組的塊。malloc 留出的這個塊還沒有有效的型別。編譯器尚未跟蹤存盤在該資料中的型別。您在資料上具有特定的指標型別這一事實不會改變這一點,只要資料未被取消參考,它就還沒有有效的型別。
通常,堆塊不會獲得型別,直到您[5][5]取消參考并寫入1該塊中的某個位置。那個位置現在可以說是有功型了int。
但是,在您這樣做之前,您需要這樣做*(int (*)[a][b])q。的型別在q這里無關緊要 - 您對其進行轉換并說這是一個指向陣列的指標。然后您取消參考它并獲得一個陣列 - 這是一個左值(它將衰減為指向陣列第一個元素的指標)。從那時起,至少在這個運算式中,編譯器可以假設它正在處理一個陣列 - 無論該陣列是否恰好存盤在沒有有效型別的記憶體中。如果這個陣列是一個 VLA 并且沒有足夠大的維度來... [5][5] = 1;訪問,你最終會得到普通的舊陣列越界訪問,這是未定義的行為。
具體來說,UB 根據 6.5.6/8 加法運算子:
如果指標運算元和結果都指向同一個陣列物件的元素,或者陣列物件的最后一個元素之后,求值不會產生溢位;否則,行為未定義。
arr[i]100% 等價于*(arr i), 運算子為上面參考的指標算術設定規則 - 不允許使用指標算術來訪問陣列或越界物件 - 這是未定義的行為。這意味著編譯器可能會生成行為不正確的代碼,無論假定地址是否有可用記憶體。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/356334.html
