2009-04-26 7 views
6

tengo el siguiente código C:Lenguaje C: el valor #DEFINEd estropea la multiplicación de 8 bits. ¿Por qué?

#define PRR_SCALE 255 
... 
uint8_t a = 3; 
uint8_t b = 4; 
uint8_t prr; 
prr = (PRR_SCALE * a)/b; 
printf("prr: %u\n", prr); 

Si puedo compilar este (utilizando un compilador plataforma msp430, para un pequeño sistema operativo integrado llamado contiki) el resultado es 0, mientras que esperaba 191. (uint8_t es typedef' ed como un unsigned char)

si la cambio a:

uint8_t a = 3; 
uint8_t b = 4; 
uint8_t c = 255; 
uint8_t prr; 
prr = (c * a)/b; 
printf("prr: %u\n", prr); 

funciona correctamente y grabados 191.

Al compilar una versión simple de este 'normalmente' utilizando gcc en un cuadro de Ubuntu se imprime el valor correcto en ambos casos.

No estoy exactamente seguro de por qué es esto. Podría eludirlo asignando el valor DEFINEd a una variable de antemano, pero preferiría no hacerlo.

¿Alguien sabe por qué es esto? Tal vez con un enlace a más información sobre esto?

+0

espero que ambos impriman 191. en el segundo caso, la primera cy a se promueven a int de manera independiente y por lo tanto su multiplicación no puede desbordarse. Lo mismo sucede en el primer caso (aunque allí, PRR_SCALE ya es int, pero eso no cambiará la promoción de a a int tampoco). tu gcc en tu caja se comporta exactamente bien. –

+0

compruebe que tiene el encabezado stdio.h incluido. Sé que un compilador para msp430 permite declaraciones de funciones implícitas: si ese es el caso, la llamada a printf causaría un comportamiento indefinido, y el resultado "0" se explicaría de ese modo. solo mis dos centavos. no creo que valga la pena una respuesta :) –

+0

@litb: stdio.h está incluido. – Rabarberski

Respuesta

10

La respuesta corta: el compilador tiene errores. (No hay problema con el desbordamiento, como sugirieron otros).

En ambos casos, la aritmética se realiza en int, que garantiza que tiene al menos 16 bits de longitud. En el fragmento anterior es porque 255 es un int, en este último es debido a integral promotion.

Como ha señalado, gcc maneja esto correctamente.

+0

"la aritmética se realiza en int, que se garantiza que tendrá al menos 16 bits de longitud". ¿Puede proporcionar una fuente sobre eso? Me cuestiono su validez ... – strager

+1

"la aritmética se hace en int": un resultado de promoción integral. "[int] se garantiza que tendrá al menos 16 bits de longitud": garantizado indirectamente, INT_MAX debe ser al menos 32767 e INT_MIN a lo sumo -32767 (consulte la sección sobre limits.h en el estándar). – avakar

+0

+1, eso es lo que esperaba después de la depuración un poco. ¿Puede proporcionar un enlace que especifique cómo se vuelve a convertir el valor? ¿O es solo aritmética modular? – JaredPar

2

255 se está procesando como un literal entero y hace que toda la expresión esté basada en int en lugar de estar basada en caracteres sin signo. El segundo caso obliga al tipo a ser correcto. Trate de cambiar su # define de la siguiente manera:

#define PRR_SCALE ((uint8_t) 255) 
+0

No funciona, ya lo había intentado (bueno, hice el cast directamente en la línea de cálculo. Lo reintenté como sugirió. Sin diferencia). – Rabarberski

+0

Yo ambos ejemplos de código, la aritmética se hace en int.Esto se debe a las promociones enteras, ver http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf, Sección 6.3.1.1, párrafo 2. –

1

No estoy seguro de por qué la define no funciona, pero es posible que se golpea a los vuelcos con los uint8_t variables. 255 es el valor máximo para uint8_t (2^8 - 1), por lo que si lo multiplica por 3, es probable que se encuentre con algunos problemas sutiles de reinversión.

El compilador podría estar optimizando su código y precalculando el resultado de su expresión matemática y empujando el resultado en prr (dado que cabe, aunque el valor intermedio no encaje).

comprobar lo que sucede si se rompe su expresión como esta (esto no se comportará como lo que quiere):

prr = c * a; // rollover! 
prr = prr/b; 

Es posible que necesite sólo tiene que utilizar un tipo de datos más grande.

+1

Técnicamente c * a no rebosar. El estándar C especifica que no puede ocurrir un desbordamiento para los tipos enteros sin signo. – JaredPar

+0

Genial, eso tiene sentido. Creo que "rollover" sería un término mejor. –

0

Una diferencia que se me ocurre en el caso-1 es,

El valor literal PRR_SCALE puede entrar en ROM o en el área de código. Y puede haber alguna diferencia en MUL opecode para decir,

case-1: [register], [rom] 
case -2: [register], [register] 

Puede que no tenga ningún sentido.

2

Si el compilador en cuestión es el mspgcc, debe mostrar una lista de ensamblador del programa compilado junto con el archivo binario/hexadecimal. Otros compiladores pueden requerir indicadores de compilación adicionales para hacerlo. O tal vez incluso un desensamblador separado se ejecute en el binario.

Este es el lugar donde buscar una explicación. Debido a las optimizaciones del compilador, el código real presentado al procesador podría no tener mucha similitud con el código C original (pero normalmente hace el mismo trabajo).

Recorrer las pocas instrucciones del ensamblador que representan el código defectuoso debe revelar la causa del problema.

Supongo que el compilador de alguna manera optimiza el cálculo completo ya que la constante definida es una parte conocida en tiempo de compilación. 255 * x podría optimizarse a x < < 8-x (que es más rápido y más pequeño) Tal vez algo anda mal con el código de ensamblador optimizado.

Me tomé el tiempo para compilar ambas versiones en mi sistema. Con la optimización de activos, el mspgcc produce el siguiente código:

#define PRR_SCALE 255 
uint8_t a = 3; 
uint8_t b = 4; 
uint8_t prr; 
prr = (PRR_SCALE * a)/b; 
    40ce: 3c 40 fd ff  mov #-3, r12 ;#0xfffd 
    40d2: 2a 42   mov #4, r10 ;r2 As==10 
    40d4: b0 12 fa 6f  call __divmodhi4 ;#0x6ffa 
    40d8: 0f 4c   mov r12, r15 ; 
printf("prr: %u\n", prr); 
    40da: 7f f3   and.b #-1, r15 ;r3 As==11 
    40dc: 0f 12   push r15  ; 
    40de: 30 12 c0 40  push #16576  ;#0x40c0 
    40e2: b0 12 9c 67  call printf  ;#0x679c 
    40e6: 21 52   add #4, r1 ;r2 As==10 

Como podemos ver, el compilador calcula directamente el resultado de 255 * 3 para -3 (0xfffd). Y aquí esta el problema. De alguna manera, el 255 se interpreta como -1 firmado de 8 bits en lugar de 255 sin signo de 16 bits. O se analiza primero a 8 bits y luego se extiende con signo a 16 bits. o lo que sea.

Ya se ha iniciado una discusión sobre este tema en la lista de correo mspgcc.

Cuestiones relacionadas