Ponteiros em 7 arquiteturas – de z80 a ARM

Eu admito que somente entendi ponteiros quando fiz a ponte entre código em C e Assembly. Ponteiros são complicados mesmo. É um disfarce de alto nível para o baixo nível da máquina. Ponteiros devem ser usados com cuidado em muitos aspectos; como no problema de alocação dinâmica e liberação da memória alocada, múltiplas instâncias do mesmo endereço, problemas de endianess, acessos não autorizados ou em áreas indesejadas …

Por curiosidade gerei o código assembly  do código em C abaixo para 7 processadores ou microcontroladores sendo eles: HCS08, STM8, Z80, X86-64,  ARM, ColdFire e AVR.

 int *a,*b;
 char *c;
 int i = 10;

void main(void)
 {

a = &i;
 b = a;
 *b = *b + 5;
 c = (char *) &i;
 *c = *c + 5;
 c = c + 5;

while (1);

}

E vamos aos códigos. É bom lembrar que diferenças irão ocorrer não somente pela arquitetura mas pelos compiladores usados. Um exemplo é o SDCC que para somar 5 não utilizou instrução de soma mas cinco incrementos para o Z80, provavelmente pela contagem de ciclos.

ColdFire – O mais limpo dos códigos

_main:
; main:
lea _i(a5),a0
move.l a0,_a(a5)
;
; 13: b = a;
;
move.l _a(a5),d0
move.l d0,_b(a5)
;
; 14: *b = *b + 5;
;
movea.l d0,a0
addq.l #5,(a0)
;
; 15: c = (char *) &i;
;
lea _i(a5),a0
move.l a0,_c(a5)
;
; 16: *c = *c + 5;
;
movea.l _c(a5),a0
mvs.b (a0),d0
addq.l #5,d0
move.b d0,(a0)
;
; 17: c = c + 5;
; 18:
; 19: while (1);
;
addq.l #5,_c(a5)
;
; 19: 1);
;
bra.l *+0</pre>

Z80 – Se aproveita de seus registradores de 16 Bits e acesso indireto

_main::
 61 ;ponteiros.c:9: a = &i;
ld de,#_i+0
ld (_a),de
 64 ;ponteiros.c:10: b = a;
ld hl,(_a)
ld (_b),hl
 67 ;ponteiros.c:11: *b = *b + 5;
ld hl,(_b)
push hl
ld c,(hl)
inc hl
ld b,(hl)
pop hl
inc bc
inc bc
inc bc
inc bc
inc bc
ld (hl),c
inc hl
ld (hl),b
 82 ;ponteiros.c:12: c = (char *) &i;
ld (_c),de
 84 ;ponteiros.c:13: *c = *c + 5;
ld hl,(_c)
ld a,(hl)
add a, #0x05
ld (hl),a
 89 ;ponteiros.c:14: c = c + 5;
ld hl,#_c
ld a,(hl)
add a, #0x05
ld (hl),a
inc hl
ld a,(hl)
adc a, #0x00
ld (hl),a
 98 ;ponteiros.c:16: while (1);
00102$:
jr 00102$

STM8 – O registrador x de 16 bits e as instruções de carga 16 bits ajudam muito


1 ; C Compiler for STM8 (COSMIC Software)
 2 ; Parser V4.10.2 - 02 Nov 2011
 3 ; Generator (Limited) V4.3.7 - 29 Nov 2011
 15 bsct
 16 0000 _i:
 17 0000 000a dc.w 10
 50 ; 7 main()
 50 ; 8 {
 52 switch .text
 53 0000 _main:
 57 ; 10 a = &i;
 59 ldw x,#_i
 60 ldw _a,x
 61 ; 11 b = a;
 63 ldw x,#_i
 64 ldw _b,x
 65 ; 12 *b = *b + 5;
 67 ldw x,_i
 68 addw x,#5
 69 ldw _i,x
 70 ; 13 c = (char *) &i;
 72 ldw x,#_i
 73 ldw _c,x
 74 ; 14 *c = *c + 5;
 76 ld a,_i
 77 add a,#5
 78 ld _i,a
 79 ; 15 c = c + 5;
 81 ldw x,#_i+5
 82 ldw _c,x
 83 L12:
 84 ; 17 while (1);
 86 jra L12
 140 xdef _main
 141 xdef _i
 142 switch .ubsct
 143 0000 _c:
 144 0000 0000 ds.b 2
 145 xdef _c
 146 0002 _b:
 147 0002 0000 ds.b 2
 148 xdef _b
 149 0004 _a:
 150 0004 0000 ds.b 2
 151 xdef _a
 171 end

HCS08 – Surpreso? Esse processador só tem um registrador index e um acumulador!


11: 
 12: a = &i;
 LDHX @i
 STHX a
 13: b = a;
 STHX b
 14: *b = *b + 5;
 LDHX ,X
 AIX #5
 TXA 
 PSHH 
 LDHX @i
 STA 1,X
 PULA 
 STA ,X
 15: c = (char *) &i;
 16: *c = *c + 5;
 ADD #5
 STA ,X
 17: c = c + 5;
 AIX #5
 STHX c
 L1E:
 19: while (1);
 BRA L1E
 21: }

X86-64 –

{
 push %rbp
 mov %rsp,%rbp

 a = &i;
 movq $0x0,0x0(%rip)  
 b = a;
 mov 0x0(%rip),%rax 
 mov %rax,0x0(%rip) 
 *b = *b + 5;
 mov 0x0(%rip),%rax 
  mov 0x0(%rip),%rdx 
 mov (%rdx),%edx
 add $0x5,%edx
 mov %edx,(%rax)
 c = (char *) &i;
 movq $0x0,0x0(%rip) 

 *c = *c + 5;
 mov 0x0(%rip),%rax 
 mov 0x0(%rip),%rdx 
 movzbl (%rdx),%edx
 add $0x5,%edx
 mov %dl,(%rax)
 c = c + 5;
 mov 0x0(%rip),%rax 
 add $0x5,%rax
 mov %rax,0x0(%rip) 

 while (1);
 jmp 65

ARM – É um RISC …


push {fp} ; (str fp, [sp, #-4]!)
add fp, sp, #0 
//a = &i;
ldr r3, [pc, #116] 
ldr r2, [pc, #116] 
str r2, [r3]
//b = a;
ldr r3, [pc, #104] 
ldr r3, [r3]
ldr r2, [pc, #104] 
str r3, [r2]
//*b = *b + 5;
ldr r3, [pc, #96] 
ldr r3, [r3]
ldr r2, [pc, #88] 
ldr r2, [r2]
ldr r2, [r2]
add r2, r2, #5
str r2, [r3]
//c = (char *) &i;
ldr r3, [pc, #72] 
ldr r2, [pc, #60] 
str r2, [r3]
//*c = *c + 5;
ldr r3, [pc, #60] 
ldr r3, [r3]
ldr r2, [pc, #52] 
ldr r2, [r2]
ldrb r2, [r2]
add r2, r2, #5
and r2, r2, #255 
strb r2, [r3]
//c = c + 5;
ldr r3, [pc, #28] 
ldr r3, [r3]
add r3, r3, #5
ldr r2, [pc, #16] 
str r3, [r2] 
//while (1);
b 80 

AVR – Infelizmente o processamento ficou todo em registradores


push r28
push r29
in r28, 0x3d ; 61
in r29, 0x3e ; 62 
// a = &i;
ldi r24, 0x00 ; 0
ldi r25, 0x00 ; 0
sts 0x0000, r25
sts 0x0000, r24
// b = a;
lds r24, 0x0000
lds r25, 0x0000
sts 0x0000, r25
sts 0x0000, r24
// *b = *b + 5;
lds r24, 0x0000
lds r25, 0x0000
lds r18, 0x0000
lds r19, 0x0000
mov r30, r18
mov r31, r19
ld r18, Z
ldd r19, Z+1 ; 0x01
subi r18, 0xFB ; 251
sbci r19, 0xFF ; 255
mov r30, r24
mov r31, r25
std Z+1, r19 ; 0x01
st Z, r18
// c = (char *) &i;
ldi r24, 0x00 ; 0
ldi r25, 0x00 ; 0
sts 0x0000, r25
sts 0x0000, r24
// *c = *c + 5;
lds r24, 0x0000
lds r25, 0x0000
lds r18, 0x0000
lds r19, 0x0000
mov r30, r18
mov r31, r19
ld r18, Z
subi r18, 0xFB ; 251
mov r30, r24
mov r31, r25
st Z, r18
// c = c + 5;
lds r24, 0x0000
lds r25, 0x0000
adiw r24, 0x05 ; 5
sts 0x0000, r25
sts 0x0000, r24

// while (1);
rjmp .+0

Afinal o que é um ponteiro? É simplesmente uma posição de memória que guarda um endereço que contém algum dado.

São dois endereços: Um é a variável que contém o dado, o outro contém o endereço desta variável.

Assim b = &i diz ao processador para guardar no endereço b o endereço de i. Logo b e i estão apontando para o mesmo dado. Mas b não é igual a i. A variável i é um endereço que contém um dado e o ponteiro b é um endereço que contém um endereço que contém um dado.

Veja em HCS08

// a = &i
LDHX @i
STHX a
Carrega o endereço de i em HX e posteriormente guarda o valor de HX em a

Veja que em STM8

// *b = *b + 5
67 ldw x,_i
68 addw x,#5
69 ldw _i,x

O compilador decidiu por não usar o ponteiro b para acessar i pois ele percebeu que b ainda contém o endereço de i e usou diretamente o endereço de i. Normalmente o correto seria carregar o valor de b em x e então indiretamente ‘(x)’ carregar o valor endereçado por x no acumulador e finalmente efetuar a soma.

Foi exatamente o que o Z80 iria fazer

ld hl,(_b)
push hl
ld c,(hl)
inc hl
ld b,(hl)
pop hl
inc bc
inc bc
inc bc
inc bc
inc bc
ld (hl),c
inc hl
ld (hl),b

Espero ter ajudado a compreender o que de fato é um ponteiro. Entretanto este post foi útil e agradável ao demonstrar as diferentes formas de acesso a memória em diferentes arquiteturas e principalmente as otimizações que compiladores diferentes fazem.

 

Anúncios

1 comentário

  1. Srs. Eu sei que há alguns erros de português de semântica e sintaxe. Porém toda vez que clico em editar no WordPress um post com código ele teima em mudar os caracteres especiais como > & * em formato parecido com URLs que começam com & e o código fica todo corrompido.

    Curtir

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s