Warning’i i error’y
Kto programował choć raz w swoim życiu, „dostał” od kompilatora jakiś error lub warning. Ostatnio znajomy podlinkował do fajnej rozmowy na pewnym blogu, gdzie najbardziej (stąd owy post) zaciekawiło mnie stwierdzenie jednego z blogowiczów(Kristine) na temat warningów, pozwolę sobie je zacytować :
But every warning a compiler produces is produced for perfectly legal code; otherwise it would be an error. The problem is just that C/C++ allows for constructs that are not desired in most cases (such as “if (a = 0)” or “int i = 0.5″)
No cóż, zgadzam się z takim stwierdzeniem i dlatego postanowiłem wyjaśnić tym postem kilka kwestii związanych z tym kiedy i dlaczego dostajemy error lub warning.
Warning
Warning to (jak sama nazwa wskazuje) ostrzeżenie od kompilatora, kwestia tego kiedy,dlaczego oraz ile ich dostajemy.
Ostrzeżenia dostajemy nawet dla prawidłowego (względem składni języka) kodu. No właśnie. To dlaczego w ogóle kompilator nam o tym “mówi” skoro kod jest poprawny i siłą rzeczy zostanie skompilowany (tutaj ważna kwestia, warning w przeciwieństwie do error’a nie przeszkadza w kompilacji) tyle że najprawdopodobniej nie będzie działał tak jak zaplanowaliśmy. Wyrzucając warning kompilator mówi nam jakby „Uważaj ! W tym miejscu może być coś nie tak !” zatem powinniśmy zwrócić uwagę na to. W Poradniku początkującego programisty Gynvael napisał fajny wniosek :
Ostrzeżeń nie usuwa się poprzez ich ukrywanie, tylko poprzez naprawę kodu źródłowego!
To ważne, bo sporo osób ignoruje takie ostrzeżenia albo nawet o nich nie wie . Z pomocą przychodzą dodatkowe opcje kompilacji (mowa o kompilatorach gcc/g++), których jest sporo a ich lista dostępna jest tutaj . Warto korzystać z co najmniej dwóch z w/w listy , -Wall i -Wextra (które de facto powodują włączenie innych które są na liście ) ale inne też są przydatne, zależnie od sytuacji. A jeśli ktoś ma tendencję do ignorowania warningów, to polecam dodatkowo opcję -Werror która zamienia wszystkie warningi w errory (najlepiej stosować wraz z -Wall ).
Wiemy jak się włącza pokazywanie warningów, wiemy co się z nimi robi (w/w stwierdzenie Gynvaela ), ale w dalszym ciągu nie wiemy kiedy ostrzeżenie staje się już błędem. Jak już wcześniej napisałem, warning może zostać wypisany przez kompilator dla poprawnego względem składni kodu, ale co na to standard języka ? Otóż jak się okazuje standard ma tutaj całkiem sporo do powiedzenia (btw zachęcam do poczytania standardu , wiele ciekawych rzeczy tam można znaleźć ;> ), bo warningi dotyczą właśnie kodu “nielegalnego”względem standardu (albo takich zachowań które w standardzie opisane nie są).
Przykład :
#include "stdio.h" // sic!
void main()
{
int a;
int b[a];
}
Prosty kod, który teoretycznie jest prawidłowy (względem składni języka). Ale wg standardu takie rozwiązanie jest niebezpieczne i nie powinno się go stosować. Tj można, bo jest to dopuszczalne, ale krótko mówiąc bawimy się tym na własne ryzyko. (btw, ktoś wie jak się nazywa w/w inicjlizacja tablicy ? :> ) Zobaczmy co na to kompilator :
g++ -o test test.cpp
Normalna kompilacja, bez dodatkowych opcji. Efekt ? No errors, no warnings.
g++ -o test -Wall test.cpp
I tutaj widzimy dodatkową opcję dzięki której dostajemy przepiękne ostrzeżenie :
test.cpp: In function 'int main()’:
test.cpp:10:8: warning: 'a’ is used uninitialized in this function
Errory
Teraz czas na errory. Czym są ? To błędy które wypisywane przez kompilator (lub linker (a popełniane przez programistów ) )uniemożliwiają skompilowanie/zlinkowanie programu. Dlaczego ? Ano dlatego, że błąd pojawia się wtedy gdy kod nie może być wykonany . Np niedomknięty nawias/klamra osth (ich wykrycie wymaga analizy kodu) lub po prostu jakiś inny błąd. Ofc za każdym razem dostajemy wyjaśnienie błędu i jego wskazanie, ale sama naprawa errora pozostawiona jest jego twórcy. W końcu jak sobie naważyliśmy piwa to trzeba je samemu wypić. Dodatkowe opcje kompilacji dla errorów również można znaleźć w linku podanym w sekcji powyżej.
Undefinded behavior & implicit cast
O przypadku UB (undefinded behavior) z inkrementacjami można było poczytać tutaj. Niektóre języki programowania mają swoje niezdefiniowane zachowania. Jest ich cała masa, niektóre jeszcze pewnie nie odkryte i tak do końca nie jest możliwe przewidzenie jak się zachowa kompilator w niektórych sytuacjach, które nie są mu znane. Wtedy wynik danej operacji tak naprawdę zależy od „widzimisię” kompilatora, bo to on na swój sposób interpretuje kod źródłowy. Dotąd wymyślone UB są opisane w standardzie danego języka dzięki czemu dotarcie do nich nie jest specjalnie trudne.
Kolejna rzecz to tzw implicit cast (dosłownie niejawne rzutowanie), które polega na niejawnej względem programisty konwersji danych, np float na int. Taką konwersją jest np bool na int. Rozważmy przykład :
…
bool a = true; // definicja zmiennej typu bool
int b = a; // implcit cast !
printf(„%i”,b); // wypisanie b
…
Jaki będzie wynik b ? Typ bool może przyjmować tylko jedną spośród dwóch wartości, prawda lub fałsz. Porównajmy to do układu scalonego. Ktoś się zastanawiał dlaczego procesor używa binarnego systemu liczenia ? Albo płynie prąd, albo nie (chociaż czasem jest tylko różnica napieć ). Albo 0 albo 1. Podobnie w programowaniu. Albo fałsz (0) albo prawda (1) (podobno nie zawsze ). Stąd b będzie miała wartość 1. Czy aby na pewno ?
#include
int main()
{
bool a = true;
int b = a;
printf(„%i\n”,b);return 0;
}
nism0@nism0-desktop:~$ g++ -o test test.cpp
nism0@nism0-desktop:~$ ./test
1
nism0@nism0-desktop:~$
Cóż w sumie to chyba na tyle 😉