views:

261

answers:

1

I ran in to a (in my eyes) very strange issue with gdb today, while trying to write a more C++-ish version of the reverse() method of my string class (all for learning, of course). Despite having a proper default constructor that initializes all member variables to 0, they start out as gibberish inside the member reverse() - according to gdb (everything except debugging actually works perfectly) - but not in a bare-bones program that just creates an empty string. Could this have something to do with the fact that reverse() is a member function that creates an instance of its own class? If not, why would it not happen in a bare-bones program?

BTW, before I mention any code: I compile thusly:

g++ -c -o tests.o tests.cpp -Wall -Werror -DDEBUG=0 -O0 -ggdb3
g++ -c -o string.o string.cpp -Wall -Werror -DDEBUG=0 -O0 -ggdb3
g++ -o tests tests.o string.o -Wall -Werror -DDEBUG=0 -O0 -ggdb3

Code:

string::string() : buf(NULL), _length(0), _size(0) {
    init();
}

/* This is redundant in this case (right?), but the function is used elsewhere,
   and I added the above initialization list while debugging this. */
void string::init() {
    this->buf = NULL;
    this->_length = 0;
    this->_size = 0;
}

string string::reverse(void) const {
    string rev;
    rev.alloc(this->_length + 1);

    for (size_t i=0; i<this->_length; i++) {
    ...

Here's what I get from running the above through gdb 7.0 (on Linux):
(Sorry for the horizontal scroll, but it doesn't matter since you can see all you need anyhow.)

Breakpoint 1, exscape::string::reverse (this=0x7fffffffd580) at string.cpp:368
368                     string rev;
(gdb) next
378                     rev.alloc(this->_length + 1); <<<< not yet executed when we print below!
(gdb) p rev
$1 = {buf = 0x7fffffffd560 "", _length = 140737488344448, _size = 140737488344128}
(gdb) n
380                     for (size_t i=0; i<this->_length; i++) {
(gdb) 
381                             rev.buf[this->_length-i-1] = this->buf[i];
380                     for (size_t i=0; i<this->_length; i++) {
(gdb) p rev
$2 = {buf = 0x7fffffffd560 "P\321`", _length = 140737488344448, _size = 140737488344128}
(gdb) n
381                             rev.buf[this->_length-i-1] = this->buf[i];
(gdb) 
380                     for (size_t i=0; i<this->_length; i++) {
...
384                     rev._length = this->_length;
(gdb) 
386             }
(gdb) p rev
$3 = {buf = 0x7fffffffd560 "P\321`", _length = 140737488344448, _size = 140737488344128}
(gdb) next
main () at tests.cpp:72
(gdb) p r2 <<<< r2 is the name of the variable returned by reverse() of course
$4 = {buf = 0x60d150 "ABCDEF", _length = 6, _size = 7}

Why do the member variables appear to end up with gibberish values in gdb? (The gibberish values are always very close this, by the way! In this running, this == 0x7fffffffd580 == 140737488344448, the same value as _length). The function works perfectly, valgrind never complains, and all is well... until I try to rewrite the method and can't debug it properly, that is.

Any advice?

Update: A sample program that calls the function:

#include <iostream>
#include "string.hpp"
int main() {
    exscape::string s("Hello, world!");
    exscape::string s2; // Use the default constructor
    s2 = s.reverse();
    std::cout << "Reversed: " << s2 << std::endl;

    return 0;
}

(gdb) break exscape::string::reverse
Breakpoint 1 at 0x402ed6: file string.cpp, line 368.
(gdb) run
Starting program: /home/serenity/programming/cpp/string/a.out 

Breakpoint 1, exscape::string::reverse (this=0x7fffffffdc80) at string.cpp:368
368                     string rev;
(gdb) n
378                     rev.alloc(this->_length + 1);
(gdb) p rev
$1 = {buf = 0x7fffffffdca0 "", _length = 140737488346240, _size = 140737488346208}
(gdb) finish
Run till exit from #0  exscape::string::reverse (this=0x7fffffffdc80) at string.cpp:378
0x000000000040163f in main () at bare.cpp:6
6               s2 = s.reverse();
Value returned is $2 = {buf = 0x607030 "!dlrow ,olleH", _length = 13, _size = 14}
(gdb) n
7               std::cout << "Reversed: " << s2 << std::endl;
(gdb) n
Reversed: !dlrow ,olleH

Update:

I copied the code to my Mac, and tried debugging it there - worked like a charm:

Breakpoint 1, exscape::string::reverse (this=0x7fff5fbff800) at string.cpp:368
368   string rev;
(gdb) n
378   rev.alloc(this->_length + 1);
(gdb) p rev
$1 = (exscape::string &) @0x7fff5fbff7c0: {
  buf = 0x0, 
  _length = 0, 
  _size = 0
}

That version of gdb is quite ancient, though - "GNU gdb 6.3.50-20050815 (Apple version gdb-1344)".
I tried downgrading the Linux gdb to 6.8 and 6.6 to no avail. -- I actually tried 6.3 too, doesn't work either (and on a totally unrelated note, it appears the tab completion has gradually been getting better over the years :-).

A: 

My guess is that the local variable 'rev' is not created by the compiler until it is referenced (a quite normal optimization) and as 'rev.alloc' is the first reference to the local it won't be created until that code is executed.

That is why the debugger shows uninitialized data before the call to 'rev.alloc'. I am sure if you switch to assembly stepping you will see that if you can interpret the assembly. Alternatively you could "use" 'rev' before calling 'rev.alloc' and in that case I am pretty sure that when you break at 'rev.alloc' you will see the initialized local.

lothar
Thanks for the answer. However, if that's the case... shouldn't the subsequent lines (the ones after the call to rev.alloc()) work? The value of "rev" is gibberish until the function has returned the variable, even after all the work is done and the data is in place in memory.
@lothar I don't think that's the reason. After all, he did compile with -O0 to ensure that isn't the case.
Victor T.