Punning türü yazın: yerinde önemsiz yapı


9

Bunun oldukça yaygın bir konu olduğunu biliyorum, ancak tipik UB'nin bulunması kolay olduğu kadarıyla, bu varyantı bulamadım.

Yani, verilerin gerçek bir kopyasını kaçınarak resmi olarak Pixel nesnelerini tanıtmaya çalışıyorum.

Bu geçerli mi?

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.
}

Beklenen kullanım şekli, büyük ölçüde basitleştirilmiş:

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

Daha spesifik olarak:

  • Bu kod iyi tanımlanmış bir davranışa sahip mi?
  • Evetse, döndürülen işaretçiyi kullanmayı güvenli hale getirir mi?
  • Evet ise, başka hangi Pixeltürlere genişletilebilir? (is_trivial sınırlama? pikselini sadece 3 bileşenle gevşetmek?).

Hem clang hem de gcc tüm döngüyü hiçliğe göre optimize eder, bu da istediğim şeydir. Şimdi, bunun bazı C ++ kurallarını ihlal edip etmediğini bilmek istiyorum.

Onunla oynamak istiyorsanız Godbolt bağlantısı .

(Not: std::byteSoru kullanılmaya devam ettiğinden c ++ 17 etiketlemedim char)


2
Ancak Pixelyeni yerleştirilen bitişik s hala bir Pixels dizisi değildir .
Jarod42

1
@spectras Bu bir dizi yapmaz. Yan yana bir grup Pixel nesneniz var. Bu bir diziden farklı.
NathanOliver

1
Öyleyse hayır, nerede pixels[some_index]ya da *(pixels + something)? Bu UB olurdu.
NathanOliver

1
İlgili bölüm buradadır ve anahtar kelime, P bir dizi nesnesinin x bir dizi elemanına i işaret ediyorsa . Burada pixels(P) dizi nesnesine bir gösterici değil, tek bir göstericidir Pixel. Bu, yalnızca pixels[0]yasal olarak erişebileceğiniz anlamına gelir .
NathanOliver

Yanıtlar:


3

Sonucu promotedizi olarak kullanmak tanımsız bir davranıştır . Biz bakarsak [expr.add] /4.2 Elimizdeki

Aksi takdirde, öğeler ([dcl.array]) içeren bir dizi nesnesinin bir dizi öğesine işaret ediyorsaPixn , ifadeler P + Jve J + P( Jdeğerin olduğu yerde j) if öğesinin (muhtemelen varsayımsal) dizi öğesine i+jişaret xeder 0≤i+j≤nve ifade P - J( muhtemelen, varsayımsal-) dizi elemanı i−jarasında xise 0≤i−j≤n.

işaretçinin bir dizi nesnesini göstermesini gerektirdiğini görüyoruz. Aslında bir dizi nesneniz yok. Bitişik bellekte Pixelbaşka bir Pixelsizleyen tek bir işaretçi var . Bu aslında erişebileceğiniz tek elemanın ilk eleman olduğu anlamına gelir. İşaretçinin geçerli etki alanının sonunu geçtiğiniz için başka bir şeye erişmeye çalışmak tanımsız bir davranış olacaktır.


Bunu çabuk bulduğun için teşekkürler. Bunun yerine bir yineleyici yapacağım sanırım. Bir sidenote olarak, bu aynı zamanda &somevector[0] + 1UB anlamına gelir (yani, sonuçtaki işaretçiyi kullanmak olurdu).
spectras

@spectras Aslında sorun değil. İşaretçiyi her zaman bir nesnenin ötesine getirebilirsiniz. Orada geçerli bir nesne olsa bile, o işaretçiyi kaldıramazsınız.
NathanOliver

Evet, yorumu kendim daha açık hale getirmek için düzenledim, sonuçta ortaya çıkan işaretçiyi silme işlemini kastetmiştim :) Onayladığınız için teşekkürler.
spectras

@spectras Sorun değil. C ++ 'ın bu kısmı çok zor olabilir. Donanım yapmasını istediğimiz şeyi yapsa da, aslında bir kodlama değildi. C ++ soyut makineye kodlama yapıyoruz ve bu bir persnickety makinesi;) Umarım P0593 benimsenir ve bu çok daha kolay hale gelir.
NathanOliver

1
@spectras Hayır, çünkü std vektörü bir dizi içerdiği olarak tanımlanır ve dizi öğeleri arasında işaretçi aritmetiği yapabilirsiniz. Std vektörünü ne yazık ki UB'ye girmeden C ++ 'da uygulamanın hiçbir yolu yoktur.
Yakk - Adam Nevraumont

1

Döndürülen işaretçinin sınırlı kullanımı ile ilgili zaten bir cevabınız var, ancak ilkine std::laundererişebilmeniz gerektiğini de düşündüğümü eklemek istiyorum Pixel:

reinterpret_castHerhangi önce yapılır Pixelnesne oluşturulur (eğer öyle yapmayın varsayarak getSomeImageData). Bu nedenle reinterpret_castişaretçi değerini değiştirmez. Ortaya çıkan işaretçi yine std::bytede işleve iletilen dizinin ilk öğesini gösterecektir.

Eğer oluştururken Pixelnesneleri, bunlar olacak yuvalanmış içinde std::bytedizinin ve std::bytedizi edilecek depolama sağlayan yönelik Pixelnesneler.

Depolama alanının yeniden kullanılmasının eski nesneye bir işaretçi otomatik olarak yeni nesneyi göstermesine neden olduğu durumlar vardır. Ama burada olan şey bu değil, bu yüzden resultyine de std::bytenesneyi değil, Pixelnesneyi gösterecek . Sanırım bir Pixelnesneyi işaret ediyormuş gibi kullanıyorum , teknik olarak tanımlanmamış bir davranış olacak.

Nesneyi reinterpret_castoluşturduktan sonra yapsanız bile, bunun hala geçerli olduğunu düşünüyorum Pixel, çünkü Pixelnesne ve bunun std::byteiçin depolama alanı işaretçi ile dönüştürülemez . Böylece işaretçi bile nesneyi std::bytedeğil, Pixelnesneyi göstermeye devam ederdi .

İşaretçiyi yeni yerleşimden birinin sonucundan döndürmek için aldıysanız Pixel, söz konusu nesneye erişim söz konusu olduğunda , her şey yolunda olmalıdır .


Ayrıca std::byteişaretçinin uygun şekilde hizalandığından Pixelve dizinin gerçekten yeterince büyük olduğundan emin olmanız gerekir . Hatırladığım kadarıyla standart gerçekten Pixelaynı hizalamaya sahip std::byteveya dolgu içermiyor gerektirmez .


Ayrıca bunların hiçbiri Pixelönemsiz veya gerçekten başka bir mülke bağlı değildir. std::byteDizi yeterli büyüklükte ve Pixelnesneler için uygun şekilde hizalandığı sürece her şey aynı şekilde davranır .


Bunun doğru olduğuna inanıyorum. Dizi şey (bir unimplementability bile std::vector) bir sorun değildi, yine de ihtiyacım olacağını std::launderYerleşim herhangi birine erişmeden önce sonucu newed Pixels. Şu an itibariyle, std::launderburada UB var, çünkü bitişik Pixels aklanmış işaretçiden ulaşılabilir .
Fureeish

@Fureeish Geri dönmeden önce std::launderuygulandığında neden UB olacağından emin değilim result. Bitişik eel.is/c++draft/ptr.launder#4 anlayışımla aklanan işaretli işaretçi aracılığıyla Pixel" ulaşılamaz " değildir . Ve ben bile nasıl UB olduğunu görmüyorum, çünkü tüm orijinal dizi orijinal işaretçiden ulaşılabilir . std::byte
ceviz

Ancak bir sonraki işaretçiye Pixelulaşılamaz std::byte, ancak laundered işaretçisinden gelir. İnanıyorum bu burada önemlidir. Yine de düzeltildiğim için mutluyum.
Fureeish

@Fureeish Verilen örneklerin hiçbiri burada geçerli değildir ve gereksinimin tanımı da standartla aynı şeyi söyler. Erişilebilirlik, nesnelerin değil, depolama baytları cinsinden tanımlanır. Bir sonraki tarafından işgal edilen bayt Pixelbana orijinal işaretçiden erişilebiliyor gibi görünüyor, çünkü orijinal işaretçi, std::bytediziyi "yapmak için depolamayı oluşturan baytları içeren Pixel" veya Z'nin bir eleman "koşulu (burada geçerli Zolduğu Y, yani std::byteeleman kendisi).
Ceviz

Ben sonraki sonraki Pixelkaplar depolama bayt olsa aklanmış işaretçi aracılığıyla ulaşılamaz olduğunu düşünüyorum , çünkü sivri- Pixelnesne bir dizi nesnesinin unsuru değildir ve aynı zamanda herhangi bir diğer ilgili nesne ile işaretçi-birbirine dönüştürülebilir değildir. Ama ben de std::launderbu derinlikte ilk kez bu ayrıntıyı düşünüyorum . Ben de bu konuda% 100 emin değilim.
Ceviz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.