Frage

Ich bin neugierig auf Konventionen für Typ-punning Zeiger / Arrays in C ++. Hier ist die Verwendung Fall, dass ich im Moment habe:

Berechnen eines einfachen 32-Bit-Prüfsumme über einen binären Blob von Daten, indem es als eine Anordnung von 32-Bit-Integer Behandlung (wir wissen, seine Gesamtlänge ein Vielfaches von 4 ist), und dann werden alle Werte zusammenfassend und Überlauf ignoriert.

Ich würde erwarten, eine solche Funktion wie folgt aussehen:

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

Nun ist die Frage, die ich habe, ist, was halten Sie für den „besten“ Weg data zu konvertieren udata?

C-Casts?

udata = (const uint32_t *)data

C ++ Besetzung, die alle Zeiger sind wandelbar geht davon aus?

udata = reinterpret_cast<const uint32_t *>(data)

C ++ als solche, dass zwischen beliebigen Zeigertypen unter Verwendung von Zwischen void*?

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

Die Besetzung durch eine Vereinigung?

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

Ich stelle fest, voll, dass dies keine 100% portable Lösung sein wird, aber ich erwarte es nur auf eine kleine Gruppe von Plattformen zu verwenden, wo ich weiß, es funktioniert (und zwar nicht ausgerichteten Speicherzugriffe und Compiler Annahmen über Zeiger Aliasing). Was würden Sie empfehlen?

War es hilfreich?

Lösung

Was die C ++ Standard betrifft, litb ‘ s Antwort ist völlig richtig, und die meisten tragbaren. Casting const char *data zu einem const uint3_t *, ob es über einen C-Casts sein, static_cast oder reinterpret_cast, bricht die strengen Aliasing-Regeln (siehe Strenge Aliasing Verständnis). Wenn Sie mit voller Optimierung kompilieren, gibt es eine gute Chance, den Code wird nicht auf die richtige Sache.

Casting durch eine Vereinigung (wie litb des my_reint) ist wahrscheinlich die beste Lösung, obwohl es technisch die Regel verstößt, dass, wenn Sie zu einer Vereinigung durch ein Mitglied schreiben und sie durch eine andere lesen, es in undefinierten Verhalten führt. Doch praktisch alle Compiler unterstützen dies, und es ergibt sich die dem erwarteten Ergebnis. Wenn Sie unbedingt zum Standard 100% entsprechen wollen, gehen Sie mit dem Bit-Schiebeverfahren. Ansonsten würde ich empfehlen, mit Gießen durch eine Vereinigung geht, was wahrscheinlich ist, dass Sie eine bessere Leistung geben.

Andere Tipps

Das Ignorieren Effizienz, zur Vereinfachung des Code, den ich tun würde:

#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);
}

Ich mag auch litb die letzte Antwort, die, die jedes Zeichen wiederum verschiebt, mit der Ausnahme, dass seit char unterzeichnet werden könnte, halte ich es für eine zusätzliche Maske benötigt:

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

Wenn Typ punning ein potenzielles Problem ist, ziehe ich es nicht pun zu geben, anstatt zu versuchen, so sicher zu tun. Wenn Sie keine Alias-Zeiger von verschiedenen Arten in erster Linie erstellen, dann müssen Sie keine Sorge, was der Compiler mit Aliase tun könnte, und auch nicht die Wartung Programmierer, der Ihre mehrfachen static_casts durch eine Vereinigung sieht.

Wenn Sie nicht möchten, dass so viele zusätzlichen Speicher zuweisen, dann:

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

Genug Optimierung wird der Memcpy loszuwerden und das zusätzlichen uint32_t Variable ganz auf gcc, und nur einen ganzzahligen Wert unaligned gelesen, in welcher die effizienteste Art und Weise zu tun, die auf Ihrer Plattform, gerade aus dem Quell-Array. Ich würde hoffen, das gleiche gilt für andere „schwere“ Compiler ist. Aber dieser Code ist jetzt größer als litb ist, so gibt es nicht viel gesagt werden, denn es außer ich ist leichter in eine Funktionsvorlage zu drehen, die genauso gut mit uint64_t arbeiten, und ich arbeitet als native Bytereihenfolge eher als wenig Kommissionierung -endian.

Das ist natürlich nicht vollständig tragbar. Er geht davon aus, dass der Speicher Darstellung von sizeof (uint32_t) Zeichen an den Speicher Darstellung eines uin32_t in der Art, wie wir wollen, entspricht. Dies wird durch die Frage impliziert, da es heißt, dass man die anderen „wie er behandelt“ werden. Endian-ness, ob ein Zeichen 8 Bit ist, und ob uint32_t all Bits in seinem Speicher Darstellung verwendet, kann offensichtlich stören, aber die Frage impliziert, dass sie nicht.

Es gibt meinen fünfzig Cent - verschiedene Möglichkeiten, es zu tun

.
#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";
}

Ich weiß, dass dieser Thread eine Zeit lang nicht aktiv war, aber dachte, dass ich eine einfache generische Casting-Routine für diese Art der Sache schreiben würde:

// 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 );

Hoffe, es hilft jemand!

Das sieht wie ein Fallbeispiel für wenn reinterpret_cast zu verwenden, alles andere wird Ihnen die gleiche Wirkung ohne die Eindeutigkeit Sie verwenden eine Sprache für den amtlichen Gebrauch konstruieren erhalten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top