Flagi -Wall i -Wextra to nie wszystko, czyli zbiór przydatnych flag do Gcc


2018-06-06, 00:00

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() {}

Interaktywny przykład

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
}

Interaktywny przykład

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'
}

Interaktywny przykład

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() {
}

Interaktywny przykład

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() {
}

Interaktywny przykład

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'
}

Interaktywny przykład

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'
}

Interaktywny przykład

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
}

Interaktywny przykład

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;
  }
}

Interaktywny przykład

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
}

Interaktywny przykład

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'
}

Interaktywny przykład

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() {}

Interaktywny przykład

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
}

Interaktywny przykład

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



Wojciech Razik

Programista C++ z wieloletnim stażem. Uwielbia czytać standard C++ przed snem, na co dzień tworzy oprogramowanie do robota. Jego drugą pasją jest hejtowanie JSa.

Profil na LinkedIn
Pssst! Używamy Cookies. Poprzez używanie naszego serwisu zgadzasz się na odczytywanie i zapisywanie Cookies w swojej przeglądarce.