programowanieWiodący programista C, Programista systemowy

Jak w języku C realizowane jest wykonywanie efektów ubocznych przy obliczaniu argumentów funkcji? Jaka jest kolejność obliczania argumentów i jakie niespodzianki mogą się tutaj pojawić?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W języku C kolejność obliczania argumentów funkcji nie jest zdefiniowana przez standard (do C99 włącznie). Argumenty mogą być obliczane od lewej do prawej, od prawej do lewej lub w dowolnej innej kolejności (według uznania kompilatora lub architektury).

  • Wszystkie wyrażenia/efekty uboczne w argumentach muszą być zakończone przed wywołaniem funkcji, ale nie ma gwarancji, że są one wykonywane w określonej kolejności.
  • Oznacza to, że użycie zmiennych z modyfikacją wartości w kilku argumentach jest potencjalnym źródłem nieokreślonego zachowania (undefined behavior) lub po prostu różnic między architekturami.

Przykład

void fn(int a, int b) { /* ... */ } int x = 1; fn(x++, x++); // kolejność obliczania x++ i x++ nie jest określona!

Pytanie z pułapką

"W jakiej kolejności obliczane są argumenty funkcji w C i czy można na to polegać przy pisaniu kodu?"

Częstym błędem jest założenie, że argumenty są obliczane od lewej do prawej (analogicznie do wyrażeń). W praktyce każde wywołanie funkcji jest kompilowane według uznania kompilatora (i platformy).

void foo(int a, int b, int c); int x = 1; foo(x++, x++, x++); // wynik zależy od kolejności obliczania argumentów

Prawdziwa odpowiedź: nie można polegać — zachowanie nie jest określone!

Przykłady rzeczywistych błędów z powodu nieznajomości szczegółów tematu


Historia

W wieloplatformowym produkcie programista napisał push(stack, stack->size++, data);. Na większości platform wszystko działało, ale na jednej rozmiar stosu zwiększał się przed przekazaniem danych, na innej — po. Dane "traciły się" lub były adresowane niepoprawnie, błąd występował rzadko i był bardzo trudny do debugowania.


Historia

W bibliotece protokołu sieciowego funkcja logowania była wywoływana z wyrażeniami-argumentami, które inkrementowały liczniki statystyczne. Raporty statystyczne były generowane nieprawidłowo: u różnych klientów indeksy liczników różniły się, ponieważ nie były wykonywane w oczekiwanej kolejności.


Historia

W interfejsie zarządzania sprzętem funkcja inicjalizacji przekazywała wskaźniki i przesunięcia z inkrementem wewnątrz argumentów (typ init(ptr++, cnt++);). Na niektórych procesorach sprzęt był inicjowany poprawnie, a na innych występowały błędy, których przyczynę długo szukano w sprzęcie, mimo że problem tkwił w niepoprawnym kodzie C.