De C a la Asamblea

16

Supongamos que tenemos el siguiente código C para un avr-8bit:

int v1=1;
int v2=2;
v2=v2+v1;

Esperaba el siguiente desmontaje

ldi r18, 1;
ldi r19, 2;
add r19, r18;

pero después corrí:

avr-gcc -mmcu=atmega2560 Test.c -o Test.elf

y

avr-objdump -S Test.elf > Test.lss

Tengo el siguiente desmontaje

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0
    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01
    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03
    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02
    add r24, r18
    adc r25, r19
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

¿Hay alguien que pueda ayudarme a comprender el resultado del desensamblador?

Editar: Usando char, el ensamblaje se convierte en:

ldi r24, 0x01
std Y+1, r24
ldi r24, 0x02
std Y+2, r24
ldd r25, Y+2
ldd r24, Y+1
add r24, r25
std Y+2, r24

¿Cuándo hay instrucción estándar?

Café oscuro
fuente

Respuestas:

20

Respuesta corta: sus registros son de 8 bits y sus valores son de 16 bits. Por lo tanto, los maneja en dos piezas.

Respuesta larga:

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0

Almacene el valor 1 de 16 bits en registros de 8 bits r24, r25.

    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01

Guárdelo en ubicaciones de pila Y + 1, Y + 2.

    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0

Almacene el valor de 16 bits 2 en registros de 8 bits r24, r25.

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Guárdelo en ubicaciones de pila Y + 3, Y + 4.

    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02

Copie de nuevo de la pila a (r18, r19) y (r24, r25)

    add r24, r18
    adc r25, r19

Agregue (r18, r19) a (r24, r25), incluido el carry en la segunda adición

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Guárdelo de nuevo en la pila.

Para obtener su ensamblaje original, intente dos cosas:

  • utilizar variables "char"
  • use la opción del compilador "-O2"

Editar : la razón por la que el compilador almacena variables en la pila en lugar de mantenerlas en los registros es porque están almacenadas con el tipo de almacenamiento "automático" predeterminado. se puede optimizarlas en los registros pero no tiene que, incluso si se declaran en "registrar" clase de almacenamiento.

Si bien este no es un requisito estricto del lenguaje, es un comportamiento normal del compilador. Si en algún momento toma la dirección de v1, debe asignarle una ubicación de almacenamiento y guardarla allí siempre que cambie el valor de "v1". Entonces, para guardar la contabilidad de si v1 debe almacenarse o no en un registro o en la pila, lo mantiene en la pila y trata cada línea de código por separado.

pjc50
fuente
¡Gracias! ¡Ahora está más claro! Encuentra mi edición en la pregunta.
DarkCoffee
1
Mira mi edición. Prueba -O2 también. Quizás -O3, aunque eso puede producir código roto.
pjc50
3
Una gran cantidad de código incrustado con el que trabajo define tipos adicionales que son específicos de su tamaño, como "uint8, uint16, uint32" para las entradas sin firmar, por ejemplo. De esa manera, siempre sabes exactamente con qué tipo de variable estás tratando. Especialmente en pequeños incrustados, firmados, flotantes, "int" de tamaño / firma indefinido le costará ciclos de CPU en el mejor de los casos y causará errores graves en el peor.
John U
Los compiladores reales dejaron de comportarse así hace unos 10-15 años. El problema de asignación de registros se resuelve principalmente y los compiladores son muy buenos en eso. Saben exactamente cuándo una variable debe estar en la pila y cuándo puede estar en un registro, si vale la pena moverla y cuándo hacerlo. La contabilidad se realiza en tiempo de compilación, y los compiladores tienen gigabytes de memoria. La gran excepción es el modo de depuración, por razones obvias, pero luego todo está en la pila.
MSalters
@ pjc50 -O3puede producir código roto? [cita requerida] (y no, el código C que invoca Comportamiento indefinido y luego rompe con alguna configuración de optimización no cuenta)
marcelm
4

Cuando encontré algún código de ejemplo, responderé a mi comentario; otros ya han explicado el problema.

Una gran cantidad de código incrustado con el que trabajo define tipos adicionales que son específicos de su tamaño, como "uint8, uint16, uint32" para las entradas sin firmar, por ejemplo. De esa manera, siempre sabes exactamente con qué tipo de variable estás tratando. Especialmente en pequeño incrustado, firmado, flotante, "int" de tamaño / firma indefinido le costará ciclos de CPU en el mejor de los casos y causará errores graves en el peor.

Aquí están nuestros #defines actuales:

/*
 * Example - the basic data types from our embedded code
 */
typedef unsigned char       uint8;  /*  8 bits */
typedef unsigned short int  uint16; /* 16 bits */
typedef unsigned long int   uint32; /* 32 bits */

typedef char                int8;   /*  8 bits */
typedef short int           int16;  /* 16 bits */
typedef int                 int32;  /* 32 bits */

typedef volatile int8       vint8;  /*  8 bits */
typedef volatile int16      vint16; /* 16 bits */
typedef volatile int32      vint32; /* 32 bits */

typedef volatile uint8      vuint8;  /*  8 bits */
typedef volatile uint16     vuint16; /* 16 bits */
typedef volatile uint32     vuint32; /* 32 bits */
John U
fuente
3
Buena idea; uint8_t y sus amigos ahora son parte del estándar: stackoverflow.com/questions/16937459/…
pjc50
¡Qué práctico! Los heredamos un poco con un proyecto que era C89, por lo que es bueno saber que hay una versión oficial.
John U
2

Su código C utiliza variables enteras de 16 bits (int). El compilador no puede leer su mente, por lo que compila exactamente lo que está en el archivo fuente. Entonces, si desea variables de 8 bits, debe usar el tipo respectivo.

Como resultado, aún podrá almacenar los valores en la memoria (aunque más simple). No soy tan bueno en C, pero en mi humilde opinión, hay algunas opciones para asignar la variable a algún registro, si desea que algunas variables estén en los registros en lugar de RAM. Algo como:

register unsigned char VARNAME asm("r3");

Tenga en cuenta que no todos los registros están disponibles para tales trucos.

Entonces, la conclusión? Escribe tus programas en asamblea. Siempre serán más pequeños, más rápidos y fáciles de leer / soportar.

johnfound
fuente
El ensamblaje es más fácil de leer que C?
dext0rb
@ dext0rb: sí. Por supuesto, si conoces ambos lo suficientemente bien. Si solo conoce C, entonces el ensamblaje y cualquier otro lenguaje serán difíciles de leer.
johnfound
Tengo que estar en desacuerdo con el último punto, los programas escritos en ensamblador son mucho más difíciles de leer. Simplemente compare el código fuente dado anteriormente. El código C es mucho más claro y corto, así como su intención. Esta diferencia solo crece a medida que se usan estructuras.
soandos
@soandos - El código C es más corto, sí. Más claro? No estoy seguro. Si fuera así, la pregunta anterior no tendría que hacerse en absoluto. En realidad, el precio de la "escasez" es el "desenfoque" de los detalles.
johnfound
Por supuesto, el tipo que dice "No soy tan bueno en C" proclamará las virtudes de la asamblea pura. : D
dext0rb