1

I have a question dedicated to:

void* malloc (size_t size);

In the regular example that can be found on millions of sites over the internet it's shown that the right way to use malloc is the following:

int main()
{
   int* num;
   num = malloc(sizeof(int));

   *num = 10;
   printf("Value = %d\n", *num);
   
   free(num);

   return 0;
}

But If I want to allocate memory within a function and use it in main like below, then the only option is to implement the function the following way:

void func_alloc(int** elem, int num_value)
{
    *elem = malloc(sizeof(int));
    **elem = num_value;
}

int main()
{
    int* num;
    func_alloc(&num, 10);
    free(num);

    return 0;
}

I assumed by mistake, that such code as below would work:

void func_alloc(int* elem, int num_value)
{
    elem = malloc(sizeof(int));
    *elem = num_value;
}

int main()
{
    int* num;
    
    func_alloc(num, 10);
    free(num);

    return 0;
}

Could you please explain or maybe give a link to resource with explanation why does it work only this way?

I really cannot understand why do I need double pointer as an input parameter and why in the other case it comes to "segmentation fault"...

Thank in advance ;)

anastaciu
  • 23,467
  • 7
  • 28
  • 53
alex
  • 53
  • 4
  • 1
    *"the only option"* - Not quite. You can always return the pointer, rather than take an output parameter. – StoryTeller - Unslander Monica Feb 15 '21 at 21:50
  • 1
    Because C uses [pass by value](https://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value). – Nate Eldredge Feb 15 '21 at 21:53
  • C passes arguments by value. In the last snippet `elem` is just a local variable. Changing it has no effect on `num` in `main`. So `free(num)` is trying to free a pointer that hasn't been assigned a value. Enable the compiler warnings, and the compiler will tell you that. – user3386109 Feb 15 '21 at 21:54

3 Answers3

1

I assumed by mistake, that such code as below will work.

In C, the arguments are passed by value, when you pass a pointer as an argument of a function, you are passing the value of the pointer, basically a copy of it, not the pointer itself, malloc will change the value of that pointer, but since what you passed was a copy, that is what's changed, not the original pointer, that one remains unchanged.


In the second code snippet, the working code, *elem = malloc(sizeof(int)); broadly means make this pointer elem point to this valid memory address given to me by malloc(assuming it succeeds), the value of the pointer to the pointer elem which you passed as an argument remains unchanged, it being a copy doesn't matter because it's not changed, it's still the same address that was passed as argument, the address of the pointer num which is now pointing to the memory location given by malloc.

**elem = num_value means store num_value in the address stored in the pointer where elem is pointing to, which is where num is pointing to, which is the new memory block previously given by malloc.


That being said, it's not the only option, you can use a local pointer, return it and assign it to another local pointer in the caller side, this is still a copy, but it's a copy of the changed pointer:

int *func_alloc(int num_value)
{
    int *elem = malloc(sizeof *elem); //more idiomatic

    if(elem == NULL){        // check for allocation errors
        perror("malloc" );
        exit(EXIT_FAILURE);
    }

    *elem = num_value;
    return elem;
}

int main()
{
    int* num = func_alloc(10);
    free(num);
    return EXIT_SUCCESS;
}

Footnote:

In the third code snippet, freeing num, given that it is uninitialized is a bad idea, I assume you know as much, nonetheless I thought I'd mention it. This may be the reason for the segfault you experienced, whatever garbage value num has will be assumed to be valid memory address, and free will try to deallocate it, doing this will invoke undefined behavior. If it was NULL, it's a different story, it's well defined behavior (execept in some very old standars). Initializing variables when they are declared is, in most cases, a good idea.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
0

A commented explanation :

void func_alloc(int* elem, int num_value)
{
    /* elem points to address gave by malloc, let's say 0x12345678 */
    elem = malloc(sizeof(int));

    /* at address 0x12345678 you have now your num_value */
    *elem = num_value;

    /* end of the function. Changes made to parameters passed by value are lost */
}

int main()
{
    int* num;
    
    /* num is a pointer to an address you could not have write access to, you actually don't know */

    func_alloc(num, 10);
    /* As C arguments are passed by value, changes made into the function are lost */

    /* You try to free num which is still a pointer to an address you potentially have no access to => SEGFAULT */
    free(num);

    return 0;
}

EDIT: Not shown in this example, but it is good practice to always check that pointer returned by malloc is not NULL, otherwise you should exit without trying to assign a value to the pointer.

Peartree
  • 13
  • 2
-1

If you have:

#include <stdio.h>

void foo(int x)
{
    x = 9;
}

int main(void)
{
    int a = 1;
    foo(a);
    printf("%d\n", a);
}

you probably don't expect the value of a in main() to change just because foo() assigned to x, right? It doesn't change, because parameters are assigned by value. The variables x in foo(), and a in main() are two different variables.

The same applies in your code. elem in func_alloc() is a different variable from num in main(), and assigning to the former doesn't change the value of the latter. The fact that these two are of type int *, and not e.g. just int, makes no difference in this.

That said, you can also return the pointer you got from malloc(), e.g.

int *alloc_int(int value)
{
    int *p = malloc(sizeof(int));
    *p = value;
    return p;
}

(not that it seems to make much sense for a mere int.)

ilkkachu
  • 6,221
  • 16
  • 30