2010-08-16 29 views
7

Dado el siguiente fragmento:largo largo vs int multiplicación

#include <stdio.h> 

typedef signed long long int64; 
typedef signed int int32; 
typedef signed char int8; 

int main() 
{ 
    printf("%i\n", sizeof(int8)); 
    printf("%i\n", sizeof(int32)); 
    printf("%i\n", sizeof(int64)); 

    int8 a = 100; 
    int8 b = 100; 
    int32 c = a * b; 
    printf("%i\n", c); 

    int32 d = 1000000000; 
    int32 e = 1000000000; 
    int64 f = d * e; 
    printf("%I64d\n", f); 
} 

La salida con MinGW GCC 3.4.5 es (-O0):

1 
4 
8 
10000 
-1486618624 

La primera multiplicación es fundido a una int32 internamente (según la salida del ensamblador). La segunda multiplicación no se realiza. No estoy seguro de si los resultados difieren porque el programa se estaba ejecutando en un IA32 o porque está definido en algún lugar del estándar C. Sin embargo, estoy interesado si este comportamiento exacto se define en algún lugar (ISO/IEC 9899?), Porque me gusta entender mejor por qué y cuándo tengo que lanzar manualmente (tengo problemas para portar un programa desde una arquitectura diferente).

Respuesta

7

El estándar C99 especifica que los operadores binarios como * no funcionan los tipos enteros más pequeños que int. Las expresiones de estos tipos se promueven a int antes de aplicar el operador. Ver 6.3.1.4 párrafo 2 y las numerosas apariciones de las palabras "promoción entera". Pero esto es algo ortogonal a las instrucciones de ensamblaje generadas por el compilador, que operan en int s porque esto es más rápido incluso cuando el compilador podría calcular un resultado más corto (porque el resultado se almacena inmediatamente en un valor l de un corto tipo, por ejemplo).

En cuanto int64 f = d * e; donde d y e son de tipo int, la multiplicación se lleva a cabo como un int de acuerdo con las mismas reglas de la promoción. El desbordamiento es técnicamente comportamiento indefinido, aquí obtiene el resultado de dos complementos, pero puede obtener cualquier cosa de acuerdo con la norma.

Nota: las reglas de promoción distinguen entre los tipos con y sin firma al promocionar. La regla es promocionar tipos más pequeños a int a menos que int no pueda representar todos los valores del tipo, en cuyo caso se usa unsigned int.

5

El problema es que la multiplicación es int32 * int32, que se realiza como int32, y el resultado se asigna a un int64. Obtendrá casi el mismo efecto con double d = 3/2;, que se dividiría 3 por 2 con división de enteros, y asignaría 1.0 a d.

Debe prestar atención al tipo de expresión o subexpresión siempre que sea importante. Esto requiere asegurarse de que la operación apropiada se calcule como el tipo apropiado, como convertir uno de los multiplicandos a int64 o (en mi ejemplo) 3.0/2 o (float)3/2.

+1

Muy bien puesto. @azraiyl debería cambiar esa línea a 'int64 f = (int64) d * e;' –

+0

Lamento que no haya declarado que conozco la solución aquí. Lo que me interesa es por qué la multiplicación es int32 * int32 en el primer caso y no int8 * int8. Incluso si la CPU solo admite la multiplicación int32, puede volver a conectarse a un int8 después de la multiplicación. Pero la instrucción imul IA32 funciona para los registros de 8 bits (AL, ...). – azraiyl

+0

@azrayl: Al menos en C90, C promovió todos los operandos aritméticos a 'int' si eran de tipos más pequeños. Un vistazo al estándar C99 sugiere que este ya no es el caso, pero no estoy seguro. ¿Qué compilador de C está utilizando y, si corresponde, con qué opciones? –

2

a * b se calcula como un int, y luego convertir al tipo de variable de recepción (que sólo pasa a ser int)

d * e se calcula como un int, y luego fundido para el tipo de variable de recepción (que sólo pasa int64)

Si cualquiera de las variables de tipo fuera más grande que un int (o sea punto flotante), entonces ese tipo se habría utilizado. Pero dado que todos los tipos utilizados en las multiplicaciones eran int o menores, se usaron ints.

2

Lea K & R (el original). Todas las operaciones de enteros se realizan con el tipo de entero natural a menos que involucre variables que están (o están fundidas) en algo más grande. Las operaciones en char se convierten a 32 bits porque ese es el tamaño natural del número entero en esa arquitectura. La multiplicación de los dos enteros de 32 bits se realiza en 32 bits porque nada lo convierte en algo más grande (hasta que lo asigne a la variable de 64 bits, pero ya es demasiado tarde). Si desea que la operación ocurra en 64 bits, envíe una o ambas entradas a 64 bits.

int64 f = (int64)d * e;