Pertanyaan Bolehkah saya memperlakukan array 2D sebagai array 1D bersebelahan?


Pertimbangkan kode berikut:

int a[25][80];
a[0][1234] = 56;
int* p = &a[0][0];
p[1234] = 56;

Apakah baris kedua memunculkan perilaku tidak terdefinisi? Bagaimana dengan baris keempat?


32
2017-09-01 10:35


asal


Jawaban:


Terserah interpretasi. Sementara persyaratan kedekatan array tidak meninggalkan banyak imajinasi dalam hal bagaimana tata letak array multidimensi (ini telah ditunjukkan sebelumnya), perhatikan bahwa ketika Anda melakukan p[1234] Anda mengindeks elemen 1234 dari baris zeroth hanya 80 kolom. Beberapa menafsirkan satu-satunya indeks yang valid menjadi 0,79 (&p[80] menjadi kasus khusus).

Informasi dari C FAQ yang merupakan kebijaksanaan yang dikumpulkan dari Usenet pada hal-hal yang relevan dengan C. (Saya tidak berpikir C dan C ++ berbeda dalam hal itu dan bahwa ini sangat relevan.)


7
2017-09-01 16:47



Ya, Anda dapat (tidak, itu bukan UB), itu secara tidak langsung dijamin oleh standar. Begini caranya: array 2D adalah array array. Array dijamin memiliki memori yang berdekatan dan sizeof(array) aku s sizeof(elem) kali jumlah elemen. Dari sini, apa yang Anda coba lakukan adalah sah secara hukum.


10
2017-09-01 10:37



Kedua garis melakukan menghasilkan perilaku tidak terdefinisi.

Subscripting ditafsirkan sebagai penambahan pointer diikuti oleh suatu tipuan, yaitu, a[0][1234]/p[1234] setara dengan *(a[0] + 1234)/*(p + 1234). Menurut [expr.add] / 4 (di sini saya mengutip draft terbaru, sementara untuk waktu OP diusulkan, Anda bisa merujuk ke komentar ini, dan kesimpulannya sama):

Jika berekspresi P menunjuk ke elemen x [i] dari objek array x dengan n elemen, ekspresi P + J dan J + P (dimana J memiliki nilai j) arahkan ke elemen (mungkin-hipotetis) x [i + j] jika 0≤i + j≤n; jika tidak, perilaku tidak terdefinisi.

sejak a[0](membusuk ke pointer ke a[0][0]) /p menunjuk ke elemen a[0] (sebagai larik), dan a[0] hanya memiliki ukuran 80, perilaku tidak terdefinisi.


Suatu hal yang menarik adalah bahwa pada saat OP diusulkan (sebelum C ++ 17), yang berikut ini didefinisikan dengan baik:

int* p = &a[0][0];
p[80] = 56; 
p += 80; // p now passes the end of a[0], and points to a[1][0] at the same time
p[80] = 56; // ok because p points to an element of a[1]

Menurut

Jika suatu objek bertipe T terletak di alamat A, penunjuk dari tipe cv T * yang nilainya adalah alamat A dikatakan menunjuk ke objek itu, terlepas dari bagaimana nilai diperoleh. [ Catatan: Misalnya, alamat yang melewati ujung array ([expr.add]) akan dianggap menunjuk ke objek yang tidak terkait dari tipe elemen array yang mungkin terletak di alamat tersebut. Ada pembatasan lebih lanjut pada pointer ke objek dengan durasi penyimpanan dinamis; lihat [basic.stc.dynamic.safety]. - catatan akhir]

Namun, ini tidak diizinkan lagi setelah C ++ 17. Kata-kata di atas diubah menjadi

Nilai dari jenis pointer yang merupakan penunjuk ke atau melewati ujung objek mewakili alamat dari byte pertama dalam memori ([intro.memory]) ditempati oleh objek atau byte pertama dalam memori setelah akhir penyimpanan ditempati oleh objek, masing-masing. [ Catatan: Penunjuk melewati ujung objek ([expr.add]) tidak dianggap menunjuk ke objek yang tidak terkait dari jenis objek yang mungkin terletak di alamat tersebut.Nilai penunjuk menjadi tidak valid ketika penyimpanan yang ditunjukkannya mencapai akhir durasi penyimpanannya; lihat [basic.stc]. - catatan akhir]

per P0137.


1
2018-06-11 10:24



Memori yang direferensikan oleh a keduanya a int[25][80] dan a int[2000]. Jadi kata Standar, 3.8p2:

[Catatan: Umur objek array dimulai segera setelah penyimpanan dengan ukuran dan penyelarasan yang tepat diperoleh, dan masa pakainya berakhir ketika penyimpanan yang digunakan array digunakan kembali atau dirilis. 12.6.2 menggambarkan umur subobjek dasar dan anggota. - catatan akhir]

a memiliki tipe tertentu, itu adalah lvalue of type int[25][80]. Tapi p hanya saja int*. Bukan itu "int* menunjuk ke dalam int[80]"atau semacamnya. Jadi sebenarnya, itu int menunjuk adalah elemen int[25][80] bernama a, dan juga elemen int[2000] menempati ruang yang sama.

Sejak p dan p+1234 keduanya adalah elemen yang sama int[2000] objek, aritmatika pointer didefinisikan dengan baik. Dan sejak itu p[1234] cara *(p+1234), itu juga terdefinisi dengan baik.

Efek dari aturan ini untuk seumur hidup array adalah bahwa Anda dapat dengan bebas menggunakan aritmatika pointer untuk bergerak melalui objek yang lengkap.


Sejak std::array disebutkan dalam komentar:

Jika ada std::array<std::array<int, 80>, 25> a; lalu di sana tidak ada Sebuah std::array<int, 2000>. Memang ada int[2000]. Saya mencari apa pun yang membutuhkan sizeof (std::array<T,N>) == sizeof (T[N]) (dan == N * sizeof (T)). Absen itu, Anda harus berasumsi bahwa mungkin ada celah yang mengacaukan traversal dari bersarang std::array.


0
2018-06-20 00:19



Anda bebas menafsirkan ulang memori dengan cara apa pun yang Anda suka. Selama kelipatannya tidak melebihi memori linier. Anda bahkan dapat memindahkan ke 12, 40 dan menggunakan indeks negatif.


-1
2017-09-01 10:39



Compiler Anda akan membuang banyak peringatan / kesalahan karena subscript di luar jangkauan (baris 2) dan jenis tidak sama (baris 3), tetapi selama variabel yang sebenarnya (int dalam kasus ini) adalah salah satu dari tipe dasar intrinsik ini. disimpan untuk dilakukan di C dan C ++. (Jika variabel adalah kelas / struct itu mungkin akan tetap bekerja di C, tetapi di C ++ semua taruhan tidak aktif.)

Mengapa Anda ingin melakukan ini .... Untuk varian 1: Jika kode Anda bergantung pada jenis ini mengacaukannya akan rawan kesalahan dan sulit dipertahankan dalam jangka panjang.

Saya dapat melihat beberapa penggunaan untuk varian kedua ketika kinerja mengoptimasi loop pada array 2D dengan menggantinya dengan pointer 1D berjalan di atas ruang data, tetapi kompilator pengoptimal yang baik akan sering melakukannya dengan sendirinya. Jika badan pengulangan sangat besar / rumit, kompiler tidak dapat mengoptimalkan / mengganti loop dengan menjalankan 1D pada dirinya sendiri, peningkatan kinerja dengan melakukannya secara manual kemungkinan besar tidak akan signifikan juga.


-1
2017-09-01 10:46