[英]Treating 2D array as 1D array
假設我們有一個 2D int
數組:
int a[3][4] = { { 1,3,2,4 }, { 2,1,5,3 }, { 0,8,2,3 } };
取其地址並將其重新解釋為指向int
的一維數組的指針是否合法有效? 基本上:
int *p = reinterpret_cast<int *>(&a);
這樣我就可以做(大致)這樣的事情:
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T *p = reinterpret_cast<T *>(&arr);
std::sort(p, p + X*Y);
}
演示: https : //ideone.com/tlm190
據我所知,該標准保證二維數組的對齊在內存中是連續的,雖然p + X*Y
技術上講是超出范圍的,但永遠不會被訪問,因此也不應該導致未定義的行為。
我可以在需要時絕對將二維數組視為一維數組嗎?
感謝大家的回復和評論,但我認為正確的答案是 - 就目前而言,代碼展示了技術 UB,但可以更正。 我瀏覽了其中的一些問題 [ 1 , 2 ] @xskxzr 鏈接,這讓我從標准中找到了這句話:
如果兩個對象是指針可互轉換的,則它們具有相同的地址,並且可以通過reinterpret_cast從指向另一個的指針獲取指向一個的指針。 [注意:一個數組對象和它的第一個元素是指針不可轉換的,即使它們有相同的地址。 —尾注]
然后在reinterpret_cast
頁面上有以下注釋和示例:
假設滿足對齊要求, reinterpret_cast不會在處理指針可互轉換對象的一些有限情況之外更改指針的值:
int arr[2];
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast and
// is "pointer to arr"
即使這在沒有警告的情況下編譯並運行,這在技術上是一個 UB,因為從技術上講p5
仍然是指向arr
而不是arr[0]
的指針。 所以基本上我使用reinterpret_cast
的方式會導致 UB。 考慮到上述情況,如果我要直接將int *
創建到第一個int
(根據@codekaizer 的回答這是可以的),那么這應該是有效的,對吧?:
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T *p = &arr[0][0]; // or T *p = arr[0];
std::sort(p, p + X * Y);
}
但它也可能是 UB,因為指針p
指向具有Y
元素的T
s 的第一個數組的第一個T
因此p + X*Y
將指向第一個T
數組的范圍,因此是 UB (再次感謝@xskxzr 提供鏈接和評論)。
如果表達式 P 指向具有 n 個元素的數組對象 x 的元素 x[i],則表達式 P + J 和 J + P(其中 J 的值為 j)指向(可能是假設的)元素 x[i+ j] 如果 0≤i+j≤n; 否則,行為未定義。
所以這是我放棄之前的最后一次嘗試:
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T(&a)[X * Y] = reinterpret_cast<T(&)[X * Y]>(arr);
std::sort(a, a + X * Y);
}
這里T arr[X][Y]
首先轉換為T a[X*Y]
,再次使用reinterpret_cast
,我認為現在是有效的。 重新解釋的數組a
愉快地衰減到指向數組a[X*Y]
第一個元素的指針( a + X * Y
也在范圍內)並在std::sort
轉換為迭代器。
TL;DR 版本
由於reinterpret_cast
使用不當,OP 中的行為未定義。 將二維數組轉換為一維數組的正確方法是:
//-- T arr2d[X][Y]
T(&arr1d)[X*Y] = reinterpret_cast<T(&)[X*Y]>(arr2d);
T1 類型的左值表達式可以轉換為對另一個類型 T2 的引用。 結果是一個左值或 xvalue 引用與原始左值相同的對象,但具有不同的類型。 沒有臨時創建,沒有復制,沒有構造函數或轉換函數被調用。 只有在類型別名規則允許的情況下才能安全地訪問結果引用
別名規則:
每當嘗試通過 AliasedType 類型的泛左值讀取或修改 DynamicType 類型的對象的存儲值時,除非滿足以下任一條件,否則行為未定義:
- AliasedType 和 DynamicType 類似。
非正式地,兩種類型是相似的 if,忽略頂級 cv-qualification
- 它們都是相同大小的數組或都是未知邊界的數組,數組元素類型相似。
在聲明
T
D
,其中D
的形式為
D1 [ constant-expression opt ] attribute-specifier-seq opt
並且聲明
T
D1
中的標識符類型為“ derived-declarator-type-listT
”,則D
的標識符類型為數組類型; 如果D
的標識符類型包含 auto類型說明符,則程序格式錯誤。T
稱為數組元素類型;
來自http://www.cplusplus.com/doc/tutorial/arrays/
int jimmy [3][5]; // is equivalent to
int jimmy [15]; // (3 * 5 = 15)
創建數組(任何維度)時,數組內存是size = sizeof(type) * dim0 * dim1 * ....;
固定的內存塊size = sizeof(type) * dim0 * dim1 * ....;
所以對於你的問題,是的,你可以安全地將數組重鑄為一維數組。
是的。 它是合法有效的。
根據dcl.array :
如果 E 是秩為 i×j×⋯×k 的 n 維數組,則出現在需要進行數組到指針轉換的表達式中的 E 將轉換為指向 (n−1) 維數組的指針秩為 j×⋯×k。 如果
*
運算符(作為下標的結果顯式或隱式)應用於此指針,則結果是指向的 (n-1) 維數組,該數組本身立即轉換為指針。
二維數組不能被視為一維數組
正如@KillzoneKid 所指出的,數組的地址不能與第一個元素的地址相互轉換,即使它們共享相同的地址值。
constexpr
提供了一種評估 UB 的便捷方法。 當評估constexpr
時,編譯器需要檢測 UB。 這樣就可以進行簡單的測試。 此函數在編譯時評估時將檢測超出數組范圍的訪問值。
// sum "n" sequential ints
constexpr int sum(const int* pi, int n) {
int sum = 0;
while (n-- > 0)
sum += *pi++;
return sum;
};
當在運行時使用指向一維或二維數組的第一個元素的指針調用它時,它將對元素求和,但如果“n”超出數組的邊界,則為 UB。 對於二維數組,這將是最低維度的范圍。 不是整個數組的大小。
檢查指向 int 的指針的不同實例,我們可以看到,一旦我們嘗試訪問超出數組維度的值,就會發生 UB。
int main()
{
constexpr int i0{1};
constexpr int i1[2]{ 1,2 };
constexpr int i2[2][2] = { { 1,2}, {3,4} };
constexpr int sum0 = sum(&i0, 1); // fails for n>1
constexpr int sum1 = sum(&i1[0], 2); // fails for n>2
constexpr int sum2 = sum(&i2[0][0], 2); // fails for n>2
const int sum3 = sum(&i2[0][0], 4); // runtime calc, UB. fails if constexpr
return sum0 + sum1 + sum2 + sum3; // 17
}
對於超出現有數據的訪問,例如在 sum0 和 sum1 中,UB 是明確的。 但是 sum2 指向 n=[0:3) 的現有數據,但 constexpr 評估表明它是 n=4 的 UB。
得知這一點,我有些驚訝。 我使用過的每個編譯器在執行諸如按固定量縮放矩陣的所有系數之類的操作時都按預期工作。 但是我可以看到基於優化假設的基本原理,即矩陣的部分不會因對不在同一數組序列中的另一部分的函數調用的結果而改變。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.