Question

Je suis curieux de connaître les conventions relatives aux pointeurs / matrices de typage en C ++. Voici le cas d'utilisation que j'ai pour le moment:

Calculez une somme de contrôle 32 bits simple sur un blob binaire de données en le traitant comme un tableau d'entiers 32 bits (nous savons que sa longueur totale est un multiple de 4), puis en additionnant toutes les valeurs et en ignorant les débordements.

Je m'attendrais à ce qu'une telle fonction ressemble à ceci:

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

Maintenant, la question que je me pose est la suivante: que considérez-vous comme le "meilleur"? moyen de convertir les données en udata ?

Casting de style C?

udata = (const uint32_t *)data

La conversion C ++ suppose que tous les pointeurs sont convertibles?

udata = reinterpret_cast<const uint32_t *>(data)

C ++ transpose cela entre les types de pointeurs arbitraires utilisant un void * ?

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

Cast à travers un syndicat?

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

Je suis tout à fait conscient que ce ne sera pas une solution 100% portable, mais je ne compte l’utiliser que sur un petit ensemble de plates-formes sur lesquelles je sais que cela fonctionne (à savoir des accès mémoire non alignés et des hypothèses du compilateur sur les alias de pointeur). Que recommanderiez-vous?

Était-ce utile?

La solution

En ce qui concerne le standard C ++, litb ' La réponse est tout à fait correcte et la plus portable. Transformation de const char * data en const uint3_t * , que ce soit via une conversion de style C, static_cast ou reinterpret_cast , enfreint les règles strictes en matière d'aliasing (voir Comprendre l'alias strict ). Si vous compilez avec une optimisation complète, il y a de bonnes chances que le code ne soit pas correct.

La diffusion via une union (telle que my_reint de litb) est probablement la meilleure solution, même si elle enfreint techniquement la règle selon laquelle vous écrivez à une union via un membre et la lisez par un autre. entraîne un comportement indéfini. Cependant, pratiquement tous les compilateurs le supportent, ce qui donne le résultat attendu. Si vous souhaitez absolument vous conformer à la norme 100%, optez pour la méthode de transfert de bits. Sinon, je vous conseillerais de choisir un syndicat, ce qui vous donnera probablement de meilleures performances.

Autres conseils

Ignorant l'efficacité, pour la simplicité du code, je le ferais:

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

J'aime aussi la dernière réponse de litb, celle qui déplace chaque caractère à tour de rôle, sauf que, puisque caractère peut être signé, je pense qu'il faut un masque supplémentaire:

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

Lorsque le typage est un problème potentiel, je préfère ne pas le taper au lieu de tenter de le faire en toute sécurité. Si vous ne créez pas de pointeurs alias de types distincts, vous n'avez pas à vous soucier de ce que le compilateur pourrait faire des alias, pas plus que le programmeur de maintenance qui voit vos multiples static_casts via une union.

Si vous ne souhaitez pas allouer autant de mémoire supplémentaire, alors:

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

Une optimisation suffisante supprimera entièrement la mémoire et la variable supplémentaire uint32_t sur gcc, et lira juste une valeur entière non alignée, quelle que soit la méthode la plus efficace utilisée sur votre plate-forme, directement à partir du tableau source. J'espère que la même chose est vraie des autres " sérieux " compilateurs. Mais ce code est maintenant plus gros que celui de litb, il n'y a donc pas grand chose à en dire, sinon le mien est plus facile à transformer en un modèle de fonction qui fonctionnera tout aussi bien avec uint64_t, et le mien fonctionne comme un endianisme natif plutôt que de choisir peu -endien.

Ceci n’est bien sûr pas complètement portable. Il suppose que la représentation de stockage de sizeof (uint32_t) correspond à la représentation de stockage de uin32_t de la manière souhaitée. Ceci est implicite dans la question, car il est dit que l'on peut être "traité comme" L'autre. Une finalité, si un caractère est de 8 bits et si uint32_t utilise tous les bits de sa représentation de stockage peut évidemment constituer une intrusion, mais la question implique qu'ils ne le feront pas.

Il y a mes cinquante cents - différentes façons de le faire.

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

Je sais que ce fil de discussion est inactif depuis un moment, mais je pensais publier une routine de casting générique simple pour ce genre de choses:

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

J'espère que ça aide quelqu'un!

Cela ressemble à un exemple de cas où l'utilisation de reinterpret_cast , tout le reste vous donnera le même effet sans la clarté que vous obtenez en utilisant une construction de langage pour son usage officiel.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top