3

I am trying to understand code snippets in free a double pointer

and Why use double pointer? or Why use pointers to pointers?

I want to understand the difference between the following. Both snippets are from the above urls

int** pt; 

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

and

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

Could you elaborate with some examples and drawing

user2979872
  • 413
  • 1
  • 9
  • 20

3 Answers3

12

First of all, the code snippet is bad for several reasons - the first is casting the result of malloc to the wrong type, and is using the wrong type to compute the amount of memory. Fixing the cast and type issues, we have:

int **pt;

 pt = malloc( sizeof  *pt * 10 );   // allocate space for 10 int *
*pt = malloc( sizeof **pt * 10 );   // allocate space for 10 int

After executing the first line, you have the following:

     int **                int *
    +---+                 +---+
pt: |   | --------------->|   | pt[0]
    +---+                 +---+       
                          |   | pt[1]
                          +---+
                          |   | pt[2]
                          +---+
                           ...
                          +---+
                          |   | pt[9]
                          +---+

You've set aside space for 10 int * objects, and pt points to the first of them.

The next line

*pt = malloc( sizeof **pt * 10 ); // allocate space for 10 int

allocates space for 10 int objects, and sets pt[0] to point to them:

     int **                int *                int 
    +---+                 +---+                +---+
pt: |   | --------------->|   | pt[0] -------->|   | pt[0][0]
    +---+                 +---+                +---+
                          |   | pt[1]          |   | pt[0][1]
                          +---+                +---+
                          |   | pt[2]          |   | pt[0][2]
                          +---+                +---+
                           ...                  ...
                          +---+                +---+
                          |   | pt[9]          |   | pt[0][9]
                          +---+                +---+

This illustrates one way of allocating a "jagged" array; you can still index it as pt[i][j], but unlike a true 2D array the rows are not adjacent in memory, and each row may be a different length. You'd normally write that as

pt = malloc( sizeof *pt * ROWS );
if ( pt )
{
  for ( size_t r = 0; r < ROWS; r++ )
  {
    pt[r] = malloc( sizeof *pt[r] * COLS );
  }
}

When that's all done, you have something like this:

     int **           int *                 int
    +---+            +---+                 +---+---+     +---+
pt: |   | ---------> |   | pt[0] --------> |   |   | ... |   | pt[0][0] - pt[0][COLS-1]
    +---+            +---+                 +---+---+     +---+
                     |   | pt[1] ------+
                     +---+             |   +---+---+     +---+
                     |   | pt[2] ---+  +-> |   |   | ... |   | pt[1][0] - pt[1][COLS-1]
                     +---+          |      +---+---+     +---+
                      ...           | 
                                    |      +---+---+     +---+
                                    +----> |   |   | ... |   | pt[2][0] - pt[2][COLS-1]
                                           +---+---+     +---+
ikegami
  • 367,544
  • 15
  • 269
  • 518
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • The casting of the result is a very good idea because it allows the compiler to issue a message for this statement pt = (int*) malloc(sizeof(int)*10); – Vlad from Moscow Oct 10 '17 at 17:48
  • @VladfromMoscow: using `sizeof *pt` instead of `sizeof(int)` eliminates that particular issue. `T *p = malloc( sizeof *p * N );` will always do the right thing, regardless of `T`. – John Bode Oct 10 '17 at 18:05
  • Are you sure? Consider for example char *p = malloc( sizeof( struct A ) ); and char *p = malloc( sizeof *p ); – Vlad from Moscow Oct 10 '17 at 18:26
  • @VladfromMoscow: I'm not sure what point you're trying to make - if my target is `p`, I'm going to use `sizeof *p`, not `sizeof some_arbitrary_type_that_isn't_the_same_type_as_*p`. – John Bode Oct 10 '17 at 18:36
  • It is not always is true. For example you need to allocate a character buffer that to copy a structure or you need to allocate a one-dimensional array that can accommodate a two-dimensional array and so on. – Vlad from Moscow Oct 10 '17 at 18:40
  • @VladfromMoscow: Then the size of the structure or the array would be my `N`: `struct A instance; char *p = malloc( sizeof *p * sizeof instance ); if (p) memcpy( p, &instance, sizeof instance);`, etc. – John Bode Oct 10 '17 at 18:48
  • It can be that in the moment of executing this statement char *p = malloc( sizeof *p * sizeof instance ); there is no any instance. Moreover this statement is awful for readers. Take into account that it is not necessary that the call of malloc is used in a declaration. So nobody knows what type of p. – Vlad from Moscow Oct 10 '17 at 18:51
3

The following is wrong, compiler should complain about types:

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

This is also wrong for another reason (here pt does not actually point to anything usable):

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

A pointer to T is a variable of type T * that may contain address of some memory that may contains elements of type T:

+------+
|      | pointer to T 
+------+
    |
    v
+-------------+-------------+-------------+
|             |             |             | elements of type T
+-------------+-------------+-------------+ 

For example, in C, to obtain what is drawn, you can write:

int *pi;
pi = malloc(sizeof(int)*3);

If you have a pointer to pointer to T then then diagram could be something like:

+------+
|      | pointer to pointer to T 
+------+
    |
    v
+------+------+------+
|      |      |      | pointers to T 
+------+------+------+
    |      |      |     +-------------+-------------+-------------+
    |      |      +---->|             |             |             | elements of type T
    |      |            +-------------+-------------+-------------+ 
    |      |     +-------------+-------------+
    |      +---->|             |             | elements of type T
    |            +-------------+-------------+ 
    |
    v
+-------------+-------------+-------------+-------------+
|             |             |             |             | elements of type T
+-------------+-------------+-------------+-------------+ 

and the code could be:

int **ppi;
ppi = malloc(sizeof(int *)*3);
ppi[0] = malloc(sizeof(int)*3);
ppi[1] = malloc(sizeof(int)*2);
ppi[2] = malloc(sizeof(int)*4);

Of course, malloc could fail and return value should be tested against failure.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
0

Using the casting you helped the compiler to find an error in this code snippet

int** pt; 

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

For example the error message can look like

 error: assignment from incompatible pointer type [-Werror=incompatible-pointer-types]
  pt = (int*) malloc(sizeof(int)*10);
     ^

Without the casting the compiler could accept this evidently invalid code because the return type of the function malloc is void * and a pointer of the type void * may be assigned to a pointer to object of any other type.

That is in the right side of the assignment the evaluated expression has the type int * while in the left side of the assignment there is an object of the type int ** and there is no implicit conversion from the type int * to the type int **.

This code snippet

int** pt; 

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

is invalid by another reason. The pointer pt is not initialized by a valid address of an object. It has either indeterminate value if the pointer has automatic storage duration or NULL if the pointer has static storage duration. In any case its dereferencing results in undefined behavior.

So it would be correctly to write

int* pt; 
^^^^^^^
pt = (int*) malloc(sizeof(int)*10);

However this construction

int** pt; 

//...

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

can be made valid in some context.

Let's assume that you declared a pointer

int *pt;

and want to initialize it in a function. In this case you have to pass the pointer to the function by reference. Otherwise the function will deal with a copy of the pointer and in this case the original pointer will not be assigned in the function.

So the corresponding code snippet can look as it is shown in the demonstrative program

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

size_t f( int **pt )
{
    const size_t N = 10;

    *pt = (int*) malloc( sizeof( int ) * N );

    if ( *pt )
    {
        int value = 0;
        for ( size_t i = 0; i < N; i++ ) ( *pt )[i] = value++;
    }

    return *pt == NULL ? 0 : N;
}

int main( void )
{
    int *pt;

    size_t n = f( &pt );

    if ( n )
    {
        for ( size_t i = 0; i < n; i++ ) printf( "%d ", pt[i] );
        putchar( '\n' );
    }

    free( pt );
}

The program output is

0 1 2 3 4 5 6 7 8 9
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • can't you pass pointer to f ( ) like f(pt) from main (). If not , why not? – user2979872 Oct 10 '17 at 20:17
  • @user2979872 In fact I described why it is impossible in my answer. Can you elaborate what exactly is unclear? – Vlad from Moscow Oct 10 '17 at 20:53
  • the thing that is not clear is the difference between f(pt) and f(&pt). As you declared pt as int *pt, I am wondering what is the difference between pt and &pt. If i understood correctly, &pt means the address of pt and pt points to the address of pointee (only when assigned like pt = &a where a is declared as int a = 5). Otherwise pt points to no valid address. Please correct me if i am wrong – user2979872 Oct 10 '17 at 21:06
  • @user2979872 pt and &pt are different types and this is written in the answer. So the compiler will issue an error because the type of the argument does not correspond to the type of the parameter. We are going to change the original pt in the function. So we have to pass it by reference otherwise the function will deal with a copy of the pt and the copy (not the original pt) will be change in the function. – Vlad from Moscow Oct 10 '17 at 21:24
  • @user2979872 I am sorry but I do not understand what you mean in the comment. The expression &pt yields the address of the variable pt itself. A pointer is an object as any other objects. So you can apply operator & to get its address. – Vlad from Moscow Oct 10 '17 at 21:37
  • First thank you for your patience. All i am trying to understand is the difference between &pt and pt itself without any * or & – user2979872 Oct 10 '17 at 22:55