Comparison of Diagnostics between GCC and Clang

It is often repeated that the Clang compiler produces far superior diagnostics to GCC. For example the Expressive Diagnostics page shows examples where Clang's diagnostics were indeed superior to GCC 4.2. However, that version of GCC is a few years old, and GCC has improved considerably since then. This page revisits the examples using recent versions of GCC and add further interesting examples.1

Column Numbers and Caret Diagnostics

GCC has printed column numbers for several releases and the 4.8 release series added caret diagnostics too.

For this source:

   1 #include <stdio.h>
   2 void f() {
   3    printf("%.*d");
   4 }

$ gcc-4.8 -fsyntax-only  -Wformat format-strings.c

format-strings.c: In function 'f':
format-strings.c:3:4: warning: field precision specifier '.*' expects a matching 'int' argument [-Wformat]
    printf("%.*d");
    ^
format-strings.c:3:4: warning: format '%d' expects a matching 'int' argument [-Wformat]
    printf("%.*d");
    ^

$ clang-3.1 -fsyntax-only format-strings.c

format-strings.c:3:14: warning: '.*' specified field precision is missing a matching 'int' argument
   printf("%.*d");
           ~~^~

GCC detects that there are two errors, while Clang only detects one. On the other hand, in this particular example GCC's location information is not as accurate as Clang's.2

No Pretty Printing of Expressions in Diagnostics

Since GCC 4.8 has caret diagnostics, it does not need to pretty print expressions:3

   1 void foo(char **p, char **q)
   2 {
   3   (p - q)();
   4   p();
   5 }

$ gcc-4.2 -fsyntax-only t.c

#‘exact_div_expr’ not supported by pp_c_expression#’
t.c:3:10: error: called object  is not a function
t.c:4:4: error: called object ‘p’ is not a function

$ clang -fsyntax-only t.c

t.c:3:10: error: called object type 'long' is not a function or function pointer
  (p - q)();
  ~~~~~~~^
t.c:4:4: error: called object type 'char **' is not a function or function pointer                                                                                                    
  p();
  ~^

$ gcc-4.8 -fsyntax-only t.c

t.c:3:10: error: called object is not a function or function pointer
   (p - q)();
          ^
t.c:4:4: error: called object ‘p’ is not a function or function pointer
   p();
    ^
t.c:1:17: note: declared here
 void foo(char **p, char **q)
                 ^

As shown above, GCC 4.8 not only avoids pretty-printing expressions, but also detects when the object is actually a variable declared elsewhere, and points to the declaration.

Another example:

   1   struct a {
   2     virtual int bar();
   3   };
   4   
   5   struct foo : public virtual a {
   6   };
   7   
   8   void test(foo *P) {
   9     return P->bar() + *P;
  10   }

$ gcc-4.2 t.cc

  t.cc: In function 'void test(foo*)':
  t.cc:9: error: no match for 'operator+' in '(((a*)P) + (*(long int*)(P->foo::<anonymous>.a::_vptr$a + -0x00000000000000020)))->a::bar() + * P'
  t.cc:9: error: return-statement with a value, in function returning 'void'

$ gcc-4.8 t.cc

vtable.cc:9:22: error: no match for ‘operator+’ (operand types are ‘int’ and ‘foo’)
   return P->bar() + *P;
                   ^
vtable.cc:9:22: warning: return-statement with a value, in function returning 'void' [-fpermissive]
   return P->bar() + *P;
                      ^

$ clang t.cc

  t.cc:9:18: error: invalid operands to binary expression ('int' and 'foo')
    return P->bar() + *P;
           ~~~~~~~~ ^ ~~

Typedef Preservation and Selective Unwrapping

GCC has behaved similarly to Clang since at least GCC 4.4, and GCC 4.8 adds also the caret:

   1 typedef float __m128 __attribute__ ((vector_size (32)));
   2 void f()
   3 {
   4   __m128 myvec[2];
   5   int const *P;
   6   myvec[1]/P;
   7 }

$ gcc-4.8 -fsyntax-only t.c 

t.c: In function 'f':
t.c:6:11: error: invalid operands to binary / (have '__m128' and 'const int *')
   myvec[1]/P;
           ^

$ clang-3.1 -fsyntax-only t.c

t.c:6:11: error: can't convert between vector values of different size ('__m128' and 'int const *')
  myvec[1]/P;
  ~~~~~~~~^~

For this example gcc hasn't improved, but g++ shows an "aka" like Clang:

   1 typedef int pid_t;
   2 void f() {
   3   pid_t myvar;
   4   myvar = myvar.x;
   5 }

$ g++-4.8 -fsyntax-only t.c

t.c: In function ‘void f()’:
t.c:4:17: error: request for member ‘x’ in ‘myvar’, which is of non-class type ‘pid_t {aka int}’
   myvar = myvar.x;
                 ^

$ clang-3.1 -fsyntax-only t.c

t.c:4:17: error: member reference base type 'pid_t' (aka 'int') is not a structure or union
  myvar = myvar.x;
          ~~~~~ ^

Automatic Macro Expansion

   1 #define MYMAX(A,B)    __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
   2 struct mystruct { };
   3 void f() {
   4   int X;
   5   float F;
   6   struct mystruct P;
   7   X = MYMAX(P, F);
   8 }

GCC 4.8 tracks macro expansions by default and uses it to print macro expansions with caret:

$ gcc-4.8 -fsyntax-only t.c

t.c:1:94: error: invalid operands to binary < (have ‘struct mystruct’ and ‘float’)
 #define MYMAX(A,B)    __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
                                                                                              ^
t.c:7:7: note: in expansion of macro 'MYMAX'
   X = MYMAX(P, F);
       ^

$ clang-3.1 -fsyntax-only t.c

t.c:7:7: error: invalid operands to binary expression ('typeof (P)' (aka 'struct mystruct') and 'typeof (F)' (aka 'float'))
  X = MYMAX(P, F);
      ^~~~~~~~~~~
t.c:1:94: note: expanded from macro 'MYMAX'
#define MYMAX(A,B)    __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
                                                                                         ~~~ ^ ~~~
t.c:7:5: error: assigning to 'int' from incompatible type 'void'
  X = MYMAX(P, F);
    ^ ~~~~~~~~~~~

Quality of Implementation and Attention to Detail

GCC's error recovery is much better now:

   1 foo_t *P = 0;

$ gcc-4.8 t.c

t.c:1:1: error: unknown type name 'foo_t'
 foo_t *P = 0;
 ^

$ clang-3.1 t.c

t.c:1:1: error: unknown type name 'foo_t'
foo_t *P = 0;
^

GCC and G++ handle missing semicolons after structs and classes well now:

   1 template<class T>
   2 class a {}
   3 class temp {};
   4 a<temp> b;
   5 struct b {
   6 }

$ g++-4.8 t.cc

t.cc:2:10: error: expected ';' after class definition
 class a {}
          ^
t.cc:6:1: error: expected ';' after struct definition
 }
 ^

$ clang++-3.1 t.cc

t.cc:2:11: error: expected ';' after class
class a {}
         ^
         ;
tsc.cc:6:2: error: expected ';' after struct
}
 ^
 ;


The following examples are not on the original Clang page, but show some cases where G++ produces better diagnostics.

Recursive template instantiations

GCC can detect a recursive template instantiation and avoids giving a cascade of diagnostics:

   1 template <int N> struct X {
   2     static const int value = X<N-1>::value;
   3 };
   4 template struct X<1000>;

 $ clang-3.1 -fsyntax-only recursive.C

recursive.C:2:28: fatal error: recursive template instantiation exceeded maximum depth of 1024
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<-24>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<-23>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<-22>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<-21>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<-20>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: (skipping 1015 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
recursive.C:2:28: note: in instantiation of template class 'X<996>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<997>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<998>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:2:28: note: in instantiation of template class 'X<999>' requested here
  static const int value = X<N-1>::value;
                           ^
recursive.C:4:17: note: in instantiation of template class 'X<1000>' requested here
template struct X<1000>;
                ^
recursive.C:2:28: note: use -ftemplate-depth-N to increase recursive template instantiation depth
  static const int value = X<N-1>::value;
                           ^
1 error generated.

$ gcc-4.8 -fsyntax-only recursive.C

recursive.C:2:36: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating ‘struct X<100>’
   static const int value = X<N-1>::value;
                                    ^
recursive.C:2:36:   recursively required from ‘const int X<999>::value’
recursive.C:2:36:   required from ‘const int X<1000>::value’
recursive.C:4:17:   required from here

recursive.C:2:36: error: incomplete type ‘X<100>’ used in nested name specifier
   static const int value = X<N-1>::value;
                                    ^

C++ templates errors

This invalid code should say typename T::type in line 1:

   1 template<class T> void f(T::type) { }
   2 struct A { };
   3 void g()
   4 {
   5     A a;
   6     f<A>(a);
   7 }

Neither GCC nor Clang gives any clue what the real problem is:

$ g++-4.8 missing-typename.cc

missing-typename.cc:1:33: error: variable or field 'f' declared void
 template<class T> void f(T::type) { }
                                 ^
missing-typename.cc: In function 'void g()':
missing-typename.cc:6:4: error: 'f' was not declared in this scope
    f<A>(a);
    ^
missing-typename.cc:6:7: error: expected primary-expression before '>' token
    f<A>(a);
       ^

$ clang++-3.1 -fsyntax-only t.cc

t.cc:1:24: error: variable 'f' declared as a template
template<class T> void f(T::type) { }
~~~~~~~~~~~~~~~~~      ^
t.cc:1:34: error: expected ';' at end of declaration
template<class T> void f(T::type) { }
                                 ^
                                 ;
t.cc:1:35: error: expected unqualified-id
template<class T> void f(T::type) { }
                                  ^
t.cc:6:5: error: use of undeclared identifier 'f'
    f<A>(a);
    ^
t.cc:6:7: error: 'A' does not refer to a value
    f<A>(a);
      ^
t.cc:2:8: note: declared here
struct A { };
       ^

Clang's carets and range highlighting don't help identify the problem, and the poor error recovery that means "A" is reported as undeclared doesn't help either.

If you add the missing typename then G++ does better:

   1 template<class T> void f(typename T::type) { }
   2 struct A { };
   3 void g()
   4 {
   5    A a;
   6    f<A>(a);
   7 }

$ g++-4.7 deduce.cc 

deduce.cc: In function 'void g()':
deduce.cc:6:10: error: no matching function for call to 'f(A&)'
deduce.cc:6:10: note: candidate is:
deduce.cc:1:24: note: template<class T> void f(typename T::type)
deduce.cc:1:24: note:   template argument deduction/substitution failed:
deduce.cc: In substitution of 'template<class T> void f(typename T::type) [with T = A]':
deduce.cc:6:10:   required from here
deduce.cc:1:24: error: no type named 'type' in 'struct A'

$ g++-4.8 deduce.cc

deduce.cc: In function 'void g()':
deduce.cc:6:10: error: no matching function for call to 'f(A&)'
    f<A>(a);
          ^
deduce.cc:6:10: note: candidate is:
    f<A>(a);
          ^
deduce.cc:1:24: note: template<class T> void f(typename T::type)
 template<class T> void f(typename T::type) { }
                        ^
deduce.cc:1:24: note:   template argument deduction/substitution failed:
 template<class T> void f(typename T::type) { }
                        ^
deduce.cc: In substitution of 'template<class T> void f(typename T::type) [with T = A]':
deduce.cc:6:10:   required from here
deduce.cc:1:24: error: no type named 'type' in 'struct A'
 template<class T> void f(typename T::type) { }
                        ^

$ clang++-3.1 -fsyntax-only t.cc

t.cc:6:5: error: no matching function for call to 'f'
    f<A>(a);
    ^~~~
t.cc:1:24: note: candidate template ignored: substitution failure [with T = A]
template<class T> void f(typename T::type) { }
                       ^

GCC correctly identifies the precise reason why the template argument deduction fails as "no type named 'type' in 'struct A'", while Clang only says "substitution failure".

TODO

--

  1. TODO: Both GCC (since GCC 4.9) and Clang support color diagnostics, although this page does not (yet) show diagnostics in color. GCC does not yet highlight ranges. (1)

  2. PR52952 (2)

  3. If it does, it is a bug, please report it (3)

None: ClangDiagnosticsComparison (last edited 2013-09-06 10:32:27 by tschwinge)