[ACCEPTED]-How do I best handle dynamic multi-dimensional arrays in C/C++?-multidimensional-array

Accepted answer
Score: 21

Use boost::multi_array.

As in your example, the only thing 17 you need to know at compile time is the 16 number of dimensions. Here is the first 15 example in the documentation :

#include "boost/multi_array.hpp"
#include <cassert>

int 
main () {
  // Create a 3D array that is 3 x 4 x 2
  typedef boost::multi_array<double, 3> array_type;
  typedef array_type::index index;
  array_type A(boost::extents[3][4][2]);

  // Assign values to the elements
  int values = 0;
  for(index i = 0; i != 3; ++i) 
    for(index j = 0; j != 4; ++j)
      for(index k = 0; k != 2; ++k)
        A[i][j][k] = values++;

  // Verify values
  int verify = 0;
  for(index i = 0; i != 3; ++i) 
    for(index j = 0; j != 4; ++j)
      for(index k = 0; k != 2; ++k)
        assert(A[i][j][k] == verify++);

  return 0;
}

Edit: As suggested 14 in the comments, here is a "simple" example application that let you 13 define the multi-dimensional array size 12 at runtime, asking from the console input. Here 11 is an example output of this example application 10 (compiled with the constant saying it's 9 3 dimensions) :

Multi-Array test!
Please enter the size of the dimension 0 : 4

Please enter the size of the dimension 1 : 6

Please enter the size of the dimension 2 : 2

Text matrix with 3 dimensions of size (4,6,2) have been created.

Ready!
Type 'help' for the command list.

>read 0.0.0
Text at (0,0,0) :
  ""

>write 0.0.0 "This is a nice test!"
Text "This is a nice test!" written at position (0,0,0)

>read 0.0.0
Text at (0,0,0) :
  "This is a nice test!"

>write 0,0,1 "What a nice day!"
Text "What a nice day!" written at position (0,0,1)

>read 0.0.0
Text at (0,0,0) :
  "This is a nice test!"

>read 0.0.1
Text at (0,0,1) :
  "What a nice day!"

>write 3,5,1 "This is the last text!"
Text "This is the last text!" written at position (3,5,1)

>read 3,5,1
Text at (3,5,1) :
  "This is the last text!"

>exit

The important parts in the 8 code are the main function where we get 7 the dimensions from the user and create 6 the array with :

const unsigned int DIMENSION_COUNT = 3; // dimension count for this test application, change it at will :)

// here is the type of the multi-dimensional (DIMENSION_COUNT dimensions here) array we want to use
// for this example, it own texts
typedef boost::multi_array< std::string , DIMENSION_COUNT > TextMatrix;

// this provide size/index based position for a TextMatrix entry.
typedef std::tr1::array<TextMatrix::index, DIMENSION_COUNT> Position; // note that it can be a boost::array or a simple array

/*  This function will allow the user to manipulate the created array
    by managing it's commands.
    Returns true if the exit command have been called.
*/
bool process_command( const std::string& entry, TextMatrix& text_matrix );

/* Print the position values in the standard output. */
void display_position( const Position& position );

int main()
{
    std::cout << "Multi-Array test!" << std::endl;

    // get the dimension informations from the user
    Position dimensions; // this array will hold the size of each dimension 

    for( int dimension_idx = 0; dimension_idx < DIMENSION_COUNT; ++dimension_idx )
    {
        std::cout << "Please enter the size of the dimension "<< dimension_idx <<" : ";
        // note that here we should check the type of the entry, but it's a simple example so lets assume we take good numbers
        std::cin >> dimensions[dimension_idx]; 
        std::cout << std::endl;

    }

    // now create the multi-dimensional array with the previously collected informations
    TextMatrix text_matrix( dimensions );

    std::cout << "Text matrix with " << DIMENSION_COUNT << " dimensions of size ";
    display_position( dimensions );
    std::cout << " have been created."<< std::endl;
    std::cout << std::endl;
    std::cout << "Ready!" << std::endl;
    std::cout << "Type 'help' for the command list." << std::endl;
    std::cin.sync();


    // we can now play with it as long as we want
    bool wants_to_exit = false;
    while( !wants_to_exit )
    {
        std::cout << std::endl << ">" ;
        std::tr1::array< char, 256 > entry_buffer; 
        std::cin.getline(entry_buffer.data(), entry_buffer.size());

        const std::string entry( entry_buffer.data() );
        wants_to_exit = process_command( entry, text_matrix );
    }

    return 0;
}

And you can see that to 5 accede an element in the array, it's really 4 easy : you just use the operator() as in 3 the following functions :

void write_in_text_matrix( TextMatrix& text_matrix, const Position& position, const std::string& text )
{
    text_matrix( position ) = text;
    std::cout << "Text \"" << text << "\" written at position ";
    display_position( position );
    std::cout << std::endl;
}

void read_from_text_matrix( const TextMatrix& text_matrix, const Position& position )
{
    const std::string& text = text_matrix( position );
    std::cout << "Text at ";
    display_position(position);
    std::cout << " : "<< std::endl;
    std::cout << "  \"" << text << "\"" << std::endl;
}

Note : I compiled 2 this application in VC9 + SP1 - got just 1 some forgettable warnings.

Score: 9

There are two ways to represent a 2-dimension 24 array in C++. One being more flexible than 23 the other.

Array of arrays

First make an array of pointers, then 22 initialize each pointer with another array.

// First dimension
int** array = new int*[3];
for( int i = 0; i < 3; ++i )
{
    // Second dimension
    array[i] = new int[4];
}

// You can then access your array data with
for( int i = 0; i < 3; ++i )
{
    for( int j = 0; j < 4; ++j )
    {
        std::cout << array[i][j];
    }
}

THe 21 problem with this method is that your second 20 dimension is allocated as many arrays, so 19 does not ease the work of the memory allocator. Your 18 memory is likely to be fragmented resulting 17 in poorer performance. It provides more 16 flexibility though since each array in the 15 second dimension could have a different 14 size.

Big array to hold all values

The trick here is to create a massive 13 array to hold every data you need. The hard 12 part is that you still need the first array 11 of pointers if you want to be able to access 10 the data using the array[i][j] syntax.

int* buffer = new int[3*4];   
int** array = new int*[3];

for( int i = 0; i < 3; ++i )
{
    array[i] = array + i * 4;
}

The 9 int* array is not mandatory as you could 8 access your data directly in buffer by computing 7 the index in the buffer from the 2-dimension 6 coordinates of the value.

// You can then access your array data with
for( int i = 0; i < 3; ++i )
{
    for( int j = 0; j < 4; ++j )
    {
        const int index = i * 4 + j;
        std::cout << buffer[index];
    }
}

The RULE to keep in mind

Computer memory 5 is linear and will still be for a long time. Keep 4 in mind that 2-dimension arrays are not 3 natively supported on a computer so the 2 only way is to "linearize" the array into 1 a 1-dimension array.

Score: 5

You can alloc rowscolssizeof(int) and access 1 it by table[row*cols+col].

Score: 5

Here is the easy way to do this in C:

void manipulate(int rows, int cols, int (*data)[cols]) {
    for(int i=0; i < rows; i++) {
        for(int j=0; j < cols; j++) {
            printf("%d ", data[i][j]);       
        }
        printf("\n");
    }
}

int main() {
    int rows = ...;
    int cols = ...;
    int (*data)[cols] = malloc(rows*sizeof(*data));
    manipulate(rows, cols, data);
    free(data);
}

This 9 is perfectly valid since C99, however it 8 is not C++ of any standard: C++ requires 7 the sizes of array types to be compile times 6 constants. In that respect, C++ is now fifteen 5 years behind C. And this situation is not 4 going to change any time soon (the variable 3 length array proposal for C++17 does not 2 come close to the functionality of C99 variable 1 length arrays).

Score: 4

The standard way without using boost is 23 to use std::vector :

std::vector< std::vector<int> > v;
v.resize(rows, std::vector<int>(cols, 42)); // init value is 42
v[row][col] = ...;

That will take care 22 of new / delete the memory you need automatically. But 21 it's rather slow, since std::vector is not primarily 20 designed for using it like that (nesting 19 std::vector into each other). For example, all the 18 memory is not allocated in one block, but 17 separate for each column. Also the rows 16 don't have to be all of the same width. Faster 15 is using a normal vector, and then doing 14 index calculation like col_count * row + col to get at a certain 13 row and col:

std::vector<int> v(col_count * row_count, 42);
v[col_count * row + col) = ...;

But this will loose the capability 12 to index the vector using [x][y]. You also have 11 to store the amount of rows and cols somewhere, while 10 using the nested solution you can get the 9 amount of rows using v.size() and the amount of 8 cols using v[0].size().

Using boost, you can use boost::multi_array, which 7 does exactly what you want (see the other 6 answer).


There is also the raw way using 5 native C++ arrays. This envolves quite some 4 work and is in no way better than the nested 3 vector solution:

int ** rows = new int*[row_count];
for(std::size_t i = 0; i < row_count; i++) {
    rows[i] = new int[cols_count];
    std::fill(rows[i], rows[i] + cols_count, 42);
}

// use it... rows[row][col] then free it...

for(std::size_t i = 0; i < row_count; i++) {
    delete[] rows[i];
}

delete[] rows;

You have to store the amount 2 of columns and rows you created somewhere 1 since you can't receive them from the pointer.

Score: 3

2D C-style arrays in C and C++ are a block 12 of memory of size rows * columns * sizeof(datatype) bytes.

The actual [row][column] dimensions 11 exist only statically at compile time. There's 10 nothing there dynamically at runtime!

So, as 9 others have mentioned, you can implement

  int array [ rows ] [ columns ];

As:

 int  array [ rows * columns ]

Or 8 as:

 int * array = malloc ( rows * columns * sizeof(int) );

Next: Declaring a variably sized array. In C this 7 is possible:

int main( int argc, char ** argv )
{
  assert( argc > 2 );

  int rows    = atoi( argv[1] );
  int columns = atoi( argv[2] );

  assert(rows > 0 && columns > 0);
  int data [ rows ] [ columns ];  // Yes, legal!

  memset( &data, 0, sizeof(data) );

  print( rows, columns, data );
  manipulate( rows, columns, data );
  print( rows, columns, data );
}

In C you can just pass the variably-sized 6 array around the same as a non-variably-sized 5 array:

void manipulate( int theRows, int theColumns, int theData[theRows][theColumns] )
{
  for (   int r = 0; r < theRows;    r ++ )
    for ( int c = 0; c < theColumns; c ++  )
      theData[r][c] = r*10 + c;
}

However, in C++ that is not possible. You need to 4 allocate the array using dynamic allocation, e.g.:

int *array = new int[rows * cols]();

or 3 preferably (with automated memory management)

std::vector<int> array(rows * cols);

Then 2 the functions must be modified to accept 1 1-dimensional data:

void manipulate( int theRows, int theColumns, int *theData )
{
  for (   int r = 0; r < theRows;    r ++ )
    for ( int c = 0; c < theColumns; c ++  )
      theData[r * theColumns + c] = r*10 + c;
}
Score: 2

If you're using C instead of C++ you might 9 want to look at the Array_T abstraction 8 in Dave Hanson's library C Interfaces and Implementations. It's exceptionally 7 clean and well designed. I have my students 6 do a two-dimensional version as an exercise. You 5 could do that or simply write an additional 4 function that does an index mapping, e.g.,

void *Array_get_2d(Array_T a, int width, int height, int i, int j) {
    return Array_get(a, j * width, i, j);
}

It 3 is a bit cleaner to have a separate structure 2 where you store the width, the height, and 1 a pointer to the elements.

Score: 1

I recently came across a similar problem. I 45 did not have Boost available. Vectors of 44 vectors turned out to be pretty slow in 43 comparison to plain arrays. Having an array 42 of pointers makes the initialization a lot 41 more laborious, because you have to iterate 40 through every dimension and initialize the 39 pointers, possibly having some pretty unwieldy, cascaded 38 types in the process, possibly with lots 37 of typedefs.

DISCLAIMER: I was not sure 36 if I should post this as an answer, because 35 it only answers part of your question. My 34 apologies for the following:

  • I did not cover how to read the dimensions from standard input, as other commentators had remarked.
  • This is primarily for C++.
  • I have only coded this solution for two dimensions.

I decided to 33 post this anyway, because I see vectors 32 of vectors brought up frequently in reply 31 to questions about multi-dimensional arrays 30 in C++, without anyone mentioning the performance 29 aspects of it (if you care about it).

I 28 also interpreted the core issue of this 27 question to be about how to get dynamic 26 multi-dimensional arrays that can be used 25 with the same ease as the Java example from 24 the question, i.e. without the hassle of 23 having to calculate the indices with a pseudo-multi-dimensional 22 one-dimensional array.

I didn't see compiler 21 extensions mentioned in the other answers, like 20 the ones provided by GCC/G++ to declare 19 multi-dimensional arrays with dynamic bounds 18 the same way you do with static bounds. From 17 what I understand, the question does not 16 restrict the answers to standard C/C++. ISO 15 C99 apparently does support them, but in 14 C++ and prior versions of C they appear 13 to be compiler-specific extensions. See 12 this question: Dynamic arrays in C without malloc?

I came up with a way that 11 people might like for C++, because it's 10 little code, has the ease of use of the 9 built-in static multi-dimensional arrays, and 8 is just as fast.

template <typename T> 
class Array2D {
private:
    std::unique_ptr<T> managed_array_;
    T* array_;
    size_t x_, y_;

public:
    Array2D(size_t x, size_t y) {
        managed_array_.reset(new T[x * y]);
        array_ = managed_array_.get();
        y_ = y;
    }
    T* operator[](size_t x) const {
        return &array_[x * y_];
    }
};

You can use it like this. The 7 dimensions do not

auto a = Array2D<int>(x, y);
a[xi][yi] = 42;

You can add an assertion, at 6 least to all but the last dimension and 5 extend the idea to to more than two dimensions. I 4 have made a post on my blog about alternative 3 ways to get multi-dimensional arrays. I 2 am also much more specific on the relative 1 performance and coding effort there.

Performance of Dynamic Multi-Dimensional Arrays in C++

Score: 0

You could use malloc to accomplish this 3 and still have it accessible through normal 2 array[][] mean, verses the array[rows * cols 1 + cols] method.

main()
{
   int i;
   int rows;
   int cols;
   int **array = NULL;

   array = malloc(sizeof(int*) * rows);
   if (array == NULL)
       return 0;  // check for malloc fail

   for (i = 0; i < rows; i++)
   {
       array[i] = malloc(sizeof(int) * cols)
       if (array[i] == NULL)
           return 0;  // check for malloc fail
   }

   // and now you have a dynamically sized array
}

More Related questions