0

While working on a submission, I found behaviour that I don't understand. I have three ways of populating my matrix from the input. One of them works, one of them compiles and runs but produces a slightly wrong result, and one of them crashes with a free(): invalid pointer.

For simplicity, I leave the part about a wrong result out of this question and reduce the code to just reading input. Let's ignore the output for a moment and just consider the error messages for two test cases:

root@41d06f89ab19:/code# gp so1.cpp && ./so1.exe <evenmat/test1.in > so1.txt
root@41d06f89ab19:/code# gp so2.cpp && ./so2.exe <evenmat/test1.in > so2.txt
Segmentation fault (core dumped)
root@41d06f89ab19:/code# gp so3.cpp && ./so3.exe <evenmat/test1.in > so3.txt


root@41d06f89ab19:/code# gp so1.cpp && ./so1.exe <evenmat/test2.in > so1_2.txt
free(): invalid pointer
Aborted (core dumped)
root@41d06f89ab19:/code# gp so2.cpp && ./so2.exe <evenmat/test2.in > so2_2.txt
Segmentation fault (core dumped)
root@41d06f89ab19:/code# gp so3.cpp && ./so3.exe <evenmat/test2.in > so3_2.txt

Clearly, the behaviour of the three code snippets is not equivalent. Consider the three files below. How do their semantics differ? Why are they not all equivalent?

// File so1.cpp - 
#include <iostream>
#include <vector>
#include <iterator>
#include <iomanip>
#include <cassert>
#include <algorithm>
#include <functional>
typedef std::vector<std::vector<int>> VVI;
typedef unsigned int uint;
int main () {
    std::ios_base::sync_with_stdio(false);

    int t; std::cin >> t;

    for (int testcase = 0; testcase < t; testcase ++){
        // num matrix rows
        unsigned int n; std::cin >> n;

        // populate matrix
        VVI matrix; matrix.reserve(n);
        //VVI matrix(n, std::vector<int>(n));
        for(unsigned int row=0; row<n; row++){
            for(unsigned int col=0; col<n; col++){
                if(col==0){
                    // initialize row vector
                    //std::vector<int> myvec (n);
                    //matrix[row] = myvec;
                    matrix[row] = std::vector<int>(n);      // free(): invalid pointer happens here
                }
                int el; std::cin >> el;
                matrix[row][col]=el;
            }
        }

        for (unsigned int i = 0; i < n; i++){
            for (unsigned int j = 0; j < n; j++){
                std::cout << matrix[i][j] << ' ';
            }
            std::cout << '\n';
        }
        std::cout << "\ntestcase " << testcase << ':' << std::endl;
    }
}

// file so2.cpp
#include <iostream>
#include <vector>
#include <iterator>
#include <iomanip>
#include <cassert>
#include <algorithm>
#include <functional>
typedef std::vector<std::vector<int>> VVI;
typedef unsigned int uint;
int main () {
    std::ios_base::sync_with_stdio(false);

    int t; std::cin >> t;

    for (int testcase = 0; testcase < t; testcase ++){
        // num matrix rows
        unsigned int n; std::cin >> n;

        // populate matrix
        VVI matrix; matrix.reserve(n);
        //VVI matrix(n, std::vector<int>(n));
        for(unsigned int row=0; row<n; row++){
            for(unsigned int col=0; col<n; col++){
                if(col==0){
                    // initialize row vector
                    std::vector<int> myvec (n);
                    matrix[row] = myvec;        // segfault on this line
                    //matrix[row] = std::vector<int>(n);
                }
                int el; std::cin >> el;
                matrix[row][col]=el;
            }
        }
    }
}
// file so3.cpp
#include <iostream>
#include <vector>
#include <iterator>
#include <iomanip>
#include <cassert>
#include <algorithm>
#include <functional>
typedef std::vector<std::vector<int>> VVI;
typedef unsigned int uint;
int main () {
    std::ios_base::sync_with_stdio(false);

    int t; std::cin >> t;

    for (int testcase = 0; testcase < t; testcase ++){
        // num matrix rows
        unsigned int n; std::cin >> n;

        // populate matrix
        //VVI matrix; matrix.reserve(n);
        VVI matrix(n, std::vector<int>(n));
        for(unsigned int row=0; row<n; row++){
            for(unsigned int col=0; col<n; col++){
                /*if(col==0){
                    // initialize row vector
                    //std::vector<int> myvec (n);
                    //matrix[row] = myvec;
                    matrix[row] = std::vector<int>(n);
                }*/
                int el; std::cin >> el;
                matrix[row][col]=el;
            }
        }


        for (unsigned int i = 0; i < n; i++){
            for (unsigned int j = 0; j < n; j++){
                std::cout << matrix[i][j] << ' ';
            }
            std::cout << '\n';
        }
        std::cout << "\ntestcase " << testcase << ':' << std::endl;
    }
}

I'm hoping this is an easy question for c++ experts out there. Because if it is not, I'm really not sure how well I can minimalize the test cases.

The lines where free(): invalid pointer and the segfault happen are indicated in the code snippets. Perhaps of note is that the stack in visual studio code shows that for so1.cpp the free(): invalid pointer with the second test input file happens within the following part of stl_vector.h:

#if __cplusplus >= 201103L
      /**
       *  @brief  %Vector move assignment operator.
       *  @param  __x  A %vector of identical element and allocator types.
       *
       *  The contents of @a __x are moved into this %vector (without copying,
       *  if the allocators permit it).
       *  Afterwards @a __x is a valid, but unspecified %vector.
       *
       *  Whether the allocator is moved depends on the allocator traits.
       */
      vector&
      operator=(vector&& __x) noexcept(_Alloc_traits::_S_nothrow_move())
      {
    constexpr bool __move_storage =
      _Alloc_traits::_S_propagate_on_move_assign()
      || _Alloc_traits::_S_always_equal();
    _M_move_assign(std::move(__x), __bool_constant<__move_storage>());
    return *this;
      }

call stack for test2 for so1.cpp

For reproducibility:

lucidbrot
  • 5,378
  • 3
  • 39
  • 68
  • 2
    TL;DR. This: `VVI matrix; matrix.reserve(n);` -- The `reserve` does not create elements within a vector. – PaulMcKenzie Sep 22 '20 at 07:02
  • @PaulMcKenzie I was aware of that when writing this code, and that's why I'm creating a new vector to assign to `matrix[row]` the first time it would be written to. Or am I not doing what I think I am doing? – lucidbrot Sep 22 '20 at 07:06
  • Oh, wait. You mean the *outer* vector. – lucidbrot Sep 22 '20 at 07:06
  • There is no `matrix[row]`. If you want proof, change that line to `matrix.at(row) = std::vector(n);`. – PaulMcKenzie Sep 22 '20 at 07:06
  • `matrix[row]` attempts to acces an element that is not present in `matrix` – 463035818_is_not_an_ai Sep 22 '20 at 07:06
  • @PaulMcKenzie okay so that means that `so1.cpp` and `so2.cpp` are both simply wrong. Can the question be answered why they behave differently, though? or is that just "well, it's undefined behaviour"? – lucidbrot Sep 22 '20 at 07:08
  • 1
    Yes it's undefined behaviour, the different crashes are probably just due to using the move or copy assignment operators – Alan Birtles Sep 22 '20 at 07:13
  • 1
    @lucidbrot Undefined behavior is undefined. There is no point trying to figure out what will happen. If I run your code under Visual C++ debug mode, I probably will get a big `assert` box as soon as you tried to access `matrix[row]`, while with another compiler or with different compiler options, differing results. – PaulMcKenzie Sep 22 '20 at 07:15
  • 1
    strictly speaking there are two possible points of view. You could try to understand why it segfaults in one case and not in the other, but you would need to study the implementation you are using and the compilers output. You would only gain insight for this specific compiler targeting this specific architecture. The other view is: Correct C++ compiles everywhere, we need not care about implementation details, your code isn't correct C++ code – 463035818_is_not_an_ai Sep 22 '20 at 07:46

1 Answers1

1

The problem is not how you create the row the problem is that with matrix[row] you acces an element that does not exist.

Here

    // populate matrix
    VVI matrix; matrix.reserve(n);

You are not populating the matrix. reserve merely allocates space for elements. It does not add elements or change the vectors size. matrix has zero element and that does not change throughout your first code. Accesing the vector out-of-bounds in undefined behavior.

If you want to have matrix with n rows you need to initialize it with n elements:

VVI matrix{n};

or

VVI matrix;
matrix.resize(n);

Or... In your second code you correctly initialize the matrix with n elements that are std::vector<int>s of size n:

VVI matrix(n, std::vector<int>(n));
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you. Such an obvious mistake, yet I spent an hour on it with some friends.. So that means the two bad code snippets would be equivalent when `matrix` already exists as a vector of the correct size? What I'm asking now is: Is the different behaviour just due to undefined behaviour, or is there also a difference in meaning? – lucidbrot Sep 22 '20 at 07:13
  • 1
    @lucidbrot to be honest I didnt understand the question fully. You are refereing to the difference between `std::vector myvec (n); matrix[row] = myvec;` and `matrix[row] = std::vector(n);` ? They should be the same, but in the presence of UB everything is the same and not at the same time ;). Tomorrow you might get a segfault for the other case or no segfault at all. – 463035818_is_not_an_ai Sep 22 '20 at 07:24