programowanieInżynier C dla systemów wbudowanych

Jak działają operatory bitowe (&, |, ^, ~, <<, >>) w języku C? Jakie są ich szczególności przy pracy z typami o różnej długości i znaku, oraz jakie powszechne błędy popełniają programiści podczas ich używania?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Operatory bitowe zarządzają pojedynczymi bitami całkowitych typów liczbowych:

  • & — bitowe AND
  • | — bitowe OR
  • ^ — bitowe XOR
  • ~ — bitowe NOT
  • << — przesunięcie w lewo
  • >> — przesunięcie w prawo

Szczególności:

  • Operatory działają tylko z typami całkowitymi (int, unsigned int, itd.).
  • Liczby ze znakiem (signed) przy przesunięciach w prawo (>>) mogą generować przesunięcie arytmetyczne lub logiczne — zależy od kompilatora.
  • Przy przesunięciach o liczbę bitów przekraczającą rozmiar zmiennej pojawia się zachowanie nieokreślone.
  • Dla niezawodnej pracy często wybiera się typy unsigned, aby uniknąć rozszerzenia znaku.

Przykład:

unsigned int flags = 0; flags |= 0x1; // Ustawia 0-ty bit flags &= ~0x2; // Resetuje 1-szy bit if ((flags & 0x4) != 0) { /* ... */ } // Sprawdza 2-gi bit

Pytanie podchwytliwe.

Czym różni się operacja przesunięcia w prawo (>>) dla typów signed int i unsigned int?

Często błędna odpowiedź: Uważa się, że przesunięcie w prawo zawsze wstawia zera z lewej strony, niezależnie od znaku.

Prawidłowa odpowiedź: Dla typu unsigned int przesunięcie w prawo (>>) zawsze wstawia zera. Dla signed int wstawiane jest albo znak (jedynki, jeśli liczba jest ujemna), albo zera — zależy od implementacji kompilatora (architektura i zasady standardu C).

Przykład:

signed int a = -8; unsigned int b = (unsigned int)a; printf("%d ", a >> 1); printf("%u ", b >> 1);

W pierwszym przypadku wynik zależy od kompilatora; w drugim zawsze będzie to logiczne przesunięcie z zerami.

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


Historia

W kodzie obsługi protokołu flagi sygnałowe przechowywane były w typie char. Programista zastosował przesunięcie o 8 bitów (flag << 8), co z powodu przepełnienia i zasad podnoszenia typów doprowadziło do utraty wszystkich danych — result zawsze był równy zeru.


Historia

Odczyt danych z protokołu sieciowego (big-endian). Użycie operacji bitowych do łączenia bajtów nie było poprzedzone rzutowaniem na unsigned, co czasami prowadziło do nieoczekiwanych ujemnych wartości przy odczycie pola struktury.


Historia

Użycie ~ (bitowego NOT) do resetowania bitów w wartości typu int (np. ~0x80) było traktowane jako 0x7F, ale w rzeczywistości uzyskiwano liczbę ujemną -129, co prowadziło do błędów przy kolejnych obliczeniach i sprawdzeniach logicznych.