Variazione sul tema della punzonatura del tipo: costruzione banale sul posto


9

So che questo è un argomento piuttosto comune, ma per quanto il tipico UB sia facile da trovare, finora non ho trovato questa variante.

Quindi, sto cercando di introdurre formalmente oggetti Pixel evitando una copia effettiva dei dati.

È valido?

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

Modello di utilizzo previsto, fortemente semplificato:

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

Più specificamente:

  • Questo codice ha un comportamento ben definito?
  • In caso affermativo, è sicuro utilizzare il puntatore restituito?
  • Se sì, a quali altri Pixeltipi può essere esteso? (rilassando la restrizione is_trivial? pixel con solo 3 componenti?).

Sia clang che gcc ottimizzano l'intero ciclo fino al nulla, che è quello che voglio. Ora, vorrei sapere se questo viola alcune regole C ++ o no.

Godbolt link se vuoi giocare con esso.

(nota: nonostante non abbia taggato c ++ 17 std::byte, perché la domanda è ancora valida char)


2
Ma Pixeli nuovi contigui inseriti non sono ancora un array di Pixels.
Jarod42,

1
@spectras Questo però non costituisce un array. Hai solo un mucchio di oggetti Pixel uno accanto all'altro. È diverso da un array.
NathanOliver

1
Quindi no dove fai pixels[some_index]o *(pixels + something)? Sarebbe UB.
NathanOliver

1
La sezione pertinente è qui e la frase chiave è se P indica un elemento array i di un oggetto array x . Qui pixels(P) non è un puntatore all'oggetto array ma un puntatore a un singolo Pixel. Ciò significa che puoi accedere solo pixels[0]legalmente.
NathanOliver

3
Vuoi leggere wg21.link/P0593 .
ecatmur

Risposte:


3

È un comportamento indefinito utilizzare il risultato promotecome una matrice. Se guardiamo [expr.add] /4.2 abbiamo

Altrimenti, se Ppunta a un elemento array idi un oggetto arrayx con nelementi ([dcl.array]), le espressioni P + Je J + P(dove Jha il valore j) puntano all'elemento array (possibilmente ipotetico) i+jdi xif 0≤i+j≤ne l'espressione P - Jpunta al ( possibilmente ipotetico) elemento array i−jdi xif 0≤i−j≤n.

vediamo che richiede il puntatore per puntare effettivamente a un oggetto array. In realtà non hai un oggetto array. Hai un puntatore a un singolo Pixelche sembra averne altri che lo Pixelsseguono in memoria contigua. Ciò significa che l'unico elemento a cui puoi effettivamente accedere è il primo elemento. Cercare di accedere a qualsiasi altra cosa sarebbe un comportamento indefinito perché hai superato la fine del dominio valido per il puntatore.


Grazie per averlo scoperto così in fretta. Farò un iteratore invece suppongo. Come sidenote, questo significa anche che &somevector[0] + 1è UB (beh, voglio dire, usando il puntatore risultante sarebbe).
extra

@spectras In realtà va bene. Puoi sempre ottenere il puntatore a uno oltre un oggetto. Non puoi semplicemente dereferenziare quel puntatore, anche se c'è un oggetto valido lì.
NathanOliver

Sì, ho modificato il commento per rendermi più chiaro, intendevo dereferenziare il puntatore risultante :) Grazie per aver confermato.
extra

@spectras Nessun problema. Questa parte del C ++ può essere molto difficile. Anche se l'hardware farà ciò che vogliamo, non è in realtà ciò a cui era destinato un codice. Stiamo programmando la macchina astratta C ++ ed è una macchina persnickety;) Speriamo che P0593 venga adottato e questo diventerà molto più semplice.
NathanOliver,

1
@spectras No, perché un vettore std è definito come contenente un array e puoi eseguire l'aritmetica del puntatore tra gli elementi dell'array. Non c'è modo di implementare il vettore std nello stesso C ++, purtroppo, senza imbattersi in UB.
Yakk - Adam Nevraumont,

1

Hai già una risposta per quanto riguarda l'uso limitato del puntatore restituito, ma voglio aggiungere che penso anche che tu debba std::launderanche essere in grado di accedere al primo Pixel:

Il reinterpret_castè fatto prima di qualsiasi Pixelsi crea oggetto (a patto che non farlo in getSomeImageData). Pertanto reinterpret_castnon cambierà il valore del puntatore. Il puntatore risultante punterà comunque al primo elemento std::bytedell'array passato alla funzione.

Quando si creano gli Pixeloggetti, verranno annidati all'interno std::bytedell'array e l' std::bytearray fornirà l'archiviazione per gli Pixeloggetti.

Ci sono casi in cui il riutilizzo della memoria provoca un puntatore al vecchio oggetto che punta automaticamente al nuovo oggetto. Ma questo non è ciò che sta accadendo qui, quindi resultindicherà ancora l' std::byteoggetto, non l' Pixeloggetto. Immagino che usarlo come se indicasse un Pixeloggetto tecnicamente sarà un comportamento indefinito.

Penso che ciò valga ancora, anche se si fa reinterpret_castdopo aver creato l' Pixeloggetto, poiché l' Pixeloggetto e quello std::byteche fornisce l'archiviazione per esso non sono interconvertibili dal puntatore . Quindi anche in questo caso il puntatore continuerebbe a puntare sull'oggetto std::byte, non Pixelsull'oggetto.

Se hai ottenuto il puntatore per tornare dal risultato di uno dei nuovi posizionamenti, allora tutto dovrebbe andare bene, per quanto riguarda l'accesso a Pixelquell'oggetto specifico .


Inoltre, è necessario assicurarsi che il std::bytepuntatore sia adeguatamente allineato Pixele che l'array sia davvero abbastanza grande. Per quanto ricordo, lo standard non richiede realmente che Pixelabbia lo stesso allineamento std::byteo che non abbia imbottitura.


Inoltre, nulla di tutto ciò dipende Pixeldall'essere banale o da qualsiasi altra sua proprietà. Tutto si comporterebbe allo stesso modo purché l' std::bytearray sia di dimensioni sufficienti e adeguatamente allineato per gli Pixeloggetti.


Credo sia corretto. Anche se la cosa array (unimplementability di std::vector) non era un problema, si sarebbe comunque necessario std::launderil risultato prima di accedere una delle placement- newed Pixels. A partire da ora, std::launderecco UB, poiché le Pixels adiacenti sarebbero raggiungibili dal puntatore riciclato .
Fureeish

@Fureeish Non sono sicuro del motivo per cui std::laundersarebbe UB se applicato resultprima di tornare. L'adiacente Pixelnon è " raggiungibile " attraverso il puntatore riciclato che va dalla mia comprensione di eel.is/c++draft/ptr.launder#4 . E anche se non vedo come sia UB, perché l'intero std::bytearray originale è raggiungibile dal puntatore originale.
noce

Ma il prossimo Pixelnon sarà raggiungibile dal std::bytepuntatore, ma dal launderpuntatore ed. Credo che questo sia rilevante qui. Sono felice di essere corretto, però.
Fureeish

@Fureeish Da quello che posso dire nessuno degli esempi forniti si applica qui e la definizione del requisito dice lo stesso dello standard. La raggiungibilità è definita in termini di byte di memoria, non di oggetti. Il byte occupato dal prossimo Pixelmi sembra raggiungibile dal puntatore originale, perché il puntatore originale punta a un elemento std::bytedell'array che contiene i byte che costituiscono l'archiviazione per la Pixelcreazione del " o all'interno dell'array immediatamente racchiuso di cui Z è un si applica la condizione " element " (dove si Ztrova Y, cioè l' std::byteelemento stesso).
noce

Penso che i byte di memoria che Pixeloccupa il prossimo non siano raggiungibili tramite il puntatore riciclato, poiché l' Pixeloggetto puntato non è elemento di un oggetto array e non è neppure interconnettibile con nessun altro oggetto rilevante. Ma sto anche pensando a questo dettaglio std::launderper la prima volta in quella profondità. Non ne sono sicuro al 100%.
noce
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.