c++ - tipos - leer cadena de caracteres en c
Opiniones sobre el tipo-juego de palabras en C++? (5)
Tengo curiosidad acerca de las convenciones para los punteros tipo-punning / matrices en C ++. Aquí está el caso de uso que tengo en este momento:
Calcule una suma de comprobación simple de 32 bits sobre una masa binaria 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 resuma todos los valores e ignore el desbordamiento.
Esperaría que una función así 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 la "mejor" forma de convertir data
a udata
?
C-estilo de lanzamiento?
udata = (const uint32_t *)data
C ++ elenco que supone que todos los punteros son convertibles?
udata = reinterpret_cast<const uint32_t *>(data)
¿C ++ echó eso entre tipos de punteros arbitrarios usando el void*
intermedio void*
?
udata = static_cast<const uint32_t *>(static_cast<const void *>(data))
¿Lanzado 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 (es decir, accesos a la memoria no alineados y suposiciones del compilador sobre el alias del puntero). ¿Qué recomendarías?
Esto parece un ejemplo de libro de casos de cuándo usar reinterpret_cast
, cualquier otra cosa le dará el mismo efecto sin la explicitud que obtiene de usar una construcción de lenguaje para su uso oficial.
Ignorando la eficiencia, por la simplicidad del 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 char a su vez, excepto que dado que Char puede estar firmado, creo que necesita una máscara adicional:
checksum += ((data[i] && 0xFF) << shift[i % 4]);
Cuando el tipo de juego de palabras es un problema potencial, prefiero no escribir juegos de palabras en lugar de intentar hacerlo de manera segura. Si no crea ningún puntero alias de distintos tipos, 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;
}
Suficiente optimización eliminará por completo la memcpy y la variable uint32_t adicional en gcc, y simplemente leerá un valor entero sin alinear, de la forma más eficiente posible en su plataforma, directamente desde la matriz de origen. Espero que lo mismo sea cierto para otros compiladores "serios". Pero este código ahora es más grande que el de litb, así que no hay mucho que decir aparte del mío es más fácil convertirlo en una plantilla de función que funcionará igual de bien con uint64_t, y el mío funciona como endianidad nativa en lugar de elegir poco -Endian.
Esto, por supuesto, no es completamente portátil. Supone que la representación de almacenamiento de sizeof (uint32_t) chars corresponde a la representación de almacenamiento de 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 char es de 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.
Sé que este hilo ha estado inactivo por un tiempo, pero pensé que publicaría una rutina de lanzamiento 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!
Hay mis cincuenta centavos, diferentes formas 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";
}
En lo que respecta al estándar C ++, la respuesta de litb es completamente correcta y la más portátil. Al const uint3_t *
const char *data
en un const uint3_t *
, ya sea a través de un molde de estilo C, static_cast
o reinterpret_cast
, se rompen las reglas de aliasing estrictas (consulte Descripción del Alias estricto ). Si compila con optimización completa, hay una buena probabilidad de que el código no sea el correcto.
Casting a través de una unión (como my_reint de my_reint
) es probablemente la mejor solución, aunque infringe técnicamente la regla de que escribir en un sindicato a través de un miembro y leerlo a través de otro genera un comportamiento indefinido. Sin embargo, prácticamente todos los compiladores lo soportan y da como resultado el esperado. Si desea cumplir estrictamente con el estándar 100%, vaya con el método de cambio de bit. De lo contrario, recomiendo ir a través de un sindicato, lo que probablemente te proporcione un mejor rendimiento.