1

I have the following example program that I want to debug:

#include <iostream>
#include <assert.h>
using namespace std;

int f(int x) {

    assert(false); // something bad happens

    return 2 * x;
}

int main() {

    int a;

    for (int i = 0; i < 5; i++) {
        a++; // a is uninitialized
        cout << a << endl;
    }

    cout << f(1) << endl; 

}

This code has two problems:

  1. the variable a within the loop is uninitialized
  2. the function f causes the program to crash

To detect these problems, I compile with g++ -Wall -Og -g source.cpp and I get the following warning:

source.cpp: In function ‘int main()’:
source.cpp:17:10: warning: ‘a’ may be used uninitialized in this function [-Wmaybe-uninitialized]
   17 |         a++; // a is uninitialized
      |

As this question shows, the flag -Og (or any other optimization flag) is necessary for getting this warning.

When I debug the resulting executable with gdb, it crashes (because of the assert statement) and the backtrace looks something like this:

[...]
#4  0x000055555555528f in f (x=<optimized out>) at source.cpp:7
#5  0x0000555555555322 in main () at source.cpp:21

As you can see, the variable x has been optimized out. This happened because of the -Og flag, as discussed in this question.

Obviously, I don't want this for debugging purposes. But when I remove the -Og flag, the previously mentioned warning won't show up anymore. I now want to find a way to get this warning without having optimized out variables. Is this possible with g++?

I am using g++ version 10.2.0 and gdb version 9.2 on Ubuntu 20.10.

Tc14
  • 109
  • 1
  • 7
  • Hmm, I don't use `-Og` [here](http://coliru.stacked-crooked.com/a/2bef046ffe088387), but I still get the warning. – NathanOliver Apr 30 '21 at 18:53
  • @NathanOliver Yes, I screwed up and used the wrong code. I edited my post. Now it should be the correct one. – Tc14 Apr 30 '21 at 19:11
  • 1
    Because detecting uninitialized variables needs to do program flow analysis (look at every possible code path from the declaration to 1st use), which is expensive. Many compilers defer this detection to the optimisation pass which does this type of analysis anyway for optimisation reasons. – Richard Critten Apr 30 '21 at 19:23
  • 1
    this is quite related: https://gcc.gnu.org/wiki/Better_Uninitialized_Warnings – 463035818_is_not_an_ai Apr 30 '21 at 19:30
  • @Richard Critten So you are saying that it is not possible to do the thing I want to do? – Tc14 Apr 30 '21 at 19:33
  • 1
    @largest_prime_is_463035818 This looks interesting. A `-Wuninitialized=verbose` flag would be quite useful. – Tc14 Apr 30 '21 at 19:37

1 Answers1

1

Is this possible with g++?

No. Unfortunately, GCC performs the "is value used uninitialized?" analysis in one of its optimization passes, and disabling optimization also disables that pass.

Contrast this with Clang/LLVM, which had an explicit goal of not making warnings dependent on optimization.

clang++ -Wall -Wextra -c t.cc  # no optimization

t.cc:17:9: warning: variable 'a' is uninitialized when used here [-Wuninitialized]
        a++; // a is uninitialized
        ^
t.cc:14:10: note: initialize the variable 'a' to silence this warning
    int a;
         ^
          = 0
1 warning generated.

As you can see, the variable x has been optimized out.

That is a deficiency in either the g++ (if it didn't emit location info for the parameter), or in gdb (if it didn't decode the info which g++ emitted).

In theory -Og should not degrade your debugging experience, though it obviously did here.

Looking at the readelf -wi for g++-compiled code, I see:

<1><285d>: Abbrev Number: 114 (DW_TAG_subprogram)
    <285e>   DW_AT_external    : 1
    <285e>   DW_AT_name        : f
    <2860>   DW_AT_decl_file   : 1
    <2861>   DW_AT_decl_line   : 5
    <2862>   DW_AT_decl_column : 5
    <2863>   DW_AT_linkage_name: (indirect string, offset: 0x169d): _Z1fi
    <2867>   DW_AT_type        : <0xe4f>
    <286b>   DW_AT_low_pc      : 0x3d
    <2873>   DW_AT_high_pc     : 0x23
    <287b>   DW_AT_frame_base  : 1 byte block: 9c       (DW_OP_call_frame_cfa)
    <287d>   DW_AT_GNU_all_call_sites: 1
    <287d>   DW_AT_sibling     : <0x28e1>
 <2><2881>: Abbrev Number: 115 (DW_TAG_formal_parameter)
    <2882>   DW_AT_name        : x
    <2884>   DW_AT_decl_file   : 1
    <2885>   DW_AT_decl_line   : 5
    <2886>   DW_AT_decl_column : 11
    <2887>   DW_AT_type        : <0xe4f>
    <288b>   DW_AT_location    : 0x2dc (location list)
    <288f>   DW_AT_GNU_locviews: 0x2d8
...

I don't see any definition of 0x2dc or 0x2d8, so it seems to me that this is a problem on the g++ side, but I am not entirely sure how to read location lists. It's possible that g++ does in fact emit required info, and GDB doesn't interpret it as it should.

P.S. lldb shows the same output as GDB:

a.out: t.cc:7: int f(int): Assertion `false' failed.
Process 974666 stopped
* thread #1, name = 'a.out', stop reason = hit program assert
    frame #4: 0x0000555555555205 a.out`f(x=<unavailable>) at t.cc:7:5
   4
   5    int f(int x) {
   6
-> 7        assert(false); // something bad happens
   8
   9        return 2 * x;
   10   }

That is an extra indication that the problem is probably on the g++ side.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362