Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is GCC tricked into allowing undefined behavior simply by putting it in a loop?

The following is nonsensical yet compiles cleanly with g++ -Wall -Wextra -Werror -Winit-self (I tested GCC 4.7.2 and 4.9.0):

#include <iostream> #include <string>  int main() {   for (int ii = 0; ii < 1; ++ii)   {     const std::string& str = str; // !!     std::cout << str << std::endl;   } } 

The line marked !! results in undefined behavior, yet is not diagnosed by GCC. However, commenting out the for line makes GCC complain:

error: ‘str’ is used uninitialized in this function [-Werror=uninitialized] 

I would like to know: why is GCC so easily fooled here? When the code is not in a loop, GCC knows that it is wrong. But put the same code in a simple loop and GCC doesn't understand anymore. This bothers me because we rely quite a lot on the compiler to notify us when we make silly mistakes in C++, yet it fails for a seemingly trivial case.

Bonus trivia:

  • If you change std::string to int and turn on optimization, GCC will diagnose the error even with the loop.
  • If you build the broken code with -O3, GCC literally calls the ostream insert function with a null pointer for the string argument. If you thought you were safe from null references if you didn't do any unsafe casting, think again.

I have filed a GCC bug for this: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63203 - I'd still like to get a better understanding here of what went wrong and how it may impact the reliability of similar diagnostics.

like image 472
John Zwinck Avatar asked Sep 08 '14 05:09

John Zwinck


People also ask

Why does C++ allow undefined behavior?

Undefined behavior exists mainly to give the compiler freedom to optimize. One thing it allows the compiler to do, for example, is to operate under the assumption that certain things can't happen (without having to first prove that they can't happen, which would often be very difficult or impossible).

What causes undefined Behaviour in C?

So, in C/C++ programming, undefined behavior means when the program fails to compile, or it may execute incorrectly, either crashes or generates incorrect results, or when it may fortuitously do exactly what the programmer intended.

What is undefined behavior in programming?

In computer programming, undefined behaviour is defined as 'the result of compiling computer code which is not prescribed by the specs of the programming language in which it is written'.

Why does increment operation like a i i ++; result in undefined behavior?

Why doesn't this code: a[i] = i++; work? The subexpression i++ causes a side effect--it modifies i' s value--which leads to undefined behavior since i is also referenced elsewhere in the same expression.


1 Answers

I'd still like to get a better understanding here of what went wrong and how it may impact the reliability of similar diagnostics.

Unlike Clang, GCC doesn't have logic to detect self-initialized references, so getting a warning here relies on the code for detecting use of uninitialized variables, which is quite temperamental and unreliable (see Better Uninitialized Warnings for discussion).

With an int the compiler can figure out that you write an uninitialized int to the stream, but with a std::string there are apparently too many layers of abstraction between an expression of type std::string and getting the const char* it contains, and GCC fails to detect the problem.

e.g. GCC does give a warning for a simpler example with less code between the declaration and use of the variable, as long as you enable some optimization:

extern "C" int printf(const char*, ...);  struct string {   string() : data(99) { }   int data;   void print() const { printf("%d\n", data); } };  int main() {   for (int ii = 0; ii < 1; ++ii)   {     const string& str = str; // !!     str.print();   } }  d.cc: In function ‘int main()’: d.cc:6:43: warning: ‘str’ is used uninitialized in this function [-Wuninitialized]    void print() const { printf("%d\n", data); }                                            ^ d.cc:13:19: note: ‘str’ was declared here      const string& str = str; // !!                    ^ 

I suspect this kind of missing diagnostic is only likely to affect a handful of diagnostics which rely on heuristics to detect problems. These would be the ones that give a warning of the form "may be used uninitialized" or "may violate strict aliasing rules", and probably the "array subscript is above array bounds" warning. Those warnings are not 100% accurate and "complicated" logic like loops(!) can cause the compiler to give up trying to analyse the code and fail to give a diagnostic.

IMHO the solution would be to add checking for self-initialized references at the point of initialization, and not rely on detecting it is uninitialized later when it gets used.

like image 198
Jonathan Wakely Avatar answered Sep 23 '22 02:09

Jonathan Wakely