Flagi -Wall i -Wextra to nie wszystko, czyli zbiór przydatnych flag do Gcc
Wielu z programistów korzystających z Gcc używa standardowego zestawu flag ostrzegających przed błędami, czyli tytułowego -Wall
, -Wextra
oraz -pedantic
. Użytkownicy clang’a mają jeszcze dodatkowo flagę -Weverything
. Podobnie osoby używające MSVS - /Wall
. Warto więc wiedzieć, jak sobie radzić gdy możemy skorzystać tylko z pierwszego wymienionego kompilatora.
Dodatkowe flagi Gcc
Poniżej znajduje się lista flag, które mogą się przydać przy znajdowaniu błędów.
Przysłanianie zmiennych: -Wshadow
Kompilator ostrzeże nas, gdy zmienna zostanie przysłonięta przez zmienną o tej samej nazwie:
int main() {
int i {0};
{
int i{1}; // warning: declaration of 'i' shadows a previous local
}
return i;
}
Możesz sprawdzić działanie tego kodu klikając w link
Brak wirtualnego destruktora: -Wnon-virtual-dtor
Poinformowani zostaniemy, jeśli jakaś klasa posiada metodę wirtualną i nie dostarcza wirtualnego destruktora:
class Foo { // 'class Foo' has virtual functions and accessible non-virtual destructor
virtual void foo() {}
};
int main() {}
Wykrywanie rzutowania: -Wold-style-cast
Kompilator ostrzeże nas, gdy będziemy używać rzutowania w stylu C:
int main() {
(double)4; // use of old-style cast
}
Nieużywane identyfikatory: -Wunused
Flaga ta ostrzega o wszystkim, co nie jest używane w naszym kodzie. Implikuje ona flagi: -Wunused-function``-Wunused-label
-Wunused-value
-Wunused-variable
int main(void) {
int i{}; // unused variable 'i'
}
Częstym sposobem “pozbycia się” warninga -Wunused-variable
jest zdefiniowanie makra:
#define UNUSED(x) (void)(x)
int main(void) {
int i{};
UNUSED(i);
}
Przeciążanie metody: -Woverloaded-virtual
Dostaniemy ostrzeżenie, jeśli będziemy chcieli przeciążyć metodę zamiast ją nadpisać:
struct Foo {
virtual void foo() {} // 'virtual void Foo::foo()' was hidden
};
struct Bar: Foo {
void foo(int) {} // by 'void Bar::foo(int)'
};
int main() {
}
Aby uniknąć ostrzeżenia od kompilatora, możemy użyć słowa kluczowego using:
struct Foo {
virtual void foo() {}
};
struct Bar: Foo {
using Foo::foo;
void foo(int) {}
};
int main() {
}
Rzutowanie na niepoprawny typ: -Wsign-conversion
Informuje nas, jeśli będziemy próbowali rzutować zmienną bez znaku na zmienną ze znakiem lub odwrotnie:
int main() {
unsigned int j = -1; // unsigned conversion from 'int' to 'unsigned int' changes value from '-1' to '4294967295'
}
Nieprawidłowe wcięcia: -Wmisleading-indentation
Ostrzega nas, gdy wcięcia mogą powodować złe zrozumienie kodu:
#include <iostream>
int main(int argc, char* argv[]) {
int i = argc;
if (i == 0) // this 'if' clause does not guard...
i = 0;
std::cout << "cpp-polska rlz\n"; // ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
}
Powielone warunki: -Wduplicated-cond
Dostaniemy ostrzeżenie, gdy w warunku if/else powtórzy się ten sam warunek:
int main(int argc, char* argv[]) {
if (argc == 0) {} // previously used here
else if (argc == 0) {} // duplicated 'if' condition
}
Powielony kod w rozgałęzieniach: -Wduplicated-branches
Kompilator wypisze warning, gdy w bloku kodu if/else pojawi się zduplikowany kod:
int main(int argc, char* argv[]) {
int i {};
if (argc == 0) { // this condition has identical branches [-Wduplicated-branches]
i = 2;
}
else {
i = 2;
}
}
Wykrywanie błędów logicznych: -Wlogical-op
Dostaniemy ostrzeżenie, gdy zostanie wykryte użycie operatora logicznego zamiast bitowego lub gdy warunki są takie same:
int main(int argc, char* argv[]) {
if (argc > 0 || argc > 0); // logical 'or' of equal expressions
int i = argc || 0x00031; logical 'or' applied to non-boolean constant
}
Nieprzydatne rzutowanie: -Wuseless-cast
Kompilator wypisze ostrzeżenie, gdy będziemy rzutować na ten sam typ:
int main(int argc, char* argv[]) {
int i = static_cast(argc); // useless cast to type 'int'
}
Rzutowanie zmiennoprzecinkowe: -Wdouble-promotion
Kompilator ostrzeże nas, gdy wystąpi niejawne rzutowanie z typu float na typ double:
constexpr double LOW_QUALITY_PI = 3.1;
float arena(float radius) {
return LOW_QUALITY_PI * radius * radius; // implicit conversion from 'float' to 'double' to match other operand of binary expression
}
int main() {}
Błędy funkcji formatujących: -Wformat=2
Kompilator ostrzeże nas, gdy wykryje błędy w funkcjach formatujących (printf
, sprintf
etc.), implikuje flagi: -Wformat (domyślnie włączone z -Wall
), -Wformat-nonliteral
, -Wformat-security
oraz -Wformat-y2k
. Flaga może być przydatna na przykład w wykrywaniu luk bezpieczeństwa:
#include <cstdio>
int main(int argc, char* argv[]) {
printf(argv[0]); // format not a string literal and no format arguments
}
Clang & MSVS
Zarówno Clang jak i MSVS posiadają flagi, które potrafią włączyć wszystkie warningi. Dla Clang’a jest to -Weverything
, dla MSVS jest to /Wall
. Warto pamiętać, że taki poziom ostrzeżeń może spowodować bardzo dużo błędnych rozpoznań (ang. false-positives). Ponadto, MSVS, w odróżnieniu od Clang’a i Gcc, pokazuje nam ostrzeżenia również z bibliotek systemowych (szacuneczek!). A może Wy, znacie jeszcze jakieś ciekawe flagi, które pomagają we wczesnym znajdowaniu błędów?
Źródła: https://gcc.gnu.org, https://github.com/lefticus/cppbestpractices