Aus der Kategorie, „Spaß am Gerät, den man lieber nicht hätte“: Amoklaufende Optimierung im GCC.
Man lese folgenden Code, der alternierend ein LCD mit Zahlen, Buchstaben und „nichts“ füllt:
while (1) {
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("1234567890123456");
}
delay(250);
LCD_clear();
delay(250);
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("abcdefghijklmnop");
}
delay(250);
} |
while (1) {
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("1234567890123456");
}
delay(250);
LCD_clear();
delay(250);
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("abcdefghijklmnop");
}
delay(250);
}
What could possibly go wrong ❓
Gut, wir kennen ja Murphy: Alles, was schief gehen kann, geht schief. Und wenn schon der Code kaum Fehlerpotential hat, muss zwangsläufig der Compiler seinen Beitrag dazu leisten ❗
Genau das hat er dann auch getan… erstmal äußerte sich das ganze dann darin, dass wider erwarten nur Zahlen, leere Seite und wieder Zahlen kamen. Und dabei die Seite mit den Zahlen irgendwie „zu lang“ zu sehen war.
Was war also los?
Wie in Blick in das praktischerweise im Makefile angeforderte Extended Listing verrät, ist avr-gcc der Meinung, die beiden Schleifen würden das Gleiche machen, und kombiniert die in eine Schleife. Durch Ausprobieren anderer Optimierungsstufen zwischen -Os und -O1 kann man dann gcc immerhin überreden, nicht mehr beides in einer Schleife zu erledigen, sondern die zweite komplett zu ignorieren (toll!)… auch nicht ganz das Wahre. -O0 erzeugt Warnungen in <util/delay.h>, fällt also leider aus.
Die einzige mir bekannte Lösung ist das verlegen der beiden Schleifen in separate Methoden:
void test1(void)
{
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("1234567890123456");
}
delay(250);
}
void test2(void)
{
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("abcdefghijklmnop");
}
delay(250);
} |
void test1(void)
{
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("1234567890123456");
}
delay(250);
}
void test2(void)
{
for (unsigned char k=0;k<4;k++) {
LCD_setCursorPos(k,0);
LCD_write("abcdefghijklmnop");
}
delay(250);
}
while (1) {
test1();
LCD_clear();
delay(250);
test2();
} |
while (1) {
test1();
LCD_clear();
delay(250);
test2();
}
Gut, man muss eingestehen, der gcc hier ist schon etwas (avr-gcc (WinAVR 20080610) 4.3.0) älter. Das ist in einigen Kompatibilitätsproblemen mit anderen Projekten geschuldet, die sich auf ein bestimmtes Verhalten verlassen (hey, ich hab das nicht erfunden, ok?)
Kann also sein, dass das mittlerweile nicht mehr so ist. Aber trotzdem toll, dass so ein Bug es tatsächlich in eine Produktiv-Version schafft. Ist das kein Testcase? Naja, gcc halt…