Search code examples
cpass-by-referencevariable-assignmentpass-by-valuepass-by-pointer

C dynamic allocation of an array under struct inside a function


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?


Solution

  • "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
        }
    }