Przykład działa poprawnie, przyczynę błędu podał już kol. @rajszym. Przyczyną jest nowy, sprytny kompilator. Otóż widzi on, że podana pętla wpisująca wartości 0xAB jest efektywnie odpowiednikiem funkcji memset i zamiast pracowicie robić to co mu nakazałeś (tak robiły kompilatory do momentu wydania książki i jeszcze potem przez jakiś czas), zamienia pętlę na wywołanie funkcji bibliotecznej memset. Problem w tym, że oczywiście wywołuje ją przez call, co wymaga użycia stosu, który ta funkcja nadpisze, w efekcie powrót jest niemożliwy. Żeby było śmieszniej, to wywołanie memset jest bardziej kosztowne niż realizacja pokazanej pętli... ale najwyraźniej jest optymalne na poziomie O3.
Stąd też trzeba kompilatorowi przeszkodzić w takiej optymalizacji - można to zrobić na kilka sposobów - np. tak jak zaproponowano poprzez wyrzucenie atrybutu optimize, aczkolwiek to ryzykowne posunięcie, gdyż jeśli użytkownik dla całego projektu wybierze O3 to problem wróci. Lepiej więc zamienić O3 na Os. Można też umieścić w pętli pomiędzy zapisami asm volatile("nop") - uniemożliwi to wykorzystanie funkcji memset i wszystko zacznie działać ok. Odpal sobie ten kod w symulatorze, albo podglądnij plik lss to zobaczysz o co chodzi. Inna możliwością jest nadpisanie całej pamięci stosu, z wyjątkiem dwóch bajtów użytych na powrót z memset. Rozwiązanie skuteczne, o tyle tylko ryzykowne, że jeśli kolejna wersja kompilatora wprowadzi kolejne ulepszenia, to zapotrzebowanie na stos może się zwiększyć. Scenariusz ten jednak jest małoprawdopodobny.