Variasi pada tema jenis hukuman: konstruksi sepele di tempat

9

Saya tahu ini adalah subjek yang cukup umum, tetapi sebanyak UB tipikal mudah ditemukan, saya tidak menemukan varian ini sejauh ini.

Jadi, saya mencoba memperkenalkan objek Pixel secara resmi sambil menghindari salinan data yang sebenarnya.

Apakah ini valid?

struct Pixel {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
};

static_assert(std::is_trivial_v<Pixel>);

Pixel* promote(std::byte* data, std::size_t count)
{
    Pixel * const result = reinterpret_cast<Pixel*>(data);
    while (count-- > 0) {
        new (data) Pixel{
            std::to_integer<uint8_t>(data[0]),
            std::to_integer<uint8_t>(data[1]),
            std::to_integer<uint8_t>(data[2]),
            std::to_integer<uint8_t>(data[3])
        };
        data += sizeof(Pixel);
    }
    return result; // throw in a std::launder? I believe it is not mandatory here.
}

Pola penggunaan yang diharapkan, sangat disederhanakan:

std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data

Lebih spesifik:

  • Apakah kode ini memiliki perilaku yang jelas?
  • Jika ya, apakah aman menggunakan pointer yang dikembalikan?
  • Jika ya, untuk Pixeltipe apa lagi dapat diperpanjang? (mengendurkan batasan is_trivial? pixel dengan hanya 3 komponen?).

Baik dentang dan gcc mengoptimalkan seluruh loop ke ketiadaan, itulah yang saya inginkan. Sekarang, saya ingin tahu apakah ini melanggar beberapa aturan C ++ atau tidak.

Link Godbolt jika Anda ingin bermain-main dengannya.

(catatan: Saya tidak memberi tag c ++ 17 std::byte, karena pertanyaannya tetap menggunakan char)

spektrum
sumber
2
Tapi berdekatan Pixels ditempatkan baru masih bukan array Pixels.
Jarod42
1
@spectras Itu tidak membuat array. Anda hanya memiliki banyak objek Pixel di samping satu sama lain. Itu berbeda dari sebuah array.
NathanOliver
1
Jadi tidak di mana Anda melakukannya pixels[some_index]atau *(pixels + something)? Itu akan menjadi UB.
NathanOliver
1
Bagian yang relevan ada di sini dan frasa kunci adalah jika P menunjuk ke elemen array i dari objek array x . Di sini pixels(P) bukan pointer ke objek array tetapi pointer ke satu Pixel. Itu berarti Anda hanya dapat mengakses pixels[0]secara legal.
NathanOliver
3
Anda ingin membaca wg21.link/P0593 .
ecatmur

Jawaban:

3

Ini adalah perilaku yang tidak ditentukan untuk menggunakan hasil promotesebagai array. Jika kita melihat [expr.add] /4.2 yang kita miliki

Jika tidak, jika Pmenunjuk ke elemen array idari objek arrayx dengan nelemen ([dcl.array]), ekspresi P + Jdan J + P(di mana Jmemiliki nilai j) menunjuk ke elemen array (mungkin-hipotetis) i+jdari xjika 0≤i+j≤ndan ekspresi P - Jmenunjuk ke ( mungkin-hipotetis) elemen array i−jdari xjika 0≤i−j≤n.

kita melihat bahwa itu membutuhkan pointer untuk benar-benar menunjuk ke objek array. Anda sebenarnya tidak memiliki objek array. Anda memiliki pointer ke satu Pixelyang kebetulan memiliki lainnya Pixelsmengikutinya dalam memori yang berdekatan. Itu berarti satu-satunya elemen yang dapat Anda akses adalah elemen pertama. Mencoba mengakses hal lain adalah perilaku yang tidak terdefinisi karena Anda melewati akhir domain yang valid untuk penunjuk.

NathanOliver
sumber
Terima kasih telah mengetahui secepat itu. Saya akan membuat iterator saja. Sebagai sidenote, ini juga berarti &somevector[0] + 1UB (well, maksud saya, menggunakan pointer yang dihasilkan).
spektrum
@spectras Sebenarnya tidak apa-apa. Anda selalu bisa mendapatkan pointer ke satu melewati objek. Anda tidak bisa melakukan dereferensi pointer itu, bahkan jika ada objek yang valid di sana.
NathanOliver
Ya, saya mengedit komentar untuk membuat diri saya lebih jelas, maksud saya dereferencing pointer yang dihasilkan :) Terima kasih telah mengkonfirmasi.
spektrum
@spectras Tidak masalah. Bagian C ++ ini bisa sangat sulit. Meskipun perangkat keras akan melakukan apa yang kita inginkan, itu sebenarnya bukan tujuan pengkodean. Kami sedang mengkode ke mesin abstrak C ++ dan ini adalah mesin persnickety;) Semoga P0593 akan diadopsi dan ini akan menjadi jauh lebih mudah.
NathanOliver
1
@spectras Tidak, karena vektor std didefinisikan sebagai berisi array, dan Anda dapat melakukan aritmatika pointer antara elemen array. Tidak ada cara untuk mengimplementasikan vektor std di C ++ itu sendiri, sayangnya, tanpa berlari ke UB.
Yakk - Adam Nevraumont
1

Anda sudah memiliki jawaban mengenai penggunaan terbatas dari pointer yang dikembalikan, tetapi saya ingin menambahkan bahwa saya juga berpikir Anda perlu std::launderuntuk dapat mengakses yang pertama Pixel:

Ini reinterpret_castdilakukan sebelum Pixelobjek apa pun dibuat (dengan asumsi Anda tidak melakukannya getSomeImageData). Karena itu reinterpret_casttidak akan mengubah nilai pointer. Pointer yang dihasilkan masih akan menunjuk ke elemen pertama std::bytearray yang diteruskan ke fungsi.

Ketika Anda membuat Pixelobjek, mereka akan bersarang di dalam std::bytearray dan std::bytearray akan menyediakan penyimpanan untuk Pixelobjek.

Ada kasus di mana penggunaan kembali penyimpanan menyebabkan pointer ke objek lama secara otomatis menunjuk ke objek baru. Tapi ini bukan yang terjadi di sini, jadi resultmasih akan menunjuk ke std::byteobjek, bukan Pixelobjek. Saya kira menggunakannya seolah-olah itu menunjuk ke suatu Pixelobjek secara teknis akan menjadi perilaku yang tidak terdefinisi.

Saya pikir ini masih berlaku, bahkan jika Anda melakukan reinterpret_castsetelah membuat Pixelobjek, karena Pixelobjek dan std::bytepenyimpanan yang menyediakannya tidak pointer-interconvertible . Jadi meski begitu pointer akan tetap menunjuk ke std::byte, bukan Pixelobjek.

Jika Anda memperoleh pointer untuk kembali dari hasil salah satu penempatan-baru, maka semuanya akan baik-baik saja, sejauh menyangkut akses ke Pixelobjek tertentu tersebut.


Anda juga perlu memastikan bahwa std::bytepointer sesuai untuk Pixeldan bahwa array benar-benar cukup besar. Sejauh yang saya ingat standar tidak benar-benar mengharuskan yang Pixelmemiliki keselarasan yang sama std::byteatau tidak memiliki padding.


Juga tidak satu pun dari ini tergantung pada Pixelmenjadi sepele atau benar-benar milik lainnya. Semuanya akan berperilaku dengan cara yang sama selama std::bytearray berukuran cukup dan sesuai untuk Pixelobjek.

kenari
sumber
Saya percaya itu benar. Bahkan jika hal array (unimplementability dari std::vector) tidak masalah, Anda masih perlu untuk std::launderhasilnya sebelum mengakses salah satu bertarget penempatan newed Pixels. Sampai sekarang, di std::laundersini adalah UB, karena yang berdekatan Pixelakan dapat dijangkau dari pointer yang dicuci .
Fureeish
@Fureeish Saya tidak yakin mengapa std::launderakan menjadi UB jika diterapkan resultsebelum kembali. Yang bersebelahan Pixeltidak " dapat dijangkau " melalui pointer yang dicuci berdasarkan pemahaman saya tentang eel.is/c++draft/ptr.launder#4 . Dan bahkan itu saya tidak melihat bagaimana itu adalah UB, karena seluruh std::bytearray asli dapat dijangkau dari pointer asli.
kenari
Tetapi selanjutnya Pixeltidak akan bisa dijangkau dari std::bytepointer, tetapi dari laundered pointer. Saya percaya ini relevan di sini. Saya senang dikoreksi.
Fureeish
@ Fureeish Dari apa yang saya dapat katakan tidak ada contoh yang diberikan berlaku di sini dan definisi persyaratan juga mengatakan hal yang sama dengan standar. Reachability didefinisikan dalam hal byte penyimpanan, bukan objek. Byte yang ditempati oleh berikutnya Pixeltampaknya dapat dijangkau oleh saya dari pointer asli, karena pointer asli menunjuk ke elemen std::bytearray yang berisi byte yang membuat penyimpanan untuk Pixelpembuatan " atau di dalam array segera melampirkan di mana Z adalah sebuah unsur "ketentuan berlaku (di mana Zadalah Y, yaitu std::byteelemen itu sendiri).
kenari
Saya berpikir bahwa byte penyimpanan yang Pixelmenempati berikutnya tidak dapat dicapai melalui pointer dicuci, karena objek menunjuk-ke Pixelbukan elemen objek array dan juga tidak pointer-dipertukarkan dengan objek lain yang relevan. Tapi saya juga memikirkan detail ini std::launderuntuk pertama kalinya di kedalaman itu. Saya juga tidak 100% yakin tentang ini.
kenari