Promoción de enteros C en MCU de 8 bits

14

Usando avr-gcc como ejemplo, los tipos int se especifican para tener 16 bits de ancho. La realización de operaciones en operandos de 8 bits en C da como resultado que esos operandos se conviertan en tipos int de 16 bits debido a la promoción de enteros en C. ¿Esto significa que todas las operaciones aritméticas de 8 bits en un AVR tomarán mucho más tiempo si se escriben en C que si está escrito en asamblea debido a la promoción de enteros de C?

pr871
fuente
1
No lo creo, el compilador se dará cuenta de que la variable de destino es un carácter (sin signo), por lo tanto, no se molestará en calcular los 8 bits superiores. Aún así, descubrí que GCC a veces no es tan bueno para optimizar el código, por lo tanto, si codifica en ASM, el resultado MGIHT será más rápido. Sin embargo, a menos que esté haciendo tareas / interrupciones muy críticas de tiempo, con una restricción presupuestaria muy fuerte, entonces debe elegir un procesador más potente y programarlo en C, o simplemente no preocuparse por el bajo rendimiento (considere el tiempo -al mercado, mejor legibilidad / reutilización de código, menos errores, ecc ..).
próximo truco el
Pido disculpas por no tener tiempo para verificar. Sin embargo, creo que había un indicador de línea de comando para gcc que controlaría la 'promoción de enteros'. Puede haber incluso un pragma para controlarlo para piezas específicas de código. ¿Qué tan crítico es el rendimiento? En muchos usos de un AVR, la diferencia de velocidad para algunos aritméticos no es un problema. Foxus en hacer que el código funcione correctamente primero. Luego, si hay un problema de rendimiento, averigüe qué es. Sería fácil perder el tiempo codificando en ensamblador, solo que encontrará que no importó.
gbulmer
1
simplemente desmonte y vea lo que está haciendo el compilador. Desde una perspectiva de lenguaje puro, sí. La implementación aquí es atípica. normalmente int intenta alinearse con el tamaño del registro, y si tenía registros de 16 bits, la matemática de 8 bits es más barata en 16 bits que en 8. Pero esto es al revés y con un mcu de 8 bits tiene sentido implementar int como 16 bit. así que probablemente deberías usar uchar cuando te importe esto, pero no hagas que sea un hábito de programación común, ya que te duele más en cualquier otro lugar.
old_timer
3
Recuerde: evite responder preguntas en los comentarios.
tubería
44
Es mejor hacer este tipo de preguntas a los expertos de C en SO, ya que es una pura pregunta de software. La promoción de enteros en C es un tema algo complejo: el programador promedio de C tendrá muchos conceptos erróneos al respecto.
Lundin

Respuestas:

16

Larga historia corta:

La promoción de enteros a 16 bits siempre tiene lugar; el estándar C lo impone. Pero el compilador puede optimizar el cálculo de nuevo a 8 bits (los compiladores de sistemas integrados suelen ser bastante buenos en tales optimizaciones), si puede deducir que el signo será el mismo que hubiera sido si el tipo hubiera sido promovido.

¡Este no es siempre el caso! Los cambios de firma implícitos causados ​​por la promoción de enteros son una fuente común de errores en los sistemas integrados.

Puede encontrar una explicación detallada aquí: Reglas de promoción de tipo implícito .

Lundin
fuente
8
unsigned int fun1 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned char fun2 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned int fun3 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

unsigned char fun4 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

como se esperaba fun1 es todo ints también lo hace la matemática de 16 bits

00000000 <fun1>:
   0:   86 0f           add r24, r22
   2:   97 1f           adc r25, r23
   4:   08 95           ret

Aunque técnicamente es incorrecto, ya que es una adición de 16 bits invocada por el código, incluso sin optimizar este compilador eliminó el adc debido al tamaño del resultado.

00000006 <fun2>:
   6:   86 0f           add r24, r22
   8:   08 95           ret

No estoy realmente sorprendido de que ocurra la promoción, los compiladores no solían hacer esto, no estoy seguro de qué versión hizo que esto comenzara a ocurrir, me encontré con esto al principio de mi carrera y, a pesar de que los compiladores promocionan fuera de servicio (al igual que arriba), realicé la promoción a pesar de que yo Le dije que hiciera matemáticas uchar, no sorprendido.

0000000a <fun3>:
   a:   70 e0           ldi r23, 0x00   ; 0
   c:   26 2f           mov r18, r22
   e:   37 2f           mov r19, r23
  10:   28 0f           add r18, r24
  12:   31 1d           adc r19, r1
  14:   82 2f           mov r24, r18
  16:   93 2f           mov r25, r19
  18:   08 95           ret

y lo ideal, sé que es de 8 bits, quiero un resultado de 8 bits, así que simplemente le dije que hiciera 8 bits hasta el final.

0000001a <fun4>:
  1a:   86 0f           add r24, r22
  1c:   08 95           ret

Por lo tanto, en general, es mejor apuntar al tamaño del registro, que es idealmente el tamaño de un (u) int, para un mcu de 8 bits como este, los autores del compilador tuvieron que comprometerse ... El punto es no tener el hábito de el uso de uchar para matemáticas que usted sabe no necesita más de 8 bits, como cuando mueve ese código o escribe un código nuevo como ese en un procesador con registros más grandes, ahora el compilador tiene que comenzar a enmascarar y extender signos, lo que algunos hacen de forma nativa en algunas instrucciones, y otros no.

00000000 <fun1>:
   0:   e0800001    add r0, r0, r1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e0800001    add r0, r0, r1
   c:   e20000ff    and r0, r0, #255    ; 0xff
  10:   e12fff1e    bx  lr

Forzar 8 bit cuesta más. Hice trampa un poco / mucho, necesitaría ejemplos un poco más complicados para ver más de esto de una manera justa.

EDITAR basado en comentarios discusión

unsigned int fun ( unsigned char a, unsigned char b )
{
    unsigned int c;
    c = (a<<8)|b;
    return(c);
}

00000000 <fun>:
   0:   70 e0           ldi r23, 0x00   ; 0
   2:   26 2f           mov r18, r22
   4:   37 2f           mov r19, r23
   6:   38 2b           or  r19, r24
   8:   82 2f           mov r24, r18
   a:   93 2f           mov r25, r19
   c:   08 95           ret

00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

No sorpresa. Aunque, ¿por qué el optimizador dejó esa instrucción extra, no puedes usar ldi en r19? (Sabía la respuesta cuando la pregunté).

EDIT2

para avr

avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

para evitar el mal hábito o no la comparación de 8 bits

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

claramente la optimización estaba activada solo toma un segundo para probar con su propio compilador para ver cómo se compara con mi salida, pero de todos modos:

whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o

Y sí, usar bytes para variables de tamaño de byte, ciertamente en un avr, pic, etc., le ahorrará memoria y realmente desea tratar de conservarlo ... si realmente lo está usando, pero como se muestra aquí, lo menos posible es va a estar en la memoria, tanto como sea posible en los registros, por lo que el ahorro flash se produce al no tener variables adicionales, el ahorro de memoria RAM puede o no ser real.

viejo contador de tiempo
fuente
2
"los compiladores no solían hacer esto sin estar seguros de qué versión hizo que esto comenzara a ocurrir, me encontré con esto al principio de mi carrera y, a pesar de que los compiladores promocionaban fuera de servicio (al igual que antes), hicieron la promoción a pesar de que le dije que hiciera cálculos matemáticos, no sorprendido." Se debe a que los compiladores de sistemas embebidos C solía tener la conformidad estándar terrible :) El compilador se permite por lo general optimizar, pero aquí no se puede deducir que el resultado se ajusta en un unsigned charmodo que tiene para llevar a cabo la promoción de 16 bits, según sea necesario por el estándar
Lundin
1
@old_timer (a<<8)|bsiempre es incorrecto para cualquier sistema con int16 bits. aserá promovido implícitamente a lo intque está firmado. En caso de tener aun valor en el MSB, terminas cambiando esos datos al bit de signo de un número de 16 bits, lo que invoca un comportamiento indefinido.
Lundin
1
fun3 is fun..ny ... totalmente no optimizado por el compilador ... Consideró que r1 siempre es 0 en GCC, e indicando ra, rb, {rh, rl} los registros para las variables a, b, y el resultado, el compilador podría haber hecho: 1) mov rh, r1; 2) mov rl, ra; 2) agregar rl, rb; 3) adc rh, rh; 4) ret. 4 Instrucciones, vs 7 u 8 ... La instrucción 1 se puede cambiar en ldi rh, 0.
Next-hack
1
Esta sería una mejor respuesta si especificara el compilador y las opciones relevantes en uso.
Russell Borogove
1
Es una buena idea evitar el uso de int / char, etc. y, en su lugar, usar los int16_t y int8_t mucho más explícitos y legibles.
usuario
7

No necesariamente, ya que los compiladores modernos hacen un buen trabajo al optimizar el código generado. Por ejemplo, si escribe z = x + y;dónde están todas las variables unsigned char, se requiere que el compilador las promueva unsigned intantes de realizar los cálculos. Sin embargo, dado que el resultado final será exactamente el mismo sin la promoción, el compilador generará código que solo agrega variables de 8 bits.

Por supuesto, este no es siempre el caso, por ejemplo, el resultado z = (x + y)/2;dependería del byte superior, por lo que la promoción tendrá lugar. Todavía se puede evitar sin recurrir al ensamblaje volviendo a arrojar el resultado intermedio unsigned char.

Algunas de estas ineficiencias pueden evitarse utilizando las opciones del compilador. Por ejemplo, muchos compiladores de 8 bits tienen un pragma o un conmutador de línea de comandos para ajustar los tipos de enumeración en 1 byte, en lugar de intlo requerido por C.

Dmitry Grigoryev
fuente
44
"se requiere que el compilador los promocione a unsigned int". No, se requiere que el compilador los promocione int, ya charque probablemente no tendrá el mismo rango de conversión que inten cualquier plataforma.
Lundin
3
"Por ejemplo, muchos compiladores de 8 bits tienen un pragma o un conmutador de línea de comandos para ajustar los tipos de enumeración en 1 byte, en lugar de int como lo requiere C." El estándar C permite que las variables de enumeración se asignen en 1 byte. Solo requiere que las constantes de enumeración sean int(sí, es inconsistente). C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...
Lundin