Home > Software design >  C dynamic allocation of an array under struct inside a function
C dynamic allocation of an array under struct inside a function

Time:01-24

I have a struct that will contain some dynamic-allocated array.

I have written the following code, and it works but I do not understand why it does work.

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

struct Tray {
  int *parr;
};

int allocateTray(int n, struct Tray *tray) {
  tray->parr = calloc(n, sizeof(*tray->parr));

  for (int i = 0; i<n; i  ) {
    tray->parr[i] = i 1;
  }
}

int main() {
  struct Tray tray = {NULL}, *ptray;
  int         n;

  n = 5;

  ptray = &tray;

  allocateTray(n, ptray);

  for (int i = 0; i<n; i  ) {
    printf("ptray->parr[%d] = %d \n", i, ptray->parr[i]);
  }

  return 0;
}

With a array (not inside a struct), even if I allocate arr inside a function with argument int *arr, it does not give main the allocated array, and it forces one to use double pointer there.

But in this case, I just used a pointer to a struct, and it worked. I was thinking that I should use something like double pointer to a struct.

Why in this case it works only with a single pointer?

CodePudding user response:

You don't need a "double pointer"; you need a pointer to the object you want to modify. If you want the function to modify a pointer object, you need to pass a pointer to that object. If you want the function to modify a structure, you need to pass a pointer to that object.

And that's exactly what your code does. You want to modify main's tray, so you pass a pointer to main's tray. So your code works.

The following demonstrates this.


First, let's look at the code that doesn't work.

void fac( int *ac ) {
   ac = malloc( sizeof( int ) );
}

int main( void ) {
   int *a = NULL;
   fac( a );
   printf( "%p\n", (void *)a );   // XXX `a` isn't modified.
}

This code modifies ac, but it does not modify a. It does not achieve what we want it to achieve. To modify a, we would need to pass the address of a.

void fap( int **ap ) {
   *ap = malloc( sizeof( int ) );
}

int main( void ) {
   int *a = NULL;
   fap( &a );
   printf( "%p\n", (void *)a );   // XXX `s` isn't modified.
}

Here, rather than modifying the parameter, we modify *ap which is a. This code does achieve what we want it to achieve.


Now, let's introduce structs.

typedef struct { int *a } S;

void fsc( S sc ) {
   sc.a = malloc( sizeof( int ) );
}

int main( void ) {
   S s = { .a = NULL };
   fsc( s );
   printf( "%p\n", (void *)(s.a) );   // Ok. `s` is modified.
}

This code modifies sc, but it does not modify s. It does not achieve what we want it to achieve. To modify s, we would need to pass the address of s.

typedef struct { int *a } S;

void fsp( S *sp ) {
   sp->a = malloc( sizeof( int ) );   // `sp->a` means `(*sp).a`
}

int main( void ) {
   S s = { .a = NULL };
   fsp( &s );
   printf( "%p\n", (void *)(s.a) );   // Ok. `s` is modified.
}

Here, rather than modifying the parameter, we modify *sp which is s. This code does achieve what we want it to achieve.


Full demo:

#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct { int *a; } S;

void fac( int *ac ) {
   ac = malloc( sizeof( int ) );
}

void fap( int **ap ) {
   *ap = malloc( sizeof( int ) );
}

void fsc( S sc ) {
   sc.a = malloc( sizeof( int ) );
}

void fsp( S *sp ) {
   sp->a = malloc( sizeof( int ) );   // `sp->a` means `(*sp).a`
}

int main( void ) {
   int *a = NULL;
   fac( a );
   printf( "%p\n", (void *)a );       // XXX `a` isn't modified.

   fap( &a );
   printf( "%p\n", (void *)a );       // Ok. `a` is modified.

   S s = { .a = NULL };
   fsc( s );
   printf( "%p\n", (void *)(s.a) );   // XXX `a` isn't modified.

   fsp( &s );
   printf( "%p\n", (void *)(s.a) );   // Ok. `s` is modified.
}

Warnings:

<source>: In function 'fac':
<source>:10:16: warning: parameter 'ac' set but not used [-Wunused-but-set-parameter]
   10 | void fac( int *ac ) {
      |           ~~~~~^~
<source>: In function 'fsc':
<source>:18:13: warning: parameter 'sc' set but not used [-Wunused-but-set-parameter]
   18 | void fsc( S sc ) {
      |           ~~^~

Output:

(nil)
0x158a2d0
(nil)
0x158a310

CodePudding user response:

To change an object in a function you need to pass it by reference instead of passing by value.

In C passing by reference means passing an object indirectly through a pointer to it. Thus dereferencing the passed pointer you will get a direct access to the object pointed to by the pointer and can change it.

With a array (not inside a struct), even if I allocate arr inside a function with argument int *arr, it does not give main the allocated array, and it forces one to use double pointer there.

This means that the pointer is passed to the function by value. That is the function deals with its local variable (parameter) that was initialized by the value of the passed pointer. Changing the local variable does not changes the original pointer used as an argument of the function.

Consider a simple program

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

void f( int *p )
{
    p = malloc( sizeof( int ) );
}

int main( void )
{
    int *a = NULL;

    f( a );

    printf( "a == NULL is %s\n", a == NULL ? "true" : "false" );
}

You can imagine the function definition and its call the following way

f( a );

//...

void f( /* int *p */ )
{
    int *p = a;
    p = malloc( sizeof( int ) );
}

As you can see the function changes its local variable p that was initialized by the value of the pointer a passed to the function. The original pointer a stays unchanged.

If you want to change the pointer a declared in main within the function you need to pass it by reference. That is the program will look the following way

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

void f( int **p )
{
    *p = malloc( sizeof( int ) );
}

int main( void )
{
    int *a = NULL;

    f( &a );

    printf( "a == NULL is %s\n", a == NULL ? "true" : "false" );

    free( a );
}

Now dereferencing the pointer p within the function

*p = malloc( sizeof( int ) );

you get direct access to the original pointer a and thus can change it.

As for your first program then the pointer parr declared within the structure Tray

struct Tray {
  int *parr;
};

is passed to the function by reference through a pointer to an object of the structure type.

  struct Tray tray = {NULL}, *ptray;
  //...
  ptray = &tray;

  allocateTray(n, ptray);

That is dereferencing the pointer ptray within the function you get direct access to the original object of the structure type and can change (any) its data member.

CodePudding user response:

"even if I allocate arr inside a function with argument int *arr, it does not give main the allocated array"

In general, whatever object a function will be expected to modify when passed via it's parameter list requires that the object's address be passed, not the object itself. To illustrate, for any type T:

For T s if s is to be changed, argument of function prototype should be; void func(T *s);
With calling example being

T s = 0;
func(&s);

For T *s if *s is to be changed, argument of function prototype should be; void func(T **s);
With calling example being

T *s = 0;
func(&s);

For T **s if **s is to be changed, argument of function prototype should be; void func(T ***s);
With calling example being

T **s = 0;
func(&s);  

And so on... (Note the apparent similarity in calling convention for each.)

Example - the following code will fail to change the value of its argument:

int main(void)
{
    int x = 0;//object to be changed
    change_x(x);//passing object directly via argument
                //x is returned unchanged
    return 0;
}

void change_x(int x)
{
    x = 10;//within this function only will x now contain 10
}

But this example passes address and is able to change the value:

int main(void)
{
    int x = 0;//object to be changed
    change_x(&x);//passing the address of the object to be changed
    return 0;
}

void change_x(int *x)
{
    *x = 10;//access to the object via its address allows change to occur 
}

"I was thinking that I should use something like double pointer to a struct."

Yes, as an argument in a function prototype that would work when needing to change the contents of memory pointed to by a pointer object.
With a pointer (to any object) that needs to be modified in a function, the same is true, its address must be passed, not the pointer itself. This then would require the argument for that function to accommodate a pointer to a pointer. A simple example using a struct with int members as well as a int * member:

typedef struct {
    int a;
    int b;
    int *parr;
}val_s;    

void change_val(val_s **v, size_t num_parr);

int main(void)
{
    val_s *val = NULL;
    int num = 10;
    change_val(&val, num);//passing address to a pointer
    val->a = 10;
    val->b = 20;
    for(int i = 0;i < num; i  ) val->parr[i] = i;
    //once finished using memory, 
    //free it in the reverse order in which it was allocated
    free(val->parr);
    free(val);
    return 0;
}

void change_val(val_s **v, size_t num)//note only top level pointer address needs be send
{                                     //member pointers, whether allocated or not are 
    (*v) = malloc(sizeof(val_s));     //relative to memory of top level object
    if(*v)
    {
        (*v)->parr = malloc(num*sizeof (*v)->parr);//allocate memory to member pointer
    }
}

   

CodePudding user response:

Gerhardh's comment is correct: you are using a double pointer.

Consider:

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

struct Tray {
  int *parr;
};

void allocateTray(int n, struct Tray *tray) {
  tray->parr = calloc(n, sizeof(*tray->parr));

  for (int i = 0; i<n; i  ) {
    tray->parr[i] = i 1;
  }
}

int main() {
  int *tray = NULL, **ptray;
  int         n;

  n = 5;

  ptray = &tray;

  allocateTray(n, (struct Tray*)ptray);

  for (int i = 0; i<n; i  ) {
    printf("ptray[%d] = %d \n", i, (*ptray)[i]);
  }

  return 0;
}
  • Related