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
Pixel
tipe 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
)
Pixel
s ditempatkan baru masih bukan arrayPixel
s.pixels[some_index]
atau*(pixels + something)
? Itu akan menjadi UB.pixels
(P) bukan pointer ke objek array tetapi pointer ke satuPixel
. Itu berarti Anda hanya dapat mengaksespixels[0]
secara legal.Jawaban:
Ini adalah perilaku yang tidak ditentukan untuk menggunakan hasil
promote
sebagai array. Jika kita melihat [expr.add] /4.2 yang kita milikikita melihat bahwa itu membutuhkan pointer untuk benar-benar menunjuk ke objek array. Anda sebenarnya tidak memiliki objek array. Anda memiliki pointer ke satu
Pixel
yang kebetulan memiliki lainnyaPixels
mengikutinya 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.sumber
&somevector[0] + 1
UB (well, maksud saya, menggunakan pointer yang dihasilkan).Anda sudah memiliki jawaban mengenai penggunaan terbatas dari pointer yang dikembalikan, tetapi saya ingin menambahkan bahwa saya juga berpikir Anda perlu
std::launder
untuk dapat mengakses yang pertamaPixel
:Ini
reinterpret_cast
dilakukan sebelumPixel
objek apa pun dibuat (dengan asumsi Anda tidak melakukannyagetSomeImageData
). Karena itureinterpret_cast
tidak akan mengubah nilai pointer. Pointer yang dihasilkan masih akan menunjuk ke elemen pertamastd::byte
array yang diteruskan ke fungsi.Ketika Anda membuat
Pixel
objek, mereka akan bersarang di dalamstd::byte
array danstd::byte
array akan menyediakan penyimpanan untukPixel
objek.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
result
masih akan menunjuk kestd::byte
objek, bukanPixel
objek. Saya kira menggunakannya seolah-olah itu menunjuk ke suatuPixel
objek secara teknis akan menjadi perilaku yang tidak terdefinisi.Saya pikir ini masih berlaku, bahkan jika Anda melakukan
reinterpret_cast
setelah membuatPixel
objek, karenaPixel
objek danstd::byte
penyimpanan yang menyediakannya tidak pointer-interconvertible . Jadi meski begitu pointer akan tetap menunjuk kestd::byte
, bukanPixel
objek.Jika Anda memperoleh pointer untuk kembali dari hasil salah satu penempatan-baru, maka semuanya akan baik-baik saja, sejauh menyangkut akses ke
Pixel
objek tertentu tersebut.Anda juga perlu memastikan bahwa
std::byte
pointer sesuai untukPixel
dan bahwa array benar-benar cukup besar. Sejauh yang saya ingat standar tidak benar-benar mengharuskan yangPixel
memiliki keselarasan yang samastd::byte
atau tidak memiliki padding.Juga tidak satu pun dari ini tergantung pada
Pixel
menjadi sepele atau benar-benar milik lainnya. Semuanya akan berperilaku dengan cara yang sama selamastd::byte
array berukuran cukup dan sesuai untukPixel
objek.sumber
std::vector
) tidak masalah, Anda masih perlu untukstd::launder
hasilnya sebelum mengakses salah satu bertarget penempatannew
edPixel
s. Sampai sekarang, distd::launder
sini adalah UB, karena yang berdekatanPixel
akan dapat dijangkau dari pointer yang dicuci .std::launder
akan menjadi UB jika diterapkanresult
sebelum kembali. Yang bersebelahanPixel
tidak " 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 seluruhstd::byte
array asli dapat dijangkau dari pointer asli.Pixel
tidak akan bisa dijangkau daristd::byte
pointer, tetapi darilaunder
ed pointer. Saya percaya ini relevan di sini. Saya senang dikoreksi.Pixel
tampaknya dapat dijangkau oleh saya dari pointer asli, karena pointer asli menunjuk ke elemenstd::byte
array yang berisi byte yang membuat penyimpanan untukPixel
pembuatan " atau di dalam array segera melampirkan di mana Z adalah sebuah unsur "ketentuan berlaku (di manaZ
adalahY
, yaitustd::byte
elemen itu sendiri).Pixel
menempati berikutnya tidak dapat dicapai melalui pointer dicuci, karena objek menunjuk-kePixel
bukan elemen objek array dan juga tidak pointer-dipertukarkan dengan objek lain yang relevan. Tapi saya juga memikirkan detail inistd::launder
untuk pertama kalinya di kedalaman itu. Saya juga tidak 100% yakin tentang ini.