Was GCC nicht so gut schafft, ist den Zugriff auf globale (ausserhalb jeder Funktion) definierte Variablen zu optimieren.

So führt
Code:
typedef unsigned char byte;

byte var2;

void foo1()
{
	var2++;
	if (var2 > 10)
		var2 = 1;
}
zu folgendem asm Code (snip aus dem lst-File):
Code:
0000005c <foo1>:
  5c:	80 91 63 00 	lds	r24, 0x0063
  60:	8f 5f       	subi	r24, 0xFF	; 255
  62:	80 93 63 00 	sts	0x0063, r24
  66:	8b 30       	cpi	r24, 0x0B	; 11
  68:	18 f0       	brcs	.+6      	; 0x70
  6a:	81 e0       	ldi	r24, 0x01	; 1
  6c:	80 93 63 00 	sts	0x0063, r24
  70:	08 95       	ret
(Unser var2 wurde nach 0x0063 lokatiert).
Der Code umfasst 11 Worte (2x11 Byte).
Der Schreib-Zugriff in 62 ist überflüssig. Var2 ist ein normales, nicht-flüchtiges Objekt (nicht volatile).

Den Zugriff umgeht man am einfachsten, indem man für var2 eine lokate Variable anlegt. Wird optimiert compiliert (zb -Os), dann legt GCC locals in Register, wenn noch Platz ist:
Code:
void foo2()
{
	byte var = var2;
	
	var++;
	if (var > 10)
		var = 1;
		
	var2 = var;
}
Das führt zu Code, der nur noch 9 Einheiten groß ist:
Code:
00000072 <foo2>:
  72:	80 91 63 00 	lds	r24, 0x0063
  76:	8f 5f       	subi	r24, 0xFF	; 255
  78:	8b 30       	cpi	r24, 0x0B	; 11
  7a:	08 f0       	brcs	.+2      	; 0x7e
  7c:	81 e0       	ldi	r24, 0x01	; 1
  7e:	80 93 63 00 	sts	0x0063, r24
  82:	08 95       	ret
Nachteil ist natürlich, daß der Quellcode unschöner wird.

Eine weitere Reduktion der Codegröße kann man durch indirekten Zugriff erreichen. Alle Variablen, auf die oft (in Bezug auf Code) zugegriffen wird, legt man in eine global bekannte Struktur (global_t), erzeugt eine Instanz dieser Struktur (globals) und legt die Adresse von globals ins Y-Register (g):
Code:
// Struktur für oft gebrauchte Variablen
typedef struct
{
	byte var0;
	byte var1;
	byte var2;
	...
} global_t;

global_t globals;

// g (Y-Reg) hält die Adresse von globals
register global_t *g asm ("r28");

void foo3()
{
	byte var = g->var2;
	
	var++;
	if (var > 10)
		var = 1;
		
	g->var2 = var;
}

void main()
{
    ...
    g = &globals;
    ...
}
Das resultiert in folgendem Code. g wird in main() mit 0x0066 vorgeladen. Y+2 adressiert dann g->var2 (globals.var2).
Code:
00000084 <foo3>:
  84:	8a 81       	ldd	r24, Y+2	; 0x02
  86:	8f 5f       	subi	r24, 0xFF	; 255
  88:	8b 30       	cpi	r24, 0x0B	; 11
  8a:	08 f0       	brcs	.+2      	; 0x8e
  8c:	81 e0       	ldi	r24, 0x01	; 1
  8e:	8a 83       	std	Y+2, r24	; 0x02
  90:	08 95       	ret

000000c8 <main>:
  ...
  d0:	c6 e6       	ldi	r28, 0x66	; 102
  d2:	d0 e0       	ldi	r29, 0x00	; 0
  ...
Unsere Funktion foo3() hat nur noch eine Größe von 7, im Vergleich zu 11 in foo1()!

Nicht zu erwähnen, daß das auch Fallstricke hat!
Das Y-Register wird von GCC als Framepointer verwendet. Standardmässig ist in GCC -fomit-framepointer aktiviert, so daß dieser möglichst immer eliminiert wird. Ist eine Funktion jedoch zu komplex oder kann der Framepointer nicht eliminiert werden, dann führt unsere Technik zu falschem Code (siehe unten).

Daher muss man, wenn man diese Technik anwenden weil, auf einiges achten:

  • Die Struktur und die Deklaration von g als asm("r28") muss in ALLEN Quellen, die zum Projekt gehören, bekannt sein. Denn Y-Reg (r28:r29) darf nicht verändert werden. Das gilt auch für Quellen, die globals nicht benutzen!

  • Auf diese Weise lassen sich maximal 64 Byte sinnvoll verwalten, denn der ldd-Befehl des AVR lässt nur Offsets von 0 bis 63 zu.

  • Um sicher zu gehen, daß kein falscher Code erzeugt wurde, macht man ein grep über den generierten asm-Code:
    Die ersten beiden Fundstellen setzen den Stackpointer, die dritte ist Teil unserer Zuweisung g = &globals. Findet grep mehr, dann muss man die Quelle solange vereinfachen, bis r28:r28 nicht mehr verwendet wird und nur noch diese 3 Fundstellen verbleiben.
    Assenbler-Listings erstellt man übrigens mit -save-temps oder mit -S anstatt -c.

Code:
> grep "r28" *.s
        ldi r28,lo8(__stack - 0)
        out __SP_L__,r28
        ldi r28,lo8(globals)     ;  g,   ;  8   *movhi/4        [length =
Beherzigt man dies, dann funktioniert das prima. \/
Und vor allem auf kleinen Controllern passt deutlich mehr rein. Wo was zu holen ist, sieht man mit einem Blick ins list-File.
Ich verwende diese Technik bei kleinen AVRs wie dem 2313 problemlos.

Hier noch ein Beispiel, daß zu falschem Code führt.
Verantwortlich ist das volatile auf eine lokale Variable, so daß sie nicht in einem Register leben kann.
Code:
void foo4()
{
	volatile byte dummy;

	byte var = g->var2;
	
	var++;
	
	if (var > 10)
		var = 1;
		
	g->var2 = var;

	dummy = 0x55;
}
ergibt folgenden Horror-Code:
Code:
00000092 <foo4>:
  92:	cf 93       	push	r28
  94:	df 93       	push	r29
  96:	cd b7       	in	r28, 0x3d	; 61
  98:	de b7       	in	r29, 0x3e	; 62
  9a:	21 97       	sbiw	r28, 0x01	; 1
  9c:	0f b6       	in	r0, 0x3f	; 63
  9e:	f8 94       	cli
  a0:	de bf       	out	0x3e, r29	; 62
  a2:	0f be       	out	0x3f, r0	; 63
  a4:	cd bf       	out	0x3d, r28	; 61
  a6:	8a 81       	ldd	r24, Y+2	; 0x02
  a8:	8f 5f       	subi	r24, 0xFF	; 255
  aa:	8b 30       	cpi	r24, 0x0B	; 11
  ac:	08 f0       	brcs	.+2      	; 0xb0
  ae:	81 e0       	ldi	r24, 0x01	; 1
  b0:	8a 83       	std	Y+2, r24	; 0x02
  b2:	85 e5       	ldi	r24, 0x55	; 85
  b4:	89 83       	std	Y+1, r24	; 0x01
  b6:	21 96       	adiw	r28, 0x01	; 1
  b8:	0f b6       	in	r0, 0x3f	; 63
  ba:	f8 94       	cli
  bc:	de bf       	out	0x3e, r29	; 62
  be:	0f be       	out	0x3f, r0	; 63
  c0:	cd bf       	out	0x3d, r28	; 61
  c2:	df 91       	pop	r29
  c4:	cf 91       	pop	r28
  c6:	08 95       	ret
Dieser Code ist falsch, denn Y kann nicht zugleich den Framepointer und den Zeiger auf unser globals enthalten.