2

I have this simple C++ program:

#include <string>
#include <iostream>
using namespace std;

int main()
{
    for (int i = 0; i < 5; i++)
    {
        string a = "apple";
        cout << a << endl;
        string b = "banana";
        string c = "cherry";
    }

    return 0;
}

When stepping through line-by-line, every time it reaches "c", it goes backwards to "b" then "a" (it skips the "cout" line). Once it reaches the "for" line it will go forward through the loop, but when it reaches "c" again it will go backwards again.

I'm compiling with -g -Og -fno-inline flags. I've tried changing -Og to -O0 and changing -g to -g3 and -ggdb. None of these things helped.

However, if I change the strings to char*, the problem goes away.

My OS is Linux Mint.

Example gdb output:

Reading symbols from debug-test...done.
(gdb) b main
Breakpoint 1 at 0xb98: file debug-test.cc, line 6.
(gdb) r
Starting program: /home/brian/workspace/debug-test/debug-test 

Breakpoint 1, main () at debug-test.cc:6
6   {
(gdb) n
7       for (int i = 0; i < 5; i++)
(gdb) n
9           string a = "apple";
(gdb) n
10          cout << a << endl;
(gdb) n
apple
11          string b = "banana";
(gdb) n
12          string c = "cherry";
(gdb) n
11          string b = "banana";
(gdb) n
9           string a = "apple";
(gdb) n
7       for (int i = 0; i < 5; i++)
(gdb) n
9           string a = "apple";
(gdb) n
10          cout << a << endl;
(gdb) n
apple
11          string b = "banana";
(gdb) n
12          string c = "cherry";
(gdb) 
powerb
  • 21
  • 3

3 Answers3

2

This will always happen for local objects that have destructors. If you "step into" at one of these "go backwards" ponts, you will enter the code of the destructor of the corresponding object.

It has nothing to do with loops specifically. It happens in any kind of block.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

This is already reported gcc bug in debug info generation:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88742.

As a workaround you can use clang instead of gcc, clang generates proper debug info without jumps back.

ks1322
  • 33,961
  • 14
  • 109
  • 164
1

In addition to what n.'pronouns'm already said, you can always use a tool like godbolt.org to see how code is translated to machine code. And the parts the belong together are highlighted, if you hover them.

For the part of you code in your loop:

string a = "apple";  // 1
cout << a << endl;   // 2
string b = "banana"; // 3
string c = "cherry"; // 4

It will - for x86-64 gcc 10.1 - translate to:

// 1 ===============================================================================
        lea     rax, [rbp-23]
        mov     rdi, rax
        call    std::allocator<char>::allocator() [complete object constructor]
        lea     rdx, [rbp-23]
        lea     rax, [rbp-128]
        mov     esi, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
        lea     rax, [rbp-23]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
// 2 ===============================================================================
        lea     rax, [rbp-128]
        mov     rsi, rax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
// 3 ===============================================================================
        lea     rax, [rbp-22]
        mov     rdi, rax
        call    std::allocator<char>::allocator() [complete object constructor]
        lea     rdx, [rbp-22]
        lea     rax, [rbp-96]
        mov     esi, OFFSET FLAT:.LC1
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
        lea     rax, [rbp-22]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
// 4 ===============================================================================
        lea     rax, [rbp-21]
        mov     rdi, rax
        call    std::allocator<char>::allocator() [complete object constructor]
        lea     rdx, [rbp-21]
        lea     rax, [rbp-64]
        mov     esi, OFFSET FLAT:.LC2
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
        lea     rax, [rbp-21]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]

// the object constructed in the loop are now destructed again in reverse order

// 4 ===============================================================================
        lea     rax, [rbp-64]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
// 3 ===============================================================================
        lea     rax, [rbp-96]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
// 1 ===============================================================================
        lea     rax, [rbp-128]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]

So as you can see there are two blocks at the end of the machine code that are for the destruction of the strings at 3 and 1, as the only line to which gdb could refer to is the place where that object was constructed it will jump back to that line.

However, if I change the strings to char*, the problem goes away.

If you have:

const char *a = "apple";
const char *b = "banana";
const char *c = "cherry";

Then there will be nothing to be destructed at the and of the block of the for, because that does not create an instance of an object. apple, banana and cherry in that case will normally be located in the distinct data section in memory that will exist until the application is exited.

t.niese
  • 39,256
  • 9
  • 74
  • 101