2

I am new, and was experimenting with vectors, arrays, and dynamic memory.

I was trying to first initiate an array, allocating some memory to it, then giving one cell a value, and trying to print it onscreen. It doesn't point out any errors, the program just ends after a couple of seconds without output.

int main(){
    int **m;
    m=(int **)malloc(10*10*sizeof(int));
    m[3][3]=3;
    printf("%d", m[3][3]);
}

This is in Dev-C++, in a C file, in case it is relevant.

Chris
  • 26,361
  • 5
  • 21
  • 42
DesdreMBM
  • 21
  • 1

5 Answers5

5

You've allocated 100 ints, which will give you a pointer to int. You can't subscript this the way you have. Your C compiler should have given you errors on this.

If you want to dynamically allocate a "two-dimensional" array of ints, you need to do so explicitly, allocating an array of 10 int pointers, then assigning a dynamically allocated array of ten ints to each of those pointers.

No error checking on malloc result.

int **m = malloc(10 * sizeof(int *));
for (size_t i = 0; i < 10; i++) {
    m[i] = malloc(10 * sizeof(int));
}

But there's really no reason in this case not to allocate on the stack. Given that it's declared in main there will be no lifetime issues when passing a pointer to it to other functions, and the amount of data is not prohibitive to allocate on the stack.

int m[10][10];
Chris
  • 26,361
  • 5
  • 21
  • 42
3

By definition of the [] subscript operator, the expression

a[b]

is equivalent to:

*(a+b)

This means that the expression

m[3][3]

is equivalent to:

*( (*(m+3)) + 3 )

Since you defined m to be a pointer to a pointer to an int, the expression m[3][3] will do the following: It will treat m as if it were a pointer to the first element of an array whose elements are of type int *. It will attempt to read the fourth element of this array. It will treat this fourth element as a pointer to the first element of an array whose elements are of type int and it will attempt to read the fourth element of this array.

However, m[3] has an indeterminate ("garbage") value, so m[3] does not point to a valid array. Therefore, by dereferencing this invalid pointer in the expresson m[3][3], your program is invoking undefined behavior, which means that anything can happen, including the behavior that you described in the question.

Because you used the line

m=(int **)malloc(10*10*sizeof(int));

I assume that it was not your intent to make m point to the first element of an array whose elements are of type int *, but rather to make m point to a 2D array of int elements. In other words, m should point to the first element of an array whose elements are not pointers, but arrays.

In order to accomplish this, you must declare the variable m differently.

The line

int **m;

will declare the variable m to be a pointer to a pointer to an int. This is not what you want. You want it to point to the first sub-array of the 2D array, so you want to declare a pointer to an array of 10 int elements. For this, you must use the following syntax:

int (*m)[10];

The parentheses are important, because without them, you would be declaring an array of 10 elements of type int *, but you want to instead declare a pointer to an array of 10 int elements.

Your program should look like this:

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

int main( void )
{
    int (*m)[10];

    m = malloc( 10*10*sizeof(int) );
    if ( m == NULL )
    {
        fprintf( stderr, "Memory allocation failure!\n" );
        exit( EXIT_FAILURE );
    }

    m[3][3]=3;
    printf( "%d", m[3][3] );

    free( m );
}

This program has the following output:

3

Note that in C, in constrast to C++, it is not necessary and generally discouraged to cast the result of malloc.

Also, it is generally a good idea to check the return value of malloc, to verify that the memory was successfully allocated. Additionally, you should generally free the memory when you no longer need it.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
2

The type int** denotes the type pointer to an (int *), not a two-dimensional array.

So m[3][3] = 3; is logically equivalent to this:

 int * p = m[3];   // get the fourth pointer in the array-of-pointers-to-int
 p[3] = 3;         // set to 3 the fourth pointer in the int-array (p) points to

... which could be okay, if such an array existed and the pointers in the array were actually pointing to valid memory locations. However, in the posted code, the entire buffer is still uninitialized at this point (and wouldn't contain the expected pointer-data even if it were initialized), so dereferencing p invokes undefined behavior.

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
1

The other answers are great. Additionally, here's some elaboration on why

m=(int **)malloc(10*10*sizeof(int));

is not correct usage. malloc simply accepts a single size_t argument (the number of bytes you want to allocate), and you've given it that, but you've derived it in the wrong way. You should allocate space based on the thing being pointed to, which is "one level up" from the pointer type. Here, you've declared m to be an int** type, which means it will point to int* types, and that's what you should use to derive the size fed to malloc:

m=(int **)malloc(10*sizeof(int*));

This is always the paradigm to follow when allocating memory, the thing fed to sizeof should be the thing being pointed to. With the malloc above, m now points to enough space to hold 10 int pointers, or 10 int*s.

So far so good, but there's an even better practice way to do this:

m=(int **)malloc(10*sizeof(*m));

Here, *m is the type being pointed to. And that will always be true no matter type m is. Later on, if m changes types to

struct myStruct** m;

then you'll need to change the cast from (int**) to (struct myStruct**), and likewise for the argument to sizeof. Using *<myVar> in the argument to sizeof means less maintenance down the line if the type of the variable ever changes.

Finally, there's no need to cast the return from malloc. One of the advantages stated above, it leads to needless maintenance if the type of m ever changes. So finally, we're left with:

m = malloc(10 * sizeof(*m));

Assuming malloc doesn't return a NULL pointer (you should always check), you have enough space for 10 int pointers. Now, you can loop thru as shown in the other answers and allocate space for each of those 10 int pointers:

for (int i=0; i<10; i++)
{
   m[i] = malloc(10 * sizeof(*(m[i])));
   // check if m[i] is NULL, handle that error as you want
   // assuming not NULL, m[i] now points to enough space for 10 ints
}

Just as above, m[i] is an int* type, so *(m[i]) is an int type. But if the type of m ever changes, all the arguments to sizeof are updated "automatically".

yano
  • 4,827
  • 2
  • 23
  • 35
1

You can use:

int (*m)[10] = malloc(10 * 10 * sizeof(m[0][0]));

Now, you can index directly:

m[3][3] = 37;
printf("%d\n", m[3][3]);

If your compiler supports VLAs (variable-length arrays), you can have variable size arrays:

int rows = 10;
int cols = 7;
int (*m)[cols] = malloc(rows * cols * sizeof(m[0][0]));
m[3][3] = 37;
printf("%d\n", m[3][3]);

The number of rows and columns can be calculated at runtime rather than needing to set at compile time.

In both these scenarios, it is sufficient to use free(m) to release the allocated memory.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278