Why is my C++ program running fine with the debugger but not without it?

103 Views Asked by At

I am trying to recreate the vector class with its basic functions, but my program keeps exiting halfway through the operations. When I run the debugger it fully executes just fine. Here is what my vector class looks like:

template<class T>
class Vector {
    T* _array;
    size_t _size;
    size_t _capacity;


public:
    Vector() : _size(0), _capacity(8), _array(new T[_capacity]) { }
    ~Vector() { delete[] _array; }

    void _grow() {
        T* tmp = _array;
        size_t old_cap = _capacity;
        _capacity *= 2;
        _array = new T[_capacity];
        for (size_t i = 0; i < old_cap; i++) {
            _array[i] = tmp[i];
        }
        delete[] tmp;
    }

    void push_back(T const& item) {
        if (_size == _capacity) { _grow(); }
        _array[_size++] = item;
    }

    void pop_back() {
        _size--;
    }

    void insert(T item, int position) {
        // implement insert here
        for (int i = position; i < _size-1; i++) {
            _array[i+1] = _array[i];
        }
        _size++;
        if (_size == _capacity) {
            _grow();
        }
        _array[position] = item;
    }

    void remove(int position) {
        // implement remove here
        for (int i = position; i < _size-1; i++) {
            _array[i] = _array[i+1];
        }
        _size--;
    }

    T& operator[](int index) {
        // implement operator[] here
        return _array[index];
    }

    size_t size() const {
        return _size;
    }

    void clear() {
        // implement clear here
        _size = 0;
    }
};

And here is the code actually initializes the class object and calls its member functions:

template<class T>
void noisy_push_back(Vector<T>& vector, T item) {
    vector.push_back(item);
    std::cout << "vector.push_back(" << item << ')' << std::endl;
}

template<class T>
void print_vector(Vector<T>& vector) {
    std::cout << "vector =";
    for (int i = 0; i < vector.size(); i++) {
        std::cout << ' ' << vector[i];
    }
}

void test1() {
    std::cout << "--- Test 1 output ---\n" << std::endl;

    Vector<std::string> vector;

    noisy_push_back<std::string>(vector, "wonder");
    noisy_push_back<std::string>(vector, "yak");
    print_vector(vector);

    std::cout << std::endl;
    noisy_push_back<std::string>(vector, "bookshelf");
    noisy_push_back<std::string>(vector, "euphoria");
    print_vector(vector);
}

When I run the program normally, it adds "wonder" and "yak" and then prints the vector just fine. When it tries to push_back("bookshelf") it exits with Process finished with exit code -1073741819 (0xC0000005)

Any ideas?

I tried printing out the vector size right before it exits and it was as expected: size() = 2
Also when I try to run the debugger everything looks normal and the program runs just fine. I am using CLion by the way.

2

There are 2 best solutions below

1
ChrisMM On BEST ANSWER

Your compiler should warn you about this; if it does not, turn up your warnings!

 Vector() : _size(0), _capacity(8), _array(new T[_capacity])

You are trying to initialize your _array last, however, since your variables are declared in the order of:

    T* _array;
    size_t _size;
    size_t _capacity;

Then _array will be initialized first. _capacity will be indeterminate at the time of initialization, thus you have Undefined Behaviour.

3
Marek R On

You should learn to use tools. First enable warnings. Then use address sanitizer to find memory errors: https://godbolt.org/z/r5EjWoce8

=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000018 at pc 0x000000406672 bp 0x7ffc3d36ce50 sp 0x7ffc3d36ce48
READ of size 8 at 0x602000000018 thread T0
    #0 0x406671 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_data() const /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:234
    #1 0x406671 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_is_local() const /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:275
    #2 0x406671 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::capacity() const /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:1170
    #3 0x406671 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_assign(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.tcc:279
    #4 0x4087f1 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::assign(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:1607
    #5 0x4087f1 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator=(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:813
    #6 0x4087f1 in Vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::push_back(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /app/example.cpp:27
    #7 0x4087f1 in void noisy_push_back<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(Vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) /app/example.cpp:71
    #8 0x403304 in test1() /app/example.cpp:88
    #9 0x402328 in main /app/example.cpp:100
    #10 0x7f2a53229d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: c289da5071a3399de893d2af81d6a30c62646e1e)
    #11 0x7f2a53229e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: c289da5071a3399de893d2af81d6a30c62646e1e)
    #12 0x402384 in _start (/app/output.s+0x402384) (BuildId: d659454d1d76514eedee496e191fc29a836621ba)

0x602000000018 is located 0 bytes after 8-byte region [0x602000000010,0x602000000018)
allocated by thread T0 here:
    #0 0x7f2a53ed1ce8 in operator new[](unsigned long) (/opt/compiler-explorer/gcc-13.1.0/lib64/libasan.so.8+0xdcce8) (BuildId: 4508ebe68c8328f99c6a77c3715bff492eb1cfd5)
    #1 0x402f9e in Vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::Vector() /app/example.cpp:11
    #2 0x402f9e in test1() /app/example.cpp:86

SUMMARY: AddressSanitizer: heap-buffer-overflow /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:234 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_data() const
Shadow bytes around the buggy address:
  0x601ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x601ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x601ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x601fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x601fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x602000000000: fa fa 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==1==ABORTING

Note that stack frames 6-9 shows where problem is.

If you read compiler warning and address sanitizer report you should understand that reordering fields should resolve your problem: https://godbolt.org/z/WEovKGn65