31

I have been asked in an interview how do i allocate a 2-D array and below was my solution to it.

#include <stdlib.h>

int **array;
array = malloc(nrows * sizeof(int *));

for(i = 0; i < nrows; i++)
{
    array[i] = malloc(ncolumns * sizeof(int));
    if(array[i] == NULL)
    {
        fprintf(stderr, "out of memory\n");
        exit or return
    }
}

I thought I had done a good job but then he asked me to do it using one malloc() statement not two. I don't have any idea how to achieve it.

Can anyone suggest me some idea to do it in single malloc()?

unwind
  • 391,730
  • 64
  • 469
  • 606
Amit Singh Tomar
  • 8,380
  • 27
  • 120
  • 199
  • Actually this is making (nrows + 1) malloc calls. –  Dec 08 '19 at 21:20
  • Possible duplicate of [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays). – Lundin Sep 15 '21 at 06:28
  • Warning: many of the answers posted here are harmful. Read the answers by John Bode and chux, ignore the rest. – Lundin Sep 15 '21 at 06:30

8 Answers8

48

Just compute the total amount of memory needed for both nrows row-pointers, and the actual data, add it all up, and do a single call:

int **array = malloc(nrows * sizeof *array + (nrows * (ncolumns * sizeof **array));

If you think this looks too complex, you can split it up and make it a bit self-documenting by naming the different terms of the size expression:

int **array; /* Declare this first so we can use it with sizeof. */
const size_t row_pointers_bytes = nrows * sizeof *array;
const size_t row_elements_bytes = ncolumns * sizeof **array;
array = malloc(row_pointers_bytes + nrows * row_elements_bytes);

You then need to go through and initialize the row pointers so that each row's pointer points at the first element for that particular row:

size_t i;
int * const data = array + nrows;
for(i = 0; i < nrows; i++)
  array[i] = data + i * ncolumns;

Note that the resulting structure is subtly different from what you get if you do e.g. int array[nrows][ncolumns], because we have explicit row pointers, meaning that for an array allocated like this, there's no real requirement that all rows have the same number of columns.

It also means that an access like array[2][3] does something distinct from a similar-looking access into an actual 2d array. In this case, the innermost access happens first, and array[2] reads out a pointer from the 3rd element in array. That pointer is then treatet as the base of a (column) array, into which we index to get the fourth element.

In contrast, for something like

int array2[4][3];

which is a "packed" proper 2d array taking up just 12 integers' worth of space, an access like array[3][2] simply breaks down to adding an offset to the base address to get at the element.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • @Unwind can i have pictorial view of memory for this solution it helpes in greated details to understand this code. – Amit Singh Tomar Jan 05 '12 at 10:04
  • @AmitSinghTomar: I don't think I can represent this in ASCII clearly, and don't have the time to draw something in a real graphics program at the moment, sorry. Perhaps someone can step in? :) – unwind Jan 05 '12 at 10:08
  • Thanks any way @unwind ,I got an idea how's things are working in memory for your code. – Amit Singh Tomar Jan 05 '12 at 10:10
  • @unwind, when you allocate each row with malloc(), each row could also be different length. – Prof. Falken Jan 05 '12 at 10:34
  • @AmigableClarkKant: Yes of course ... I didn't compare against that, I compared against a simple pure C 2D array such as `int array[nrows][ncols]`. – unwind Jan 05 '12 at 10:35
  • @unwind,is not it take more bytes of allocation then required, my point is if we have int a[2][2] it would take 16 bytes in memory(assuming int size of 4 byte) but with dynamic allocation it is taking 24 byte,why? – Amit Singh Tomar Feb 25 '15 at 20:04
  • 1
    @AmitSinghTomar It takes more memory since you have a pointer for each row, which is necessary to "hide" the fact that the width of the array is dynamic. It allows double indexing like `array[i][j]` by making `array[i]` be a valid pointer at that row's data. – unwind Feb 25 '15 at 21:18
  • What is the use of "int *data = array + nrows;" ? Please explain. – Udbhav Kalra Dec 05 '16 at 05:06
  • @UdbhavKalra The use should be clear from the code; it's to just make the address of the first element more easily accessible and remove that constant calculation from the loop. I think it helps with clarity. – unwind Dec 05 '16 at 08:49
  • So, it means that some number of blocks (=number of rows) will be there at the starting location of the array, just after that, there will be some blocks (=no of rows again which is also divided into number of blocks (=number of columns) ). Please correct me if I am wrong. Thanks. – Udbhav Kalra Dec 05 '16 at 10:06
  • 1
    One question: the row `int * const data = array + nrows;` generates a warning for me: `incompatible pointer types initializing 'int *const' with an expression of type 'int **'; dereference with *`. Is this to be ignored? Otherwise thx for a great answer. – Sahand Jul 17 '17 at 11:43
  • `int **array = malloc(nrows * sizeof *array + (nrows * (ncolumns * sizeof **array));` is not guaranteed to work. For example, if pointers are 4 bytes and the data elements require 8-byte alignment, a 3xN array would place data elements on a 4-byte alignment. – Andrew Henle Sep 06 '18 at 12:55
  • Is there any benefit to using just one malloc call instead of multiple malloc calls? Does it guarantee tighter packing? –  Dec 08 '19 at 21:21
  • 1
    @OS2 It's faster by necessity (heap allocation is an expensive operation), and it minimizes per-allocation overhead. I do believe it can (and very often will) lead to tighter layout in memory, yes, since new allocations are aligned to fit any data and that allocation might be more wasteful than what is needed for the actual arrays here. – unwind Dec 13 '19 at 08:43
  • Is it possible to avoid the warning @Sahand mentions without casting `array + nrows` to `(int *)` – Zois Tasoulas Mar 24 '21 at 17:58
  • This approach fails with `int * const data = array + nrows;` in the rare case of `int` alignment needs exceeding those of `int *`. Can be repaired with additional code (e.g, a `union`) to account for the needed padding. IOWs, for `int` this very likely works, yet perhaps not for a wide type with greater than pointer alignment needs like `double`. – chux - Reinstate Monica Sep 15 '21 at 10:22
16
int **array = malloc (nrows * sizeof(int *) + (nrows * (ncolumns * sizeof(int)));

This works because in C, arrays are just all the elements one after another as a bunch of bytes. There is no metadata or anything. malloc() does not know whether it is allocating for use as chars, ints or lines in an array.

Then, you have to initialize:

int *offs = &array[nrows]; /*  same as int *offs = array + nrows; */
for (i = 0; i < nrows; i++, offs += ncolumns) {
    array[i] = offs;
}
Prof. Falken
  • 24,226
  • 19
  • 100
  • 173
  • Thanks Amigable for your solution but I don't understand where int *offs = &arr[nrows] come from,I mean purpose of this line and where we have declared &arr[nrows] ?? – Amit Singh Tomar Jan 05 '12 at 09:55
  • The added setup code looks wrong, the row stride is 1, should be ncolumns. – unwind Jan 05 '12 at 10:00
  • int* data = array + nrows; in unwinds answer is the same as int* offs = &array[nrows]; @AmitSinghTomar – Prof. Falken Jan 05 '12 at 10:23
  • Thanks @AmigableClarkKant got your point a[i]=*(a+i) ,something like this – Amit Singh Tomar Jan 05 '12 at 10:28
  • Could you please explain the second part ? – Udbhav Kalra Nov 02 '16 at 14:53
  • @UdbhavKalra, could you please elaborate your question? – Prof. Falken Nov 03 '16 at 10:52
  • Yes, thanks for replying. I would like to ask about the initialization part where &array[nrows] is mentioned. As, (nrows) is there, not (nrows-1), so, it would point to the last row + (size of 1 more row). I doubt whether I am right here or not but please clarify. And, if it points @ the end. So, shouldn't it be offs -= ncolumns. I know that I misinterpreted it. So, please explain that part. Thanks again. – Udbhav Kalra Nov 03 '16 at 12:58
  • @UdbhavKalra, hmmm... not sure. Could it be that you need to consider that position 0 is the first position in the array? – Prof. Falken Nov 03 '16 at 16:23
  • But, how could the initialization of *offset = &array[nrows] would help ? I don't know what's going on – Udbhav Kalra Nov 03 '16 at 22:12
  • @UdbhavKalra I think I made a mistake. I think it should be `int* offs = array;` – Prof. Falken Nov 04 '16 at 12:38
11

Here's another approach.

If you know the number of columns at compile time, you can do something like this:

#define COLS ... // integer value > 0
...
size_t rows;
int (*arr)[COLS];
...              // get number of rows
arr = malloc(sizeof *arr * rows);
if (arr)
{
  size_t i, j;
  for (i = 0; i < rows; i++)
    for (j = 0; j < COLS; j++)
      arr[i][j] = ...;
}

If you're working in C99, you can use a pointer to a VLA:

size_t rows, cols;
...               // get rows and cols
int (*arr)[cols] = malloc(sizeof *arr * rows);
if (arr)
{
  size_t i, j;
  for (i = 0; i < rows; i++)
    for (j = 0; j < cols; j++)
      arr[i][j] = ...;
}
John Bode
  • 119,563
  • 19
  • 122
  • 198
6

How do we allocate a 2-D array using One malloc statement (?)

No answers, so far, allocate memory for a true 2D array.

int **array is a pointer to pointer to int. array is not a pointer to a 2D array.

int a[2][3] is an example of a true 2D array or array 2 of array 3 of int


To allocate memory for a true 2D array, with C99, use malloc() and save to a pointer to a variable-length array (VLA)

// Simply allocate and initialize in one line of code
int (*c)[nrows][ncolumns] = malloc(sizeof *c);

if (c == NULL) {
  fprintf(stderr, "out of memory\n");
  return;
} 
// Use c
(*c)[1][2] = rand();
...
free(c);

Without VLA support, if the dimensions are constants, code can use

#define NROW 4
#define NCOL 5
int (*d)[NROW][NCOL] = malloc(sizeof *d);
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

You should be able to do this with (bit ugly with all the casting though):

int** array;
size_t pitch, ptrs, i;   
char* base; 
pitch = rows * sizeof(int);
ptrs = sizeof(int*) * rows;
array = (int**)malloc((columns * pitch) + ptrs);
base = (char*)array + ptrs;
for(i = 0; i < rows; i++)
{
    array[i] = (int*)(base + (pitch * i));
}
Necrolis
  • 25,836
  • 3
  • 63
  • 101
  • Clunky code - see @Amigable's answer for a more elegant and readable solution – Paul R Jan 05 '12 at 09:44
  • @PaulR: when I posted my answer he hadn't updated his yet, I prefer unwind's answer though – Necrolis Jan 05 '12 at 09:46
  • @Necrolis, I am probably damaged from trying to optimize code on 80s computers. :-) Back then, multiply inside loops were best avoided. – Prof. Falken Jan 05 '12 at 09:48
  • Both Amigable's and @unwind's are good, and are very similar - they are simple, readable and importantly they avoid unnecessary casts – Paul R Jan 05 '12 at 09:49
  • @PaulR, actually, I usually do like how unwind uses sizeof(variable) instead of sizeof(type), but in this case I wanted my answer to be as similar to the original code as possible, so the OP can more easily understand the answer. I couldn't help but promulgate my indenting style though, it's probably hard coded in my brain... :-) – Prof. Falken Jan 05 '12 at 09:54
  • @PaulR: yeah, casts are a problem, my scaring from working with Direct3D and its memory region locking :) – Necrolis Jan 05 '12 at 09:55
  • Note that "array + ptrs" may not be appropriately aligned for the element type (it's unlikely to be a problem for int, but still not guaranteed to work by the C standard - more likely to be an issue for e.g. double) - you will need to round ptrs up to the next sizeof(element type). – Random832 Jan 05 '12 at 14:42
  • 1
    @Random832: everything is done using a multiple of `sizeof(int)`, the only alignment problems would come from `malloc` (which I doubt). – Necrolis Jan 05 '12 at 14:53
  • 1
    @Necrolis My point was, `sizeof(int *)` (therefore `sizeof(int*) * rows`) may not be a multiple of `sizeof(int)`. It is, on most common platforms, but that's not guaranteed. Like I said, it's more likely to be an issue for other types like double. – Random832 Jan 05 '12 at 15:14
0

It can be done as follows:

#define NUM_ROWS 10
#define NUM_COLS 10

int main(int argc, char **argv)
{
    char (*p)[NUM_COLS] = NULL;
    p = malloc(NUM_ROWS * NUM_COLS);
    memset(p, 81, NUM_ROWS * NUM_COLS);
    p[2][3] = 'a';
    for (int i = 0; i < NUM_ROWS; i++) {
        for (int j = 0; j < NUM_COLS; j++) {
            printf("%c\t", p[i][j]);
        }
        printf("\n");
    }
} // end of main
Amit
  • 9
  • 1
0

I'm not a fan of this "array of pointers to array" to solve the multi dimension array paradigm. Always favored a single dimension array, at access the element with array[ row * cols + col]? No problems encapsulating everything in a class, and implementing a 'at' method.

If you insist on accessing the members of the array with this notation: Matrix[i][j], you can do a little C++ magic. @John solution tries to do it this way, but he requires the number of column to be known at compile time. With some C++ and overriding the operator[], you can get this completely:

class Row
{
private:
    int* _p;

public:
    Row( int* p )                   { _p = p; }
    int& operator[](int col)        { return _p[col]; }
};


class Matrix
{
private:
    int* _p;
    int _cols;

public:
    Matrix( int rows, int cols )  { _cols=cols; _p = (int*)malloc(rows*cols ); }
    Row operator[](int row)       { return _p + row*_cols; }
};

So now, you can use the Matrix object, for example to create a multiplication table:

Matrix mtrx(rows, cols);
for( i=0; i<rows; ++i ) {
    for( j=0; j<rows; ++j ) {
        mtrx[i][j] = i*j;
    }
}

You should now that the optimizer is doing the right thing and there is no call function or any other kind of overhead. No constructor is called. As long as you don't move the Matrix between function, even the _cols variable isn't created. The statement mtrx[i][j] basically does mtrx[i*cols+j].

Uri London
  • 10,631
  • 5
  • 51
  • 81
-2

You can allocate (row*column) * sizeof(int) bytes of memory using malloc. Here is a code snippet to demonstrate.

int row = 3, col = 4;
int *arr = (int *)malloc(row * col * sizeof(int));

int i, j, count = 0;
for (i = 0; i <  r; i++)
  for (j = 0; j < c; j++)
     *(arr + i*col + j) = ++count; //row major memory layout

for (i = 0; i <  r; i++)
  for (j = 0; j < c; j++)
     printf("%d ", *(arr + i*col + j));
Shibasis Patel
  • 315
  • 2
  • 14