¿32 bits sin firmar se multiplican en 64 bits causando un comportamiento indefinido?
undefined-behavior (2)
Felicitaciones por encontrar un punto de fricción.
Una forma posible:
v = (uint32_t) (UINT_MAX<=0xffffffff
? s1 * s2
: (unsigned)s1 * (unsigned)s2);
De todos modos, parece que agregar algunos typedefs a <stdint.h>
para los tipos garantizados de no ser más pequeños de lo que int
estaría en orden ;-).
Así que tengo sobre este código:
uint32_t s1 = 0xFFFFFFFFU;
uint32_t s2 = 0xFFFFFFFFU;
uint32_t v;
...
v = s1 * s2; /* Only need the low 32 bits of the result */
En todos los siguientes, asumo que el compilador no podría tener ninguna idea preconcebida en el rango de s1
o s2
, los inicializadores solo sirven para un ejemplo de arriba.
Si compilé esto en un compilador con un tamaño entero de 32 bits (como cuando compilamos para x86), no hay problema. El compilador simplemente usaría s1
y s2
como valores escritos en uint32_t
(no pudiendo promoverlos más), y la multiplicación simplemente daría el resultado como dice el comentario (módulo UINT_MAX + 1
que es 0x100000000 en este caso).
Sin embargo, si compilé esto en un compilador con un tamaño entero de 64 bits (como para x86-64), podría haber un comportamiento indefinido de lo que puedo deducir del C standard. La promoción de enteros vería que uint32_t
puede ser promovido a int
(64 bit firmado), la multiplicación intentaría multiplicar dos int
, que, si tienen los valores que se muestran en el ejemplo, causarán un desbordamiento de enteros, que es comportamiento indefinido.
¿Estoy en lo correcto con esto y, si es así, cómo lo evitarías de una manera sana?
Descubrí esta pregunta que es similar, pero cubre C ++: ¿Cuál es la mejor manera de C ++ de multiplicar los enteros sin signo de forma modular? . Aquí me gustaría obtener una respuesta aplicable a C (preferiblemente compatible con C89). No consideraría hacer que una máquina pobre de 32 bits ejecute potencialmente una multiplicación de 64 bits una respuesta aceptable (por lo general, en el código en el que sería preocupante, el rendimiento de 32 bits puede ser más crítico, ya que normalmente esas son las máquinas más lentas).
Tenga en cuenta que el mismo problema puede aplicarse a entradas sin signo de 16 bits cuando se compila con un compilador que tiene un tamaño int de 32 bits, o caracteres sin firmar cuando se compila con un compilador que tiene un tamaño int de 16 bits (este último puede ser común con compiladores para CPU de 8 bits : el estándar C requiere que los enteros sean al menos de 16 bits, por lo que es probable que un compilador conforme se vea afectado).
La forma más sencilla de lograr que la multiplicación se uint32_t
en un tipo sin signo que sea al menos uint32_t
, y también al menos unsigned int
, consiste en incluir una expresión de tipo unsigned int
.
v = 1U * s1 * s2;
Esto convierte 1U
a uint32_t
, o s1
y s2
a unsigned int
, dependiendo de lo que sea apropiado para su plataforma en particular.
@Deduplicator comenta que algunos compiladores, donde uint32_t
es más angosto que unsigned int
, pueden advertir sobre la conversión implícita en la asignación, y observa que es probable que dichas advertencias puedan suprimirse haciendo que la conversión sea explícita:
v = (uint32_t) (1U * s1 * S2);
Aunque parece un poco menos elegante, en mi opinión.