16

Is there any way to malloc a large array, but refer to it with 2D syntax? I want something like:

int *memory = (int *)malloc(sizeof(int)*400*200);
int MAGICVAR = ...;
MAGICVAR[20][10] = 3; //sets the (200*20 + 10)th element


UPDATE: This was important to mention: I just want to have one contiguous block of memory. I just don't want to write a macro like:
#define INDX(a,b) (a*200+b);

and then refer to my blob like:

memory[INDX(a,b)];

I'd much prefer:

memory[a][b];


UPDATE: I understand the compiler has no way of knowing as-is. I'd be willing to supply extra information, something like:
int *MAGICVAR[][200] = memory;

Does no syntax like this exist? Note the reason I don't just use a fixed width array is that it is too big to place on the stack.


UPDATE: OK guys, I can do this:
void toldyou(char MAGICVAR[][286][5]) {
  //use MAGICVAR
}

//from another function:
  char *memory = (char *)malloc(sizeof(char)*1820*286*5);
  fool(memory);

I get a warning, passing arg 1 of toldyou from incompatible pointer type, but the code works, and I've verified that the same locations are accessed. Is there any way to do this without using another function?

Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • Yes, this has been covered many times on SO already, e.g. [C Programming: malloc() for a 2D array (using pointer-to-pointer)](http://stackoverflow.com/questions/2842135/c-programming-malloc-for-a-2d-array-using-pointer-to-pointer) – Paul R Jun 29 '10 at 19:47
  • er sorry, I should state that I don't want to have nested pointers. i just want a contiguous block of memry. – Claudiu Jun 29 '10 at 19:49
  • After posting my answer, i had that "toldyou" idea running around in my head. I just can't imagine how this bit of syntactic sugar is worth all the hoops you have to jump through to get it ;) – Cogwheel Jun 29 '10 at 20:48
  • @Cogwheel: heh, I guess it isn't, but I didn't know that when I first posted this question! – Claudiu Jun 29 '10 at 20:58

8 Answers8

29

Yes, you can do this, and no, you don't need another array of pointers like most of the other answers are telling you. The invocation you want is just:

int (*MAGICVAR)[200] = malloc(400 * sizeof *MAGICVAR);
MAGICVAR[20][10] = 3; // sets the (200*20 + 10)th element

If you wish to declare a function returning such a pointer, you can either do it like this:

int (*func(void))[200]
{
    int (*MAGICVAR)[200] = malloc(400 * sizeof *MAGICVAR);
    MAGICVAR[20][10] = 3;

    return MAGICVAR;
}

Or use a typedef, which makes it a bit clearer:

typedef int (*arrayptr)[200];

arrayptr function(void)
{
    /* ... */
caf
  • 233,326
  • 40
  • 323
  • 462
  • ah I knew it was possible! take that, nay-sayers... @Tim: Sorry, but I didn't realize your solution did what I wanted, caf's made it blatantly obvious. – Claudiu Jun 30 '10 at 05:14
  • @Tim: Aye, I upvoted yours when I saw it, too - but I figured I might as well leave my answer, since it seemed to be only two of us against the world ;) – caf Jun 30 '10 at 05:24
  • 2
    @Claudiu: It's probably worth pointing out that `foo[]` in function parameter declarations is just syntactic sugar for `(*foo)` - it's just that `[]` means something different in actual variable declarations (where it means an array whose size is determined by the initialiser). – caf Jun 30 '10 at 05:26
  • You can do even more magic by using VLA feature of C99! `int (*MOREMAGICVAR)[b] = (int (*)[b]) malloc(a * b * sizeof(int));` – Vader B Mar 27 '17 at 16:22
  • @VaderB: Yes, the VLA feature is really only useful in variable declarations like that. You can still use the `a * sizeof MOREMAGICVAR[0]` formulation for the size, by the way (ie. don't repeat `b`). – caf Mar 28 '17 at 00:19
  • @caf What are the dimensions of the array in your answer? – Oliver Strong Dec 27 '17 at 07:35
  • @OliverStrong: It's equivalent to `[400][200]` – caf Dec 28 '17 at 13:34
19

Use a pointer to arrays:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int (*arr)[10];

    arr = malloc(10*10*sizeof(int));
    for (int i = 0; i < 10; i++)
        for(int j = 0; j < 10; j++)
            arr[i][j] = i*j;

    for (int i = 0; i < 10; i++)
        for(int j = 0; j < 10; j++)
            printf("%d\n", arr[i][j]);
    free(arr);
    return 0;
}
Tim Schaeffer
  • 2,616
  • 1
  • 16
  • 20
5

If extra indirection isn't a concern, you can use an array of pointers.

Edit

Here's a variation on @Platinum Azure's answer that doesn't make so many calls to malloc. Besides faster allocation, all the elements are guaranteed to be contiguous:

#define ROWS 400
#define COLS 200

int **memory = malloc(ROWS * sizeof(*memory));
int *arr = malloc(ROWS * COLS * sizeof(int));

int i;
for (i = 0; i < ROWS; ++i)
{
    memory[i] = &arr[i * COLS];
}

memory[20][10] = 3;
Cogwheel
  • 22,781
  • 4
  • 49
  • 67
  • hmm interesting... a bit more setup than i'd like (which is 1 line of special syntax), but this is probably closest that can be done... sadly w/ some overhead for the array of pointers. – Claudiu Jun 29 '10 at 20:03
  • Ooh, fewer mallocs? I like it. I should take note of this and use it myself. (+1) – Platinum Azure Jun 29 '10 at 20:10
  • 1
    This works, but a pointer-to-array is simpler (doesn't require the setup loop) and is exactly what the OP is after. – caf Jun 29 '10 at 23:25
3

In the same vein as Cogwheel's answer, here's a (somewhat dirty) trick that makes only one call to malloc():

#define ROWS 400
#define COLS 200
int** array = malloc(ROWS * sizeof(int*) + ROWS * COLS * sizeof(int));
int i;
for (i = 0; i < ROWS; ++i)
    array[i] = (int*)(array + ROWS) + (i * COLS);

This fills the first part of the buffer with pointers to each row in the immediately following, contiguous array data.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • This has the advantage of working even when the size of neither dimension is known at compile-time. Also see: http://c-faq.com/aryptr/dynmuldimary.html – jamesdlin Jun 30 '10 at 09:42
  • @jamesdlin Since C99 the other solutions also work when the dimension is not known at compile-time; and this has the disadvantage that the first entry in the array (after the pointer table) might not be correctly aligned. – M.M Oct 14 '14 at 23:37
2
#define ROWS 400
#define index_array_2d(a,i,j) (a)[(i)*ROWS + (j)]
...
index_array_2d( memory, 20, 10 ) = -1;
int x = index_array_2d( memory, 20, 10 );

Edit:

Arrays and pointers look very much the same, but the compiler treats them very differently. Let's see what needs to be done for an array indexing and de-referencing a pointer with offset:

  1. Say we declared a static array (array on the stack is just a bit more complicated, fixed offset from a register, but essentially the same):

    static int array[10];

  2. And a pointer:

    static int* pointer;

  3. We then de-deference each as follows:

    x = array[i];
    x = pointer[i];

The thing to note is that address of the beginning of array, as well as, address of pointer (not its contents) are fixed at link/load time. Compiler then does the following:

  1. For array de-reference:
    • loads value of i,
    • adds it to the value of array, i.e. its fixed address, to form target memory address,
    • loads the value from calculated address
  2. For pointer de-reference:
    • loads the value of i,
    • loads the value of pointer, i.e. the contents at its address,
    • adds two values to form the effective address
    • loads the value from calculated address.

Same happens for 2D array with additional steps of loading the second index and multiplying it by the row size (which is a constant). All this is decided at compile time, and there's no way of substituting one for the other at run-time.

Edit:

@caf here has the right solution. There's a legal way within the language to index a pointer as two-dimentional array after all.

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • yeah but the compiler is smart enough to do this in syntax when I declare a 2D array. is there no way to tell it to treat a single pointer like this? – Claudiu Jun 29 '10 at 19:56
  • As platinum azure said, the compiler has no way of knowing (read: there is no way to tell it). – Cogwheel Jun 29 '10 at 19:59
  • When you declare 2D array you have to tell the compiler the outer dimension - that's how it's "smart enough". There's no way to figure that out without this information. – Nikolai Fetissov Jun 29 '10 at 19:59
  • I know, but I'm willing to supply this information for the sake of convenience. e.g I can imagine syntax like "int *arrayptr[][200] = memory", then it knows what the outer dimension is. but i take it there's no way to do this? (Also it needs the inner dimension, not the outer) – Claudiu Jun 29 '10 at 20:00
  • @Nikolai: no way of doing it for pointers, except for the function trick in my update =) – Claudiu Jun 29 '10 at 20:59
  • Yes, you are tricking the compiler, it warns you, but lets you get away with it. It's C - go ahead and shoot yourself in the ... foot :) – Nikolai Fetissov Jun 29 '10 at 21:02
  • yes that's all I wanted to know.. if I could trick the compiler in this situation. turns out it's a bit difficult. much easier to make it think floats are ints or something like that. – Claudiu Jun 29 '10 at 22:30
  • There *is* a way to do this with pointers - you simply want to declare a pointer-to-an-array variable. These are seldom used, but perfectly legal, and exactly what the OP wants. – caf Jun 29 '10 at 23:23
1

The compiler and runtime have no way of knowing your intended dimension capacities with only a multiplication in the malloc call.

You need to use a double pointer in order to achieve the capability of two indices. Something like this should do it:

#define ROWS 400
#define COLS 200

int **memory = malloc(ROWS * sizeof(*memory));

int i;
for (i = 0; i < ROWS; ++i)
{
    memory[i] = malloc(COLS * sizeof(*memory[i]);
}

memory[20][10] = 3;

Make sure you check all your malloc return values for NULL returns, indicating memory allocation failure.

Platinum Azure
  • 45,269
  • 12
  • 110
  • 134
  • exactly, but can I just *tell* it somehow? There are plenty of things in C you can tell the compiler to do, even if it might know you are wrong =P. – Claudiu Jun 29 '10 at 19:51
  • You could just use fixed-width arrays in that case. :-P – Platinum Azure Jun 29 '10 at 19:56
  • @Platinum: wrong, it is too big! See http://stackoverflow.com/questions/3144135/why-do-i-get-a-segfault-in-c-from-declaring-a-large-array-on-the-stack . that's why i have to malloc in the first place – Claudiu Jun 29 '10 at 20:01
  • Well, that's my point then: Sometimes you just can't have everything. Especially with a low-level language. You can't just declare an array to be fixed-width unless you're using compile-time constants. And if you want SOME heap benefits, or all, you need to work within the constraints of the heap and the language. It sucks, I know, but what you want is just not possible in portable ANSI C. – Platinum Azure Jun 29 '10 at 20:04
  • @Platinum: see latest update, I can get it to work when passing things to a function.. really there's nothing that inherently restricts the compiler from doing this. it's not as if memory on the heap is inherently any different than memory on the stack.. but I can see that this particular feature is probably not supported. – Claudiu Jun 29 '10 at 20:16
  • You don't need to use a double pointer at all, you can use a pointer to an array, which does *exactly* what the OP wants. – caf Jun 29 '10 at 23:20
  • line 7, don't you mean i < ROW ?? – ochurlaud Jun 29 '15 at 09:56
0

Working from Tim's and caf's answers, I'll leave this here for posterity:

#include <stdio.h>
#include <stdlib.h>

void Test0() {
    int                             c, i, j, n, r;
    int                             (*m)[ 3 ];

    r = 2;
    c = 3;

    m = malloc( r * c * sizeof(int) );

    for ( i = n = 0; i < r; ++i ) {
        for ( j = 0; j < c; ++j ) {
            m[ i ][ j ] = n++;
            printf( "m[ %d ][ %d ] == %d\n", i, j, m[ i ][ j ] );
        }
    }

    free( m );
}

void Test1( int r, int c ) {
    int                             i, j, n;

    int                             (*m)[ c ];

    m = malloc( r * c * sizeof(int) );

    for ( i = n = 0; i < r; ++i ) {
        for ( j = 0; j < c; ++j ) {
            m[ i ][ j ] = n++;
            printf( "m[ %d ][ %d ] == %d\n", i, j, m[ i ][ j ] );
        }
    }

    free( m );
}

void Test2( int r, int c ) {
    int                             i, j, n;

    typedef struct _M {
        int                         rows;
        int                         cols;

        int                         (*matrix)[ 0 ];
    } M;

    M *                             m;

    m = malloc( sizeof(M) + r * c * sizeof(int) );

    m->rows = r;
    m->cols = c;

    int                             (*mp)[ m->cols ] = (int (*)[ m->cols ]) &m->matrix;

    for ( i = n = 0; i < r; ++i ) {
        for ( j = 0; j < c; ++j ) {
            mp[ i ][ j ] = n++;
            printf( "m->matrix[ %d ][ %d ] == %d\n", i, j, mp[ i ][ j ] );
        }
    }

    free( m );
}

int main( int argc, const char * argv[] ) {
    int                             cols, rows;

    rows = 2;
    cols = 3;

    Test0();
    Test1( rows, cols );
    Test2( rows, cols );

    return 0;
}
par
  • 17,361
  • 4
  • 65
  • 80
0
int** memory = malloc(sizeof(*memory)*400); 
for (int i=0 ; i < 400 ; i++) 
{
    memory[i] = malloc(sizeof(int)*200);
}
Community
  • 1
  • 1
Sean Nilan
  • 1,745
  • 1
  • 14
  • 21
  • Two things... (1) I think this is backwards; every index increment in the first dimension for `memory` will jump by 400, whereas the OP specified 200. (2) You should put your code statements on separate lines and indent using four spaces at the beginning of each line to create a
     fixed-width environment.
    – Platinum Azure Jun 29 '10 at 19:51