Pregunta

Tengo curiosidad acerca de las convenciones para punteros / matrices de escritura en C ++. Aquí está el caso de uso que tengo en este momento:

Calcule una suma de verificación simple de 32 bits sobre un blob binario de datos tratándola como una matriz de enteros de 32 bits (sabemos que su longitud total es un múltiplo de 4), y luego sumando todos los valores e ignorando el desbordamiento.

Esperaría que tal función se vea así:

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

Ahora la pregunta que tengo es, ¿cuál consideras que es el mejor? forma de convertir datos a udata ?

¿Elenco de estilo C?

udata = (const uint32_t *)data

¿C ++ cast que asume que todos los punteros son convertibles?

udata = reinterpret_cast<const uint32_t *>(data)

C ++ convierte eso entre tipos de punteros arbitrarios usando void * ?

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

¿Transmitir a través de una unión?

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

Me doy cuenta de que esta no será una solución 100% portátil, pero solo espero usarla en un pequeño conjunto de plataformas donde sé que funciona (a saber, accesos de memoria no alineados y suposiciones del compilador sobre alias de puntero). ¿Qué recomendarías?

¿Fue útil?

Solución

En lo que respecta al estándar C ++, litb ' La respuesta es completamente correcta y la más portátil. Transmitir const char * data a un const uint3_t * , ya sea mediante una conversión de estilo C, static_cast o reinterpret_cast , rompe las estrictas reglas de alias (consulte Comprensión del alias estricto ). Si compila con optimización completa, existe una buena posibilidad de que el código no funcione correctamente.

Transmitir a través de una unión (como my_reint de litb) es probablemente la mejor solución, aunque técnicamente viola la regla de que si escribe a una unión a través de un miembro y lo lee a través de otro, da como resultado un comportamiento indefinido. Sin embargo, prácticamente todos los compiladores lo admiten, y da como resultado el resultado esperado. Si desea cumplir con el estándar 100%, vaya con el método de cambio de bits. De lo contrario, recomendaría pasar a través de una unión, lo que probablemente le brindará un mejor rendimiento.

Otros consejos

Ignorando la eficiencia, por simplicidad de código que haría:

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

También me gusta la última respuesta de litb, la que cambia cada carácter por turno, excepto que dado que el carácter podría estar firmado, creo que necesita una máscara adicional:

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

Cuando el juego de palabras es un problema potencial, prefiero no escribir juegos de palabras en lugar de intentar hacerlo de forma segura. Si no crea ningún puntero con alias de distintos tipos en primer lugar, entonces no tiene que preocuparse de lo que el compilador podría hacer con los alias, y tampoco lo hace el programador de mantenimiento que ve sus múltiples static_casts a través de una unión.

Si no desea asignar tanta memoria extra, entonces:

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

Una optimización suficiente eliminará la memoria y la variable extra uint32_t por completo en gcc, y solo leerá un valor entero sin alinear, sea cual sea la forma más eficiente de hacerlo en su plataforma, directamente desde la matriz fuente. Espero que lo mismo sea cierto para otros '' serios '' compiladores Pero este código ahora es más grande que el de Litb, por lo que no hay mucho que decir, ya que el mío es más fácil de convertir en una plantilla de función que funcionará igual de bien con uint64_t, y el mío funciona como endian-ness nativo en lugar de elegir poco -endian.

Por supuesto, esto no es completamente portátil. Se supone que la representación de almacenamiento de los caracteres sizeof (uint32_t) corresponde a la representación de almacenamiento de un uin32_t de la manera que queremos. Esto está implícito en la pregunta, ya que establece que uno puede ser "tratado como" el otro. Endian-ness, si un carácter tiene 8 bits y si uint32_t usa todos los bits en su representación de almacenamiento, obviamente puede entrometerse, pero la pregunta implica que no lo harán.

Ahí están mis cincuenta centavos, diferentes maneras de hacerlo.

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

Sé que este hilo ha estado inactivo por un tiempo, pero pensé en publicar una rutina de conversión genérica simple para este tipo de cosas:

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

¡Espero que ayude a alguien!

Esto parece un ejemplo de libro de casos de cuándo usar reinterpret_cast , cualquier otra cosa le dará el mismo efecto sin la explicidad que obtiene al usar una construcción de lenguaje para su uso oficial.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top