3

I`m writing a code for which I'm using a 3 dimensional boost multiarray to save coordinates. But I always get a segmentation fault at some point. How are boost multiarray sizes limited and how can I get around those limits?

Here is a simplified test code that reproduces the problem:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <string>
#include <algorithm>
#include <map>

#include <boost/multi_array.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include "Line.h"

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>

typedef struct {
    Eigen::Vector3d coords;
    int gpHostZone;
    int gpHostFace;
    int calculated;
} Vertex;


class LGR {
public:
    LGR (int i, int j, int k) :
        grid(boost::extents[i][j][k])
    {
    };

    std::string name;
    std::vector<int> hostZones;
    std::vector<int> refine;
    boost::multi_array<Vertex*, 3> grid;
    std::vector<double> data;
 };

 int main(void){
   LGR lgr(11,11,21);
   std::cout << lgr.grid.size();
   std::vector<LGR> v;
   std::vector<Vertex> vertexDB;
   for(int i = 0; i < 1; i++ ){

     for(int j = 0; j < lgr.grid.size(); j++ ){
       for(int k = 0; k < lgr.grid[0].size(); k++ ){
         for(int l = 0; l < lgr.grid[0][0].size(); l++ ){
           Vertex coord;
           coord.coords << i,j,k;
           coord.gpHostZone = 0;
           coord.gpHostFace = 0;
           coord.calculated = 0;
           vertexDB.push_back(coord);
           lgr.grid[j][k][l] = &(vertexDB.back());
         }
       }
     }

     for(int j = 0; j < lgr.grid.size(); j++ ){
       for(int k = 0; k < lgr.grid[0].size(); k++ ){
         for(int l = 0; l < lgr.grid[0][0].size(); l++ ){
           std::cout << "At ("<< i << ","<< j << ","<< k << "," << l << ")\n";
           std::cout << lgr.grid[j][k][l]->coords<<"\n\n";
         }
       }
     }
   }
   return 1;
 }

Please do not comment on the includes. I just copy and pasted from the actual code. Most of the are probably not needed here. The dimensions are from a real example, so I actually need those kind of dimensions (and probably more).

2
  • lgr.grid[j][k][l] = &(vertexDB.back()); -- It is not a good idea to store pointers to items in a vector. If the vector becomes resized iterators and pointers to items become invalidated. Commented Oct 5, 2016 at 13:16
  • Here is how your problem is reproduced godbolt.org/z/Koc7sYEMY Commented Dec 24, 2023 at 23:02

2 Answers 2

3

The following is a definite issue that leads to undefined behavior, and doesn't have anything to do with boost::multiarray.

These lines:

std::vector<Vertex> vertexDB;
//...
vertexDB.push_back(coord);
lgr.grid[j][k][l] = &(vertexDB.back());

resizes the vertexDB vector and then stores a pointer to the last item in the vector to lgr.grid[j][k][l]. The problem with this is that pointers and iterators to items in a vector may become invalidated due to the vector having to reallocate memory when resizing the vector.

This manifests itself later here, in the subsequent loop:

std::cout << lgr.grid[j][k][l]->coords<<"\n\n";

There is no guarantee that the address you assigned previously is valid.

A quick fix for this is to use a std::list<Vertex>, since adding items to a std::list does not invalidate iterators / pointers.

Sign up to request clarification or add additional context in comments.

1 Comment

OK, now i feel kinda stupid. After you explained it, it's an obvious problem. And using a List has worked fine for the test code(unfortunatly i can't test the real code, because somehow a class i need got totally destroyed and now i have to recreate it.....). Well whatever, it works. Thank you PaulMcKenzie...
0

The other solution correctly diagnoses the problem in your original code (https://godbolt.org/z/Koc7sYEMY).

One solution, albeit fragile, is to prevent push_back from invalidating your pointers stored in the array. Given the logic of your program, this should be:

   LGR lgr(11,11,21);
   std::cout << lgr.grid.size();
   std::vector<LGR> v;
   std::vector<Vertex> vertexDB;

   vertexDB.reserve(lgr.grid.num_elements());

   for(int i = 0; i < 1; i++ ){
...

https://godbolt.org/z/Yx5aYoTcG

This will make the population of vertexDB more efficient as well.

A more elegant (still imperfect) alternative is to save the pointer indirection and deal with an array_ref to the vector data directly (also needs reserve).

class LGR {
public:
    LGR (int i, int j, int k, Vertex* data) :
    //    grid(boost::extents[i][j][k]),
        grid_ref(data, boost::extents[i][j][k])
    {
    };

    std::string name;
    std::vector<int> hostZones;
    std::vector<int> refine;
    // boost::multi_array<Vertex*, 3> grid;
    boost::multi_array_ref<Vertex, 3> grid_ref;
    std::vector<double> data;
 };

 int main(void){
   std::vector<Vertex> vertexDB;

   vertexDB.reserve(11*11*21);

   LGR lgr(11,11,21, vertexDB.data());
...

https://godbolt.org/z/Ef3xYbxn4

Finally, I converted this solution to my own array library: https://godbolt.org/z/jqzWr6dcv

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.