programowanieProgramista Embedded, Programista Backend

Wyjaśnij działanie operatora warunkowego (operatora ternarnego) w C. Jakie pułapki związane z konwersją typów i efektami ubocznymi występują przy jego użyciu?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Operator warunkowy ternarny (?:) pozwala na obliczenie i zwrócenie jednego z dwóch wyrażeń w zależności od warunku:

result = cond ? expr_true : expr_false;
  • Typ wyniku jest określany na podstawie ogólnych zasad "zwykłych konwersji arytmetycznych" (jeśli oba wyrażenia to liczby) lub na podstawie większego typu operandów (dla wskaźników i struktur).
  • Oba wyrażenia muszą być zgodne pod względem typu. Jeśli są różnych typów, kompilator spróbuje je przekonwertować do wspólnego typu, czasami z nieoczekiwanymi wynikami lub utratą danych.
  • Operator ternarny dopuszcza obecność efektów ubocznych w obu wyrażeniach, ale obliczane jest tylko to, które jest wymagane przez warunek.
  • W przypadku złożonych wyrażeń mogą występować problemy z niejednoznacznością lub nieprzewidywalnym zachowaniem (szczególnie przy zagnieżdżeniu lub używaniu makr i funkcji).

Przykład

int a = 10, b = 0; int max = (a > b) ? a : b; // max = 10

Pytanie z haczykiem

"Jeśli dwa wyrażenia operatora ternarnego zwracają obiekty różnych typów (na przykład wskaźnik i zero), jaki będzie typ wyniku?"

Wielu twierdzi, że kompilator zawsze "zgaduje" typ wyniku. W rzeczywistości, jeśli jedno z wyrażeń to wskaźnik, a drugie to 0 lub NULL, to wynik będzie miał typ wskaźnika. Jeśli różnica jest bardziej skomplikowana – na przykład, zwracany jest wskaźnik różnego typu lub typ int i typ enum – możliwa jest nieoczywista utrata informacji, a czasami kompilator zgłasza błąd.

struct node *p = NULL; void *v = cond ? p : NULL; // ok void *z = cond ? p : 0; // ok int i = cond ? 0 : "abc"; // błąd: niezgodne typy

Przykłady rzeczywistych błędów wynikających z braku znajomości niuansów tematu


Historia

W dużym projekcie z krzyżową kompilacją używane wyrażenie cond ? ptr : 0 zwracało wskaźnik przy jednym kompilatorze i int przy innym (gdzie 0 było interpretowane ściśle jako int). System zawieszał się podczas próby użycia wyniku jako wskaźnika.


Historia

W pakiecie finansowym, gdzie funkcje zwrotu używały operatora ternarnego z "nagimi" literami (cond ? 0.0 : 1), typ wyniku nieostrożnie stał się double, podczas gdy oczekiwano int, co spowodowało błędy w porównaniach i wydrukach.


Historia

W jednej z bibliotek wywołano wyrażenie z efektem ubocznym: flag ? inc(x) : dec(x). Podczas refaktoryzacji ukryty błąd: oba wyrażenia wywoływały funkcję (z własnymi efektami ubocznymi), podczas gdy oczekiwano, że zostanie wykonane tylko jedno. Zamieszanie z zagnieżdżonymi makrami prowadziło do podwójnej zmiany wartości, co wykryto dopiero w trakcie szczegółowego testowania.