![](/img/trans.png)
[英]When is a pointer-to-array convertible to a pointer-to-array of different type?
[英]Pointer-to-array overlapping end of array
這段代碼是否正確?
int arr[2];
int (*ptr)[2] = (int (*)[2]) &arr[1];
ptr[0][0] = 0;
顯然,通過訪問arr
的界限, ptr[0][1]
將無效。
注意:毫無疑問, ptr[0][0]
指定的內存位置與arr[1]
; 問題是我們是否被允許通過ptr
訪問該內存位置。 下面是一些表達式確定指定相同內存位置但不允許以這種方式訪問內存位置的示例。
注2:還要考慮**ptr = 0;
。 正如Marc van Leeuwen所指出的, ptr[0]
相當於*(ptr + 0)
,但是ptr + 0
似乎與指針運算部分相悖。 但是通過使用*ptr
代替,可以避免這種情況。
不是一個答案,而是一個評論,如果不是一個文本牆,我似乎無法說得好:
給定數組保證連續存儲它們的內容,以便可以使用指針“迭代”它們。 如果我可以指向數組的開頭並連續遞增該指針,直到我訪問了數組的每個元素,那么肯定會聲明數組可以被訪問為一系列由它組成的任何類型。
當然結合:1)Array [x]將其第一個元素存儲在地址'array'2)連續遞增指向它的指針足以訪問下一個項目3)Array [x-1]遵守相同的規則
那么至少看一下地址'array'應該是合法的,好像它是類型array [x-1]而不是類型array [x]。
此外,鑒於關於連續的要點以及指向數組中元素的指針必須如何表現,當然必須合法地將數組[x]的任何連續子集分組為數組[y],其中y <x並且它的上限不是超出數組[x]的范圍。
不是語言律師,這只是我噴出一些垃圾。 我對這次討論的結果非常感興趣。
編輯:
在進一步考慮原始代碼時,在我看來,在許多方面,數組本身就是一個特殊情況。 它們會衰減到一個指針,我相信可以按照我剛才在這篇文章中所說的那樣混淆。
因此,沒有任何支持來支持我的拙見,如果一個數組不能真正統一地作為一個整體對待,那么它就不能真正無效或整體“未定義”。
得到均勻處理的是個別元素。 所以我認為只討論訪問特定元素是有效還是定義是有意義的。
對於C ++(我正在使用草案N4296) [dcl.array]/7
特別說如果下標的結果是一個數組,它會立即轉換為指針。 也就是說,在ptr[0][0]
中,首先將ptr[0]
轉換為int*
,然后僅對其應用第二個[0]
。 所以這是完全有效的代碼。
對於C(C11草案N1570) 6.5.2.1/3
陳述相同。
是的,這是正確的代碼。 引用N4140 for C ++ 14:
[expr.sub] / 1 ...表達式
E1[E2]
與*((E1)+(E2))
相同(根據定義*((E1)+(E2))
[expr.add] / 5 ...如果指針操作數和結果都指向同一個數組對象的元素,或者指向數組對象的最后一個元素,則評估不應產生溢出; 否則,行為未定義。
這里沒有溢出。 &*(*(ptr)) == &ptr[0][0] == &arr[1]
。
對於C11(N1570),規則是相同的。 §6.5.2.1和§6.5.6
讓我給出一個反對意見:這是(至少在C ++中)未定義的行為,其原因與此問題所關聯的其他問題的原因大致相同。
首先讓我用一些簡化討論的typedef來澄清這個例子。
typedef int two_ints[2];
typedef int* int_ptr;
typedef two_ints* two_ints_ptr;
two_ints arr;
two_ints_ptr ptr = (two_ints_ptr) &arr[1];
int_ptr temp = ptr[0]; // the two_ints value ptr[0] gets converted to int_ptr
temp[0] = 0;
所以問題是,盡管沒有類型為two_ints
的對象,其地址與arr[1]
對象一致(在同一意義上, arr
的地址與arr[0]
的地址重合),因此沒有對象ptr[0]
可能可能指向,一個可以在該表達式的值仍然轉換成類型的一個int_ptr
(這里給出的名稱temp
), 做指向的對象(即對象的整數也被稱為arr[1]
我認為行為未定義的點是在ptr[0]
的評估中,它是等價的(按5.2.1 [expr.sub])到*(ptr+0)
; 更確切地說, ptr+0
的評估具有未定義的行為。
我會引用我的C ++副本,這不是官方的[N3337],但可能語言沒有改變; 令我困惑的是,章節編號根本不符合鏈接問題的已接受答案中提到的那個。 無論如何,對我來說是§5.7[expr.add]
如果指針操作數和結果都指向同一個數組對象的元素,或者指向數組對象的最后一個元素,則評估不應產生溢出; 否則行為未定義。
由於指針操作數ptr
具有指向two_ints
類型指針,因此引用文本中提到的“數組對象”必須是two_ints
對象的數組。 然而,這里只有一個這樣的對象,它的唯一元素是arr
的虛擬數組,我們應該在這種情況下讓人聯想起來(根據:“指向非陣列對象的指針與指向數組的第一個元素的指針的行為相同長度一......“),但顯然ptr
並沒有指向它獨特的元素arr
。 因此,即使ptr
和ptr+0
毫無疑問是相等的值,它們都沒有指向任何數組對象的元素(甚至不是虛構的元素),也不是指向這樣的數組對象的末尾,以及引用的短語不符合。 結果是(不是產生溢出,但是)行為是未定義的。
因此,在應用間接運算符*
之前,行為已經未定義。 我不會爭論后一種評估的未定義行為,即使短語“結果是指向表達點所指向的對象或函數的左值”很難解釋為根本沒有引用任何對象的表達式。 但是我在解釋這個問題時會很寬容,因為我認為解除引用數組的指針本身不應該是未定義的行為(例如,如果用於初始化引用)。
這表明,如果不是ptr[0][0]
,而是寫了(*ptr)[0]
或**ptr
,那么行為就不會被定義。 這很奇怪,但這不是C ++標准第一次讓我感到驚訝。
這取決於你所說的“正確”。 你在ptr上做了一個演員到arr[1]
。 在C ++中,這可能是一個reinterpret_cast
。 C和C ++是(大多數時候)假設程序員知道他在做什么的語言。 這段代碼錯誤與它是有效的C / C ++代碼無關。
您沒有違反標准中的任何規則(據我所知)。
試着回答這里為什么代碼適用於常用的編譯器:
int arr[2];
int (*ptr)[2] = (int (*)[2]) &arr[1];
printf("%p\n", (void*)ptr);
printf("%p\n", (void*)*ptr);
printf("%p\n", (void*)ptr[0]);
所有行都在常用編譯器上打印相同的地址。 因此, ptr
是一個對象,其中*ptr
表示與常用編譯器上的ptr
相同的內存位置,因此ptr[0]
實際上是指向arr[1]
的指針,因此arr[0][0]
是arr[1]
。 因此,代碼為arr[1]
賦值。
現在,讓我們假設一個反常的實現,其中指向數組的指針(注意:我說的是指向數組的指針,即&arr
的類型為int(*)[]
,而不是arr
,這意味着與&arr[0]
和int*
類型是指向數組中第二個字節的指針。 然后解除引用ptr
與使用char*
arithmetic從ptr
減去1相同。 對於結構和聯合,保證指向這些類型的指針與指向這些類型的第一個元素的指針相同,但是在將指向數組的指針轉換為指針時,沒有找到對數組的這種保證(即,指向數組的指針將是與指向數組的第一個元素的指針相同)事實上@FUZxxl計划提交有關標准的缺陷報告。 對於這種不正確的實現, *ptr
即ptr[0]
與&arr[1]
。 在RISC處理器上,事實上會由於數據對齊而導致問題。
一些額外的樂趣:
int arr[2] = {0, 0};
int *ptr = (int*)&arr;
ptr[0] = 5;
printf("%d\n", arr[0]);
該代碼應該有效嗎? 它打印5。
更有趣:
int arr[2] = {0, 0};
int (*ptr)[3] = (int(*)[3])&arr;
ptr[0][0] = 6;
printf("%d\n", arr[0]);
這有用嗎? 它打印6。
這應該顯然有效:
int arr[2] = {0, 0};
int (*ptr)[2] = &arr;
ptr[0][0] = 7;
printf("%d\n", arr[0]);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.