0

(edit) can someone help me to understand why the allocation of a dynamic array is based on two pointers ( this code works without any problem)

int main(int argc, char** argv) {
    int i;
    int n=3;
    int* *t1;
    *t1 = (int*) malloc(n*sizeof(int));
    for (i = 0 ; i <n ;  i++) 
    {
            scanf("%d",(*t1+i));
    }
    for ( i=0; i<n; i++)
    {
        printf("%d",*(*t1+i));
    }

while the only writing that i know is

int* t;
t = (int*) malloc(n*sizeof(int)); 

so what is the interest of using *t instead of t. Thank you.

  • 2
    The first version won't work as shown. `t` must be defined before its target can be validly accessed. – Tom Karzes Feb 08 '20 at 23:21
  • 1
    In C, there is no need to cast the return of `malloc`, it is unnecessary. See: [Do I cast the result of malloc?](http://stackoverflow.com/q/605845/995714) – David C. Rankin Feb 08 '20 at 23:47
  • @Tom Karzes, I am not sure if you are sure about your response but it worked for me. – phillipe cauchett Feb 08 '20 at 23:49
  • 1
    A pointer to a pointer is still _one_ pointer. – Clifford Feb 09 '20 at 00:03
  • 1
    @phillipecauchett : As Tom says, in the first example `t` is not initialised, `*t` dereferences it, then `= malloc(..)` assigns it, so you take a pointer to _who-knows-what?_, dereference it and modify _who_knows_what?_. Even if your code did not crash or raise an exception, it certainly did not "work" in any real sense - it has undefined behaviour, but never _correct_ behaviour. – Clifford Feb 09 '20 at 00:09
  • @Clifford i posted a code below that works without any problem, i don't really get why your are saying that it does not work. Please clarify more. – phillipe cauchett Feb 09 '20 at 00:15
  • @TomKarzes: `int* *t;` and `int **t;` are both perfectly valid declarations and semantically equivalent. – BadZen Feb 09 '20 at 00:15
  • @phillipecauchett You were storing through an undefined pointer, so you were corrupting memory in ways that weren't immediately visible. It may have *appeared* to work, but it was a memory corruption bug. – Tom Karzes Feb 09 '20 at 00:16
  • @BadZen Of course. What's your point? My only comment was that `t` was an undefined pointer in the first example. In the second example, it's declared `int *t;` and is not being referenced before being initialized. – Tom Karzes Feb 09 '20 at 00:24
  • @phillipecauchett : Well I am not sure how I can make it any clearer. It "works" by dumb luck. You are modifying `*t1` when `t1` has no defined value - you could be stomping on anything. – Clifford Feb 09 '20 at 00:37
  • @BadZen : That is not what Tom is objecting to. – Clifford Feb 09 '20 at 00:38
  • @phillipecauchett : I have added further explanation to my answer where it belongs. – Clifford Feb 09 '20 at 00:50

4 Answers4

2

Your question is unclear because you haven't specified what it is you are attempting to achieve. Later in a comment, you indicate you need a simple 1D array, but that isn't included in your question. Let's cover both case plus the case of using a pointer-to-array and make sure you understand the basics.

To understand dynamic allocation, you must understand there are no arrays involved (the exception being allocating for a pointer-to-array of fixed size).

Otherwise, dynamic allocation encompasses allocation of a block of memory of a given size and assignment of the starting address for that new block of memory to a pointer. In C, there is no need to cast the return of malloc, it is unnecessary. See: Do I cast the result of malloc? (in C++ it is required)

Allocating Memory For Integers

When you want to allocate a block of memory for the storage of integers, you simply allocate a block big enough to hold the number of integers you want to store, and assign the starting address for the new block of memory to a pointer. Let's take it step-by-step:

int *t;

declares a pointer-to int to which the beginning address for an allocated block of memory can be assigned.

int n = 100;
t = malloc (n * sizeof *t);

malloc allocates a new block of memory of n * sizeof *t bytes which is sufficient in size to hold n integer values and the beginning address for that block is assigned to the pointer t. Using the dereferenced sizeof *t to set the type-size ensures your type-size is correct. (t being a pointer-to int, when you dereference t (e.g. *t), your type is plain-old int, so sizeof *t is equivalent to sizeof (int))

It may not seem like it matters now (and for trivial types it doesn't make a lot of difference), but as the type of objects you deal with grow in complexity and levels of pointer indirection, mistakenly setting an incorrect type-size becomes a common problem with dealing with allocated nested structures, etc.. That is why simply using the dereferenced pointer to set type-size is recommended.

Let's take a simple example that users a pointer int *t_1D; to emphasize it is single linear allocation that can be indexed as a simple 1D array:

#define NUMINT 50       /* number of integers for t_1D */
...
int main (void) {

    int *t_1D = NULL;               /* pointer to int */

    t_1D = malloc (NUMINT * sizeof *t_1D);  /* allocate storage for NUMINT int */

    if (t_1D == NULL) {             /* validate EVERY allocation, handle error */
        perror ("malloc-t_1D");
        return 1;
    }

    for (int i = 0; i < NUMINT; i++)
        t_1D[i] = i + 1;                /* assign a value to each integer */

    puts ("\nvalues in t_1D (with \\n every 5th integer)\n");
    for (int i = 0; i < NUMINT; i++) {  /* loop over each value */
        if (i && i % ARRSZ == 0)        /* simple mod test to output newline */
            putchar ('\n');
        printf (" %3d", t_1D[i]);       /* output value at address */
    }
    puts ("\n");

    free (t_1D);                    /* don't forget to free what you allocate */

Above when you are done with that block of memory, you must free() the block to prevent a memory leak. Understand for trivial examples where you allocate in main() the memory is freed on program exit. However, it is important to develop good habits early as you will commonly be allocating in a function all and if the memory is not freed before your function returns (or later freed in the caller before it returns) you lose the pointer to the block and it cannot be free -- resulting in a memory leak.

Simulating a 2D Array with a Pointer-To-Pointer

If you use a pointer-to-pointer (e.g. int **t;) as your pointer, your allocation is a two-step process. First you allocate a block of memory to hold some number of pointers (which you generally think of as the number of rows for the object you are allocating).

Then in a second set of allocations, you will allocate a block of memory to hold the number of integers-per-row you wish to store per-row (generally thought of as the col (column) number of integers) and assign the starting address for each allocated block to one of your allocated pointers -- in sequence.

The result is a data structure where you allocate 1-block of memory to hold row number pointers and and row blocks of memory to hold col number of integers. Then you can access each of the memory locations just as you would access values in a 2D array.

However, note since there are multiple allocations required to create an object from a pointer-to-pointer, there will be multiple calls to free required to free the memory allocated to the object.

Now let's look at how that can be used. Below, we will use int **t_2D; as the variable name to indicate we are storing an object that we can index as a 2D array:

#define ARRSZ   5       /* number of integers per-row for t_2D and t_PTA */
...
    int **t_2D = NULL;              /* pointer to pointer to int */
    ...
    /* a pointer to pointer (allocate pointers, then ints for each pointer) */

    t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D);    /* allocate 10 pointers */

    if (!t_2D) {                    /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)
        /* allocate/validate storage for 5 int assign start address to each ptr */
        if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) {    /* on failure */
            while (i--)             /* free previously allocated block for int */
                free (t_2D[i]);
            free (t_2D);            /* free pointers */
            return 1;
        }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_2D[i][j] = (i * ARRSZ) + j + 1;   /* assign value for each int */

    puts ("values in t_2D output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_2D[i][j]);        /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        free (t_2D[i]);                         /* free integers */
    free (t_2D);                                /* free pointers */

Note the loop at the end that loops over each pointer freeing the storage for integers before calling free to free the block of memory holding the pointers themselves.

A Pointer-To-Array -- Single Allocation, Single-Free, 2D Indexing

There is one other common allocation that you will run into that warrants explanation. You can use a pointer-to-array of fixed length (e.g. int (*t_PTA)[CONST];) and then allocate storage for some number of the fixed arrays in a single call and be able to address the object as a 2D array just as was done above with t_2D. Since there is only a single-allocation required, there is only a single-free needed to free the memory associated with the object.

(note: do not confuse a poitner-to-array (e.g. int (*p)[CONST]) with an array-of-pointers (e.g. int *p[CONST]), they are two distinct types)

To allocate, use and free an object create from a pointer-to-array, you can do the following:

    /* a pointer to array -- single allocation, single-free, 2D indexing */

    int (*t_PTA)[ARRSZ] = NULL;     /* pointer to array of int[ARRSZ] */

    t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA);  /* storage for 50 integers */

    if (!t_PTA) {                   /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_PTA[i][j] = (i * ARRSZ) + j + 1;  /* assign value for each int */

    puts ("values in t_PTA output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_PTA[i][j]);       /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    free (t_PTA);    

(note: the convenience of the single free (t_PTA); used to free all memory associated with the object.)

Now let's put it altogether in a workable example:

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

#define ARRSZ   5       /* number of integers per-row for t_2D and t_PTA */
#define NUMINT 50       /* number of integers for t_1D */

int main (void) {

    int *t_1D = NULL,               /* pointer to int */
        **t_2D = NULL,              /* pointer to pointer to int */
        (*t_PTA)[ARRSZ] = NULL;     /* pointer to array of int[ARRSZ] */

    /* handling simple storage for integers */

    t_1D = malloc (NUMINT * sizeof *t_1D);  /* allocate storage for NUMINT int */

    if (t_1D == NULL) {             /* validate EVERY allocation, handle error */
        perror ("malloc-t_1D");
        return 1;
    }

    for (int i = 0; i < NUMINT; i++)
        t_1D[i] = i + 1;                /* assign a value to each integer */

    puts ("\nvalues in t_1D (with \\n every 5th integer)\n");
    for (int i = 0; i < NUMINT; i++) {  /* loop over each value */
        if (i && i % ARRSZ == 0)        /* simple mod test to output newline */
            putchar ('\n');
        printf (" %3d", t_1D[i]);       /* output value at address */
    }
    puts ("\n");

    free (t_1D);                    /* don't forget to free what you allocate */

    /* a pointer to pointer (allocate pointers, then ints for each pointer) */

    t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D);    /* allocate 10 pointers */

    if (!t_2D) {                    /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)
        /* allocate/validate storage for 5 int assign start address to each ptr */
        if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) {    /* on failure */
            while (i--)             /* free previously allocated block for int */
                free (t_2D[i]);
            free (t_2D);            /* free pointers */
            return 1;
        }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_2D[i][j] = (i * ARRSZ) + j + 1;   /* assign value for each int */

    puts ("values in t_2D output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_2D[i][j]);        /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        free (t_2D[i]);                         /* free integers */
    free (t_2D);                                /* free pointers */

    /* a pointer to array -- single allocation, single-free, 2D indexing */

    t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA);  /* storage for 50 integers */

    if (!t_PTA) {                   /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_PTA[i][j] = (i * ARRSZ) + j + 1;  /* assign value for each int */

    puts ("values in t_PTA output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_PTA[i][j]);       /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    free (t_PTA);    

}

Where you compile and run to receive the following:

Example Use/Output

$ ./bin/dynalloc_1_2_pta

values in t_1D (with \n every 5th integer)

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_2D output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_PTA output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

Memory Use/Error Check

It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/dynalloc_1_2_pta
==10642== Memcheck, a memory error detector
==10642== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10642== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10642== Command: ./bin/dynalloc_1_2_pta
==10642==

values in t_1D (with \n every 5th integer)

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_2D output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_PTA output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

==10642==
==10642== HEAP SUMMARY:
==10642==     in use at exit: 0 bytes in 0 blocks
==10642==   total heap usage: 14 allocs, 14 frees, 1,704 bytes allocated
==10642==
==10642== All heap blocks were freed -- no leaks are possible
==10642==
==10642== For counts of detected and suppressed errors, rerun with: -v
==10642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

It is a lot to digest at one sitting, but take it piece-by-piece and dynamic allocation will start to make sense. Let me know if you have furher questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
1

In the first case t is a pointer to int*. And the right way would be for allocation:

int **t;
t = (int**) malloc(n*sizeof(int*));

So in other word: it is an array of int*, which is the dynamically allocated version of int* t[n].

And this is different, than the second case, where t is a pointer to int type, basically t is an array of int in this case, which is like the dynamically allocated version of int t[n].

Eraklon
  • 4,206
  • 2
  • 13
  • 29
  • I think in this case you are talking about a 2D array, while the one that i mentioned is 1D. – phillipe cauchett Feb 08 '20 at 23:46
  • 1
    Not really. The first case is a 1D array of `int*` types. Indeed this is the way to make 2D arrays, but still `t` is 1D in both case, they just hold different types. – Eraklon Feb 08 '20 at 23:55
0

Please clarify. However, the first version is incorrect. If you want to allocate a array of pointers to arrays, you should be doing:

int **t;
t=(int**) malloc(n*sizeof(int*));

To allocate the array of pointers to each individual row, and then allocating the rows:

for (int i=0; i<n; ++i){
  t[i] = (int*) malloc(n*sizeof(int));
}

*t won't be valid in your first snippet, since it can be accessed. You would have to allocate **t first.

André Caceres
  • 719
  • 4
  • 15
  • 2
    That's actually not a 2d array, it's an array of *pointers* to arrays. A 2d array in C is an array of *arrays*. They can be used in similar ways, but they are quite different and are not compatible with each other. A 2d array pointer would look like `int (*p)[n];` (actually that would be a pointer to a slice of a 2d array, per the usual C conventions). – Tom Karzes Feb 08 '20 at 23:29
  • Thank you for catching this mistake. I will correct the answer. – André Caceres Feb 08 '20 at 23:41
  • The first allocation allocates a block of memory capable of storing `n` pointers to `int`. With each subsequent allocation you are allocating storage for `n` integers and assigning the beginning address for each block of integers to each of the pointers you allocated storage for with your first allocation. There is no *Array* involved at all (only pointers and blocks of memory) -- however, due to the way indexing works, this method can *Simulate* a 2D array because the integers stored can be indexed in the same manner. – David C. Rankin Feb 08 '20 at 23:50
  • It's a 1D array, the thing is that it works but i don't understand what is the difference between using both expressions – phillipe cauchett Feb 08 '20 at 23:51
  • To store integers in memory similar to how they would be stored in a 1D array, you would simply do `int *t = malloc (n * sizeof *t);` to allocate storage for `n` integers. Then you can access each `int` through the indexes `t[0 -> n-1]`just as you would access the elements of an array. (**note:** using the dereferenced pointer `sizeof *t` in your allocation will always set your *type-size* correctly) – David C. Rankin Feb 08 '20 at 23:53
  • Both casts ares not needed. [Do I cast the result of malloc?](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) – chux - Reinstate Monica Feb 09 '20 at 00:19
0

An int** would be is used when you want to allocate a block of int* objects. In your example, that is not what you have done or indeed want. In fact you have not allocated or assigned anything to t1, making the allocation to *t1 (i.e. t1[0]) invalid because t1 is itself unknown.

If for example you wanted an allocated array of pointers to multiple allocated int arrays, then int** would be the appropriate data type for the array of pointers to arrays of int:

// Allocate n arrays of m ints
int** t = malloc( sizeof(int*) * n ) ;
for( int i = 0; i < n; i++ )
{
    t[i] = malloc( sizeof(int) * m ) ;
} 

Then for example t[2][3] refers to the fourth element in the third block of of int.

It is not an issue of syntax; they are not different forms of the same thing; they are semantically different, and in the context of your example int** is inappropriate and semantically incorrect.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • @phillipecauchett you are welcome, but on SO you are discouraged from posting comments just to say thanks. Rather you should up-vote and/or accept useful answers. – Clifford Feb 09 '20 at 07:59