Błędy listing:
Listing 1c. Przykładowa funkcja inkrementująca zmienną typu unsigned char
bool uc_inc(unsigned char &a) {
/* czy nastąpi przepełnienie */
if(a > a + 1) {
return false;
}
a++;
return true;
}
Powinno być:
Listing 1c. Przykładowa funkcja inkrementująca zmienną typu unsigned char
bool uc_inc(unsigned char &a) {
/* czy nastąpi przepełnienie */
if(a > (unsigned char)(a + 1)) {
return false;
}
a++;
return true;
}
Błąd polega oczywiście na zignorowaniu integer promotion, czyli promocji typu unsigned char na int przed wykonaniem dodawania i potem porównania - a więc overflow nigdy nie następuje. Konkretniej, int(255)+int(1) to int(256), które jest oczywiście większe niż int(255), a więc overflow nie zostaje wykryty (zachęcam do rzucenia okiem na dyskusję w komentarzach do tego postu).
KrzaQ jako dowód na powyższe niechciane zachowanie wrzucił m.in. postać wynikową w assembly powyższej (nieprawidłowej) funkcji. Fragment w składni intela:
mov rax, QWORD PTR [rbp-8]
movzx eax, BYTE PTR [rax]
movzx edx, al
mov rax, QWORD PTR [rbp-8]
movzx eax, BYTE PTR [rax]
movzx eax, al
add eax, 1
cmp edx, eax
Jak widać powyżej, porównanie faktycznie dokonuje się na rejestrach 32-bitowych (edx, eax), a więc cast do int'a lub unsigned int'a musiał nastąpić.
Od siebie dorzucę jeszcze fragment zrzutu generowanego przez gcc -fdump-tree-original:
if ((int) *a > (int) *a + 1)
{
return = 0;
}
Jeśli chodzi o odpowiedni cytat ze standardu (C++, jeden z nowszych draftów; sekcja Integer Promotion), to:
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue *can* be converted to a prvalue of type unsignedint.
W ramach post mortem sprawdziłem jak doszło do popełniania przeze mnie takiego błędu (revision history w Google Docs to świetny ficzer), i wygląda na to, że wcześniej listing 1c miał następującą formę:
bool uc_inc(unsigned char *a) {
if(!a) return false;
unsigned char tmp = *a + 1;
if(*a > tmp) {
/* nastąpi przepełnienie */
return false;
}
(*a)++;
return true;
}
Jak widać w powyższym listingu mam dodatkową zmienną tmp, której celem było blokowanie promocji (w standardzie jest "can", nie "will" czy "must") lub implicit cast wyniku na unsigned char. I ten listing jest poprawny.
Niestety, wygląda na to, że później (o 3:15 AM wg revision history ;p) chciałem go uprościć, więc zamieniłem pointery na reference i pozbyłem się zmiennej tmp bez przeniesienia implicit castu na explicit castu. My bad.
Przepraszam za zamieszanie i wprowadzenie w błąd, i raz jeszcze podziękowania dla KrzaQ za wypatrzenie błędu.
Z drobniejszych rzeczy: na stronie 4 (24 w magazynie) listing na dole po prawej jest oznaczony jako "Listing 3a". Oczywiście poprawnym oznaczeniem jest "Listing 2a". Podziękowania dla rafalona.
I tyle ;)
Comments:
Forma funkcji wynika ze "spisu treści" artykułu - dla typów unsigned gdzie overflow jest w pełni zdefiniowany chciałem pokazać ten sposób, natomiast dla typów signed gdzie overflow jest niezdefiniowany pokazałem sposób o którym piszesz.
@KrzaQ
Yep.
Chociaż osobiście najchętniej bym to napisał w assembly :)
Add a comment: