Domanda

Sono curioso di conoscere le convenzioni per puntatori / matrici di tipo punzonatura in C ++. Ecco il caso d'uso che ho al momento:

Calcola un semplice checksum a 32 bit su un BLOB binario di dati trattandolo come un array di numeri interi a 32 bit (sappiamo che la sua lunghezza totale è un multiplo di 4), quindi riassume tutti i valori e ignora l'overflow.

Mi aspetto che una tale funzione sia simile a questa:

uint32_t compute_checksum(const char *data, size_t size)
{
    const uint32_t *udata = /* ??? */;
    uint32_t checksum = 0;
    for (size_t i = 0; i != size / 4; ++i)
        checksum += udata[i];
    return udata;
 }

Ora la domanda che ho è: cosa consideri il "migliore" modo per convertire i dati in udata ?

Cast in stile C?

udata = (const uint32_t *)data

Cast C ++ che presuppone che tutti i puntatori siano convertibili?

udata = reinterpret_cast<const uint32_t *>(data)

C ++ ha eseguito il cast tra tipi di puntatore arbitrari usando void * intermedio?

udata = static_cast<const uint32_t *>(static_cast<const void *>(data))

Cast attraverso un sindacato?

union {
    const uint32_t *udata;
    const char *cdata;
};
cdata = data;
// now use udata

Mi rendo pienamente conto che questa non sarà una soluzione portatile al 100%, ma mi aspetto solo di usarla su un piccolo set di piattaforme dove so che funziona (vale a dire accessi di memoria non allineati e ipotesi del compilatore sull'aliasing dei puntatori). Cosa consiglieresti?

È stato utile?

Soluzione

Per quanto riguarda lo standard C ++, litb ' la risposta è completamente corretta e la più portatile. Trasmissione di const char * data in un const uint3_t * , indipendentemente dal fatto che si tratti di un cast di tipo C, static_cast o reinterpret_cast , infrange le rigide regole di aliasing (vedi Comprensione di Aliasing rigoroso ). Se esegui la compilazione con l'ottimizzazione completa, è probabile che il codice non sia corretto.

Trasmettere attraverso un sindacato (come my_reint di litb) è probabilmente la soluzione migliore, anche se tecnicamente viola la regola che se scrivi a un sindacato attraverso un membro e lo leggi attraverso un altro, provoca un comportamento indefinito. Tuttavia, praticamente tutti i compilatori supportano questo, e si ottiene il risultato atteso. Se desideri assolutamente conformarti allo standard 100%, scegli il metodo di spostamento dei bit. Altrimenti, consiglierei di passare attraverso un sindacato, che probabilmente ti darà prestazioni migliori.

Altri suggerimenti

Ignorando l'efficienza, per semplicità di codice farei:

#include <numeric>
#include <vector>
#include <cstring>

uint32_t compute_checksum(const char *data, size_t size) {
    std::vector<uint32_t> intdata(size/sizeof(uint32_t));
    std::memcpy(&intdata[0], data, size);
    return std::accumulate(intdata.begin(), intdata.end(), 0);
}

Mi piace anche l'ultima risposta di litb, quella che sposta a turno ogni carattere, tranne per il fatto che poiché il carattere potrebbe essere firmato, penso che abbia bisogno di una maschera extra:

checksum += ((data[i] && 0xFF) << shift[i % 4]);

Quando il tipo di punzonatura è un potenziale problema, preferisco non digitare il gioco di parole piuttosto che provare a farlo in modo sicuro. Se in primo luogo non si creano puntatori con alias di tipi distinti, non è necessario preoccuparsi di ciò che il compilatore potrebbe fare con gli alias, e nemmeno il programmatore di manutenzione che vede i tuoi più static_cast attraverso un sindacato.

Se non si desidera allocare troppa memoria aggiuntiva, quindi:

uint32_t compute_checksum(const char *data, size_t size) {
    uint32_t total = 0;
    for (size_t i = 0; i < size; i += sizeof(uint32_t)) {
        uint32_t thisone;
        std::memcpy(&thisone, &data[i], sizeof(uint32_t));
        total += thisone;
    }
    return total;
}

Un'ottimizzazione sufficiente eliminerà la memcpy e la variabile extra uint32_t interamente su gcc e leggerà un valore intero non allineato, qualunque sia il modo più efficiente per farlo che è sulla tua piattaforma, direttamente dall'array di origine. Spero che lo stesso valga per gli altri "gravi" compilatori. Ma questo codice è ora più grande di quello di Litb, quindi non c'è molto da dire perché a parte il mio è più facile trasformarlo in un modello di funzione che funzionerà altrettanto bene con uint64_t, e il mio funziona come endian-native invece di scegliere poco -endian.

Questo ovviamente non è completamente portatile. Presuppone che la rappresentazione di archiviazione dei caratteri sizeof (uint32_t) corrisponda alla rappresentazione di archiviazione di un uin32_t nel modo che desideriamo. Ciò è implicito nella domanda, poiché afferma che si può essere "trattati come" l'altro. Endian-ness, se un carattere è 8 bit e se uint32_t usa tutti i bit nella sua rappresentazione di archiviazione può ovviamente intromettersi, ma la domanda implica che non lo faranno.

Ci sono i miei cinquanta centesimi - modi diversi per farlo.

#include <iostream>
#include <string>
#include <cstring>

    uint32_t compute_checksum_memcpy(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            // memcpy may be slow, unneeded allocation
            uint32_t dest; 
            memcpy(&dest,data+i,4);
            checksum += dest;
        }
        return checksum;
    }

    uint32_t compute_checksum_address_recast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //classic old type punning
            checksum +=  *(uint32_t*)(data+i);
        }
        return checksum;
    }

    uint32_t compute_checksum_union(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //Syntax hell
            checksum +=  *((union{const char* c;uint32_t* i;}){.c=data+i}).i;
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_deref(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *&data[i];
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_cast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *(data+i);
        }
        return checksum;
    }


int main()
{
    const char* data = "ABCDEFGH";
    std::cout << compute_checksum_memcpy(data, 8) << " OK\n";
    std::cout << compute_checksum_address_recast(data, 8) << " OK\n";
    std::cout << compute_checksum_union(data, 8) << " OK\n";
    std::cout << compute_checksum_deref(data, 8) << " Fail\n";
    std::cout << compute_checksum_cast(data, 8) << " Fail\n";
}

So che questa discussione è inattiva da un po ', ma ho pensato di pubblicare una semplice routine di casting generica per questo tipo di cose:

// safely cast between types without breaking strict aliasing rules
template<typename ReturnType, typename OriginalType>
ReturnType Cast( OriginalType Variable )
{
    union
    {
        OriginalType    In;
        ReturnType      Out;
    };

    In = Variable;
    return Out;
}

// example usage
int i = 0x3f800000;
float f = Cast<float>( i );

Spero che aiuti qualcuno!

Questo sembra un esempio di caso di quando usare reinterpret_cast , qualsiasi altra cosa ti darà lo stesso effetto senza l'esplicitazione che ottieni dall'usare un costrutto linguistico per il suo uso ufficiale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top