Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: pekfos
Kurs C++

Najczęściej spotykane błędy w kodzie

[pytanie/odpowiedź] Najczęściej spotykane błędy w kodzie zamieszczanym na forum.

Najczęstsze bugi

Poniższa lista, stałe work in progress, zbiera błędy popełniane przez użytkowników, które pojawiają się na forum często i regularnie. By wykryć wiele z nich, wystarczy mieć włączone ostrzeżenia kompilatora i zwracać na nie uwagę od czasu do czasu. Zwłaszcza wtedy, gdy coś nie działa..

Średnik po if, while

Problem szczególnie popularny wśród początkujących, którzy nie wiedzą jeszcze co znaczy średnik w C++, więc wstawiają go odruchowo i jak popadnie. Średnik sam z siebie oznacza instrukcję pustą. Dlatego coś takiego działa poprawnie:
C/C++
std::cout << "Hello";;;;;;;
To jest wypisanie tekstu i 6 instrukcji pustych. Można dość do wniosku, że niepotrzebne średniki są ignorowane, ale jeśli postawimy średnik po if, while itp, to warunkowi, czy pętli, będzie podlegać właśnie instrukcja pusta, a nie to, co chcieliśmy.
C/C++
if( wiek >= 18 );

std::cout << "Mozesz kupic alkohol"; // Wykonywane bezwarunkowo!

Zmienne przechowują wartości. Nie wyrażenia!

C/C++
int a, b;
int wynik = a + b;
std::cin >> a >> b;
"Wynik to suma a i b, więc wczytam a i b i wynik będzie zawierać sumę.." NIE. wynik przechowuje wartość, wynik sumowania wartości a i b, jakie by one w tym momencie nie były. Te zmienne są w tym jednym momencie użyte do zsumowania i to jest koniec. Wynik się magicznie nie zaktualizuje, gdy zmienne użyte kiedyś do obliczenia go przypadkiem zmieniły wartości. Jak jakaś operacja na zmiennych ma mieć sens, wartości zmiennych muszą być w tym momencie już obliczone.

Zły operator

C/C++
if( i = 5 )
     std::cout << "jest równe 5";
Czy ten warunek porównuje i do 5? Nie, ten warunek przypisuje 5 do i i przez to jest zawsze prawdziwy. Do if nie podaje się porównania, tylko wyrażenie - porównanie, ale nie tylko. Przejdzie wszystko, co ma wartość konwertowalną do wartości logicznej. Wartością przypisania jest wartość zmiennej po przypisaniu, więc i = 1 jest warunkiem zawsze prawdziwym, i = 0 jest warunkiem zawsze fałszywym.
warning: suggest parentheses around assignment used as truth value [-Wparentheses]
  if(i = 5)
          ^

Brak nawiasów klamrowych

if(a > 100)
    std::cout << "a > 100, ustawiam na 0\n";
    a = 0;

// coś z `a`
Wcięcia mają znaczenie dla czytelności kodu, ale C++ to nie Python, gdzie mają także wpływ na znaczenie programu. Aby wiele instrukcji było podporządkowanych warunkowi, pętli, etc, trzeba je zamknąć w blok kodu {}, a nie tylko postawić obok siebie. Po sformatowaniu kodu na forum, przypisanie do a jest już wcięte w bardziej odpowiedni sposób.
C/C++
if( a > 100 )
     std::cout << "a > 100, ustawiam na 0\n";

a = 0;

// coś z `a`

Dzielenie bez ułamków

C/C++
float half = 1 / 2;
Weźmy początkującego programistę z zamiłowaniem do ułamków zwykłych i o taki błąd nietrudno. Tworzymy zmienną float, a więc zdolną przechowywać wartości "z ułamkiem" i przypisujemy do niej ułamek. Problem w tym, że po takim zabiegu, half ma wartość zero, bo w C++ nie ma ułamków zwykłych. Jest za to dzielenie 1 przez 2, obie wartości są typu int, a dzielenie między liczbami o typach całkowitych, daje całkowity wynik. Część ułamkowa liczby jest więc tracona, bez zaokrąglania (9/10 to też 0).
Żeby taki zapis zadziałał zgodnie z intencjami, przynajmniej jedna z tych liczb musi być typu zmiennoprzecinkowego. Można to osiągnąć rzutowaniem, ale znacznie krócej jest użyć literałów zmiennoprzecinkowych:
C/C++
1 / 2.f // 2.0f, 2.f - float. int / float = float
1 / 2.// 2.0, 2. - double. int / double = double

Porównywanie liczb zmiennoprzecinkowych

C/C++
float f = 0;
for( int i = 0; i < 10; ++i )
     f += 0.1f;

std::cout << "Wynik: " << f << " czy rowne 1? " <<( f == 1.f ? "tak": "nie" );
Wynik: 1 czy rowne 1? nie
Wielu błędnie zakłada, że odpowiedź brzmi "tak". Tak mówi matematyka. Matematyka mówi wiele ciekawych rzeczy. Na przykład, że jest nieskończona ilość liczb rzeczywistych, ale bity w zmiennej float mają tylko 232 możliwych kombinacji. Blado to wypada w porównaniu do konceptu nieskończoności. Liczba 0.1 jest dobra do tego przykładu, bo wygląda niewinnie. Ma krótkie rozwinięcie dziesiętne, ale w systemie dwójkowym, ma nieskończone rozwinięcie. Każde dodawanie w tej pętli jest obarczone błędem obliczeń, więc uzyskamy liczbę bliską 1 (na tyle bliską, że przy wyświetlaniu będzie zaokrąglona), ale jednak nie równą 1.
C/C++
std::cout << f - 1;
1.19209e-007
Jeśli naprawdę musisz porównać liczby w ten sposób, musisz określić tolerancję na błędy. Na przykład sprawdzając, czy wartość bezwzględna z różnicy dwóch liczb jest mniejsza od jakiegoś Epsilon. W tym wypadku tą wartością może być 1e-6 (10-6), ale czasem potrzeba większej, lub mniejszej tolerancji, zwłaszcza przy zmianie typu na taki o większej, lub mniejszej, precyzji.
C/C++
std::cout << "Czy mniej wiecej rowne 1? " <<( fabs( f - 1 ) < Epsilon ? "tak"
    : "nie" );
Czy mniej wiecej rowne 1? tak

Niezwracanie wartości z funkcji

C/C++
int signum( int arg )
{
    if( arg > 0 )
         return 1;
   
    if( arg < 0 )
         return - 1;
   
}
Funkcja ma zwracać wartość typu int i faktycznie zwraca. Gdyby nie zwracała, pojawiło by się takie ostrzeżenie
warning: no return statement in function returning non-void [-Wreturn-type]
 }
 ^
Tu problemem jest to, że istnieją takie przypadki, w których funkcja nic nie zwraca. Kompilatory są na tyle inteligentne, że wykrywają takie błędy. Tutaj to łatwo zauważyć, ale kiedy funkcja jest długa i problem jest dla jednej wartości z N możliwych, da się to przeoczyć, zapomnieć o tym, czy zostawić na później i do tego nie wrócić.
warning: control reaches end of non-void function [-Wreturn-type]

Zwracanie referencji/wskaźnika na zmienną lokalną

C/C++
int & f()
{
    int a = 2;
    return a;
}
W momencie zwrócenia wartości, zmienna a przestaje istnieć, więc odwołanie przez zwróconą referencję, lub wskaźnik, będzie błędne.
In function 'int& f()':
warning: reference to local variable 'a' returned [-Wreturn-local-addr]
In function 'int* f()':
warning: address of local variable 'a' returned [-Wreturn-local-addr]