Home > front end >  C multidimensional array on heap
C multidimensional array on heap

Time:04-27

I don't understand how to create an multidimensional array on the heap with the new operator. In java it works like that:

int[][][] array = new int[3][3][3];

how do I achieve the same thing in c ? I wanna have an 3d array that I can access like that:

array[0][0][0] = 1;

How can I do that?

CodePudding user response:

For a known-constant-size arrays you can do this

auto shared_array = std::shared_ptr<int[3][3][3]>();

If only the first dimension is not a compile-time constant, you can do this:

auto shared_array = std::shared_ptr<int[][3][3]>(N); // N is a variable

(Caution: you need a cutting edge version of gcc or clang to compile this, and at least -std=c 20; the latest msvc /std:c latest supports this)

(There is also a unique_ptr version of the second variant, but not of the first variant. Why is it so is beyond the scope of this answer).

If a dimension other than the first is not a compile-time constant, then things get really complicated. Unlike Java, C doesn't have true multidimensional arrays. What to do?

One can use a multi-level std::vector in a pinch:

auto multi_vector = std::vector<std::vector<std::vector<int>>>();
multi_vector.resize(N);
for (auto& elem: multi_vector)
{
   elem.resize(M);
   for (auto& next_level_elem: elem)
   {
      next_level_elem.resize(K);
   }
}

You can tell this is not a most pretty solution, but write it once, hide it in a function, and forget about it. You can also write something like this to the same effect:

auto array = 
    std::vector<std::vector<std::vector<int>>>(N, 
      std::vector<std::vector<int>>(M,
        std::vector<int>(K)));

which may or may not be more readable, depending on who's reading (it's less readable for me but YMMV).

You can also use an array of unique_ptrs to arrays in much the same way but I'll leave it to you to figure it out.

The real problem with this solution is not that it is ugly, but that it is very, very inefficient. The triple indirection is a cache killer.

The real solution is to use a dedicated third-party multidimensional array library like Boost.MultiArray or Eigen.Tensor (note this is unsupported). Or, when you are a really seasoned C programmer, roll your own.

#include <boost/multi_array.hpp>
using boost3arrType = boost::multi_array<int, 3>;
using boost3shapeType = boost::array<boost3arrType::index, 3>;
auto x = boost3arrType(boost3shapeType{{2,3,4}});
x[0][1][2] = 42;

#include <eigen3/unsupported/Eigen/CXX11/Tensor>
auto eigenTensor3 = Eigen::Tensor<int, 3>(2, 3, 4);
eigenTensor3(0,1,2) = 42;

No support for eigenTensor3[0][1][2] syntax.

Finally, if you are enrolled in a really crappy C course and your professor would not accept anything he couldn't find in the 1990 Stroustrup book, there's always this. You should not use this method for anything except your assignment that disallows all of the above.

CodePudding user response:

Generally as you mentioned in the comments stack space is limited. In a lot of cases this does not really matter and most definitely it is not a problem for an int array that essentially has size in the double digits. To have it on the stack you can just do:

int array[3][3][3];

Note that this only works if 3 is known compile time. This has the benefit that it is incredibly fast and not really much trouble for you the programmer.

The second option is actually allocating on the heap. The naive way of doing this is:

int*** array = new int**[3];
for (size_t i = 0; i < 3;   i) {
    array[i] = new int*[3];
    for (size_t j = 0; j < 3;   j)
        array[i][j] = new int[3];
}

with of course delete[], when we are done with the memory like:

for (size_t i = 0; i < 3;   i) {
    for (size_t j = 0; j < 3;   j)
        delete[] array[i][j];
    delete[] array[i];
}
delete[] array;

The good thing about this is that 3 here does not have to be compile time, but this suffers greatly from the slowness of new. This second problem can be offset by using a trick like:

int* data = new int[3*3*3];
int** helper_array = new int*[3*3];
int*** array = new int**[3];
for (size_t i = 0; i < 3;   i)
    array[i] = helper_array   3*i;
for (size_t i = 0; i < 3;   i)
for (size_t j = 0; j < 3;   j)
    helper_array[3*i   j] = data   3*3*i   3*j;

with the obligatory release of memory:

delete[] array;
delete[] helper_array;
delete[] data;

This is much easier and nicer to handle, and it also suffers less from the slow speed of new, while still retaining the array[i][j][k] data access.

The easiest way however to handle dynamic memory is through some container like std::vector<>:

std::vector<std::vector<std::vector<int>>> array(3, std::vector<std::vector<int>>(3, std::vector<int>(3)));

This has the benefits that the memory is no longer managed by you, but it still allows for data that has length unknown at compile time. It also does not store data on the stack (unless you explicitly make it do that), so you don't have to worry about stack space running out, but it will have the slow down of the int*** array = new int**[3] solution. An other drawback is that this solution can become very messy at multiple nested vectors.

While the solutions presented here are perfectly valid for some other solutions involving more modern approaches to C see n. 1.8e9-where's-my-share m.'s answer. Those don't open up the possibility of memory leaks and usually are much more nice to manage them because of this.

  • Related