How do I best handle dynamic multi-dimensional arrays in C/C++?

28,327

Solution 1

Use boost::multi_array.

As in your example, the only thing you need to know at compile time is the number of dimensions. Here is the first 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 in the comments, here is a "simple" example application that let you define the multi-dimensional array size at runtime, asking from the console input. Here is an example output of this example application (compiled with the constant saying it's 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 code are the main function where we get the dimensions from the user and create 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 accede an element in the array, it's really easy : you just use the operator() as in 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 this application in VC9 + SP1 - got just some forgettable warnings.

Solution 2

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

Array of arrays

First make an array of pointers, then 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 problem with this method is that your second dimension is allocated as many arrays, so does not ease the work of the memory allocator. Your memory is likely to be fragmented resulting in poorer performance. It provides more flexibility though since each array in the second dimension could have a different size.

Big array to hold all values

The trick here is to create a massive array to hold every data you need. The hard part is that you still need the first array of pointers if you want to be able to access 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 int* array is not mandatory as you could access your data directly in buffer by computing the index in the buffer from the 2-dimension 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 is linear and will still be for a long time. Keep in mind that 2-dimension arrays are not natively supported on a computer so the only way is to "linearize" the array into a 1-dimension array.

Solution 3

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

Solution 4

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 is perfectly valid since C99, however it is not C++ of any standard: C++ requires the sizes of array types to be compile times constants. In that respect, C++ is now fifteen years behind C. And this situation is not going to change any time soon (the variable length array proposal for C++17 does not come close to the functionality of C99 variable length arrays).

Solution 5

The standard way without using boost is 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 of new / delete the memory you need automatically. But it's rather slow, since std::vector is not primarily designed for using it like that (nesting std::vector into each other). For example, all the memory is not allocated in one block, but separate for each column. Also the rows don't have to be all of the same width. Faster is using a normal vector, and then doing index calculation like col_count * row + col to get at a certain row and col:

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

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

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


There is also the raw way using native C++ arrays. This envolves quite some work and is in no way better than the nested 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 of columns and rows you created somewhere since you can't receive them from the pointer.

Share:
28,327

Related videos on Youtube

Ari Ronen
Author by

Ari Ronen

Updated on July 09, 2022

Comments

  • Ari Ronen
    Ari Ronen almost 2 years

    What is the accepted/most commonly used way to manipulate dynamic (with all dimensions not known until runtime) multi-dimensional arrays in C and/or C++.

    I'm trying to find the cleanest way to accomplish what this Java code does:

    public static void main(String[] args){
     Scanner sc=new Scanner(System.in);
     int rows=sc.nextInt();
     int cols=sc.nextInt();
     int[][] data=new int[rows][cols];
     manipulate(data);
    }
    
    public static void manipulate(int[][] data){
       for(int i=0;i<data.length;i++)
       for(int j=0;j<data[0].length.j++){
             System.out.print(data[i][j]);       
       }    
    }
    

    (reads from std_in just to clarify that dimensions aren't known until runtime).

    Edit:I noticed that this question is pretty popular even though it's pretty old. I don't actually agree with the top voted answer. I think the best choice for C is to use a single-dimensional array as Guge said below "You can alloc rowscolssizeof(int) and access it by table[row*cols+col].".

    There is a number of choices with C++, if you really like boost or stl then the answers below might be preferable, but the simplest and probably fastest choice is to use a single dimensional array as in C.

    Another viable choice in C and C++ if you want the [][] syntax is lillq's answer down at the bottom is manually building the array with lots of malloc's.

  • Johannes Schaub - litb
    Johannes Schaub - litb over 15 years
    that's supposed to be for C++
  • Martin York
    Martin York over 15 years
    You should also show how to read in the dimensions from std::cin (as requested in the question)
  • Klaim
    Klaim over 15 years
    You're right! I added an example application. Tell me if it's not clear enough :)
  • moshbear
    moshbear over 12 years
    A nested <vector> is the standard method of implementing automatically-managed Ileffe vectors. For jagged arrays, nested <vector> is not the simplest, but also the method of least headaches.
  • hobb
    hobb over 9 years
    Does this align elements row-wise? That could be important to know for cache optimized access, most programmers would assume the x are spatially close. An alternative would be to switch x,y at all locations in your code, including usage "a[yi][xi]", unintuitive for programmers but just right for mathematicians ;)
  • user2880576
    user2880576 over 9 years
    Thank you very much for pointing that out! Memory should generally be laid out in such a manner that access to it is sequential and thus predictable to modern hardware. One of the most up-voted questions on SO related to performance in this context is exactly about that: stackoverflow.com/questions/11227809/…
  • Timothy
    Timothy over 7 years
    Can one just use int type instead of using "typedef array_type::index index" for the index?