4

I have the following code

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

typedef struct {
    int age;
} data;

int storage (data **str) {
    *str = malloc(4 * sizeof(**str));
    (*str)[0].age = 12;
    return 0;
}

int main() {
    data *variable = NULL;
    storage(&variable);
    return 0;
}

I took it from a website source. I think I have a misunderstanding about a basic pointer to pointer concept because here in this code, we have a pointer to a struct, variable, and we are passing this to storage function, which expects pointer to pointer of struct type. After memory was malloced, I don't understand this assignment

(*str)[0].age = 12

It was assigned as if, str was of (*)[] type. I dont understand how this assignment works, like str is now a pointer to an array of structs?

25
  • 1
    Have you looked up what the &-operator does? Commented Mar 22, 2016 at 2:54
  • gives the address of the variable Commented Mar 22, 2016 at 2:56
  • 1
    When I say that a[b] is equivalent to *(a+b), I mean equivalent. The "direction" in which you exchange one of these expressions with the other does not matter. So your belief that a[b] can only be used if a is an array is false. In fact, by the time a[b] is evaluated, a is no longer treated as an array, but as a pointer. Commented Mar 22, 2016 at 3:33
  • 2
    @EOF if a is null pointer then a + 0 causes undefined behaviour Commented Mar 22, 2016 at 3:53
  • 1
    We all learn new things about C all the time. For example, @M.M, C11 draft standard n1570 6.5 Expressions 6 The effective type of an object for an access to its stored value is the declared type of the object, if any. 87) Allocated objects have no declared type. Why would you say that there is an array allocated by malloc()? Commented Mar 22, 2016 at 4:36

4 Answers 4

2

First, a note about C syntax for dereferencing pointers:

a[b] is equivalent to *(a + b), is equivalent to *(b + a), is equivalent to b[a].

Now, in

int main() {
    data *variable = NULL;
    storage(&variable);
    return 0;
}

variable is of type "pointer to data", therefore its address &variable is of type "pointer to pointer to data". This is passed to int storage(data **str), and is the correct type for the argument str.

int storage (data **str) {
    *str = malloc(4 * sizeof(**str));
    (*str)[0].age = 12;
    return 0;
}

Here, str is dereferenced, yielding an lvalue of type data * designating the same object as main()s variable. Since it is an lvalue, it can be assigned to.

malloc() allocates memory without declared type, large enough (and sufficiently aligned) to contain four contiguous objects of type data. It returns a pointer to the beginning of the allocation.

(*str)[0] is now an lvalue designating an object of type data, and by accessing the memory malloc() allocated through this expression, the effective type of the memory becomes data. (*str)[0].age = 12; assigns the value 12 to the age-member of this object, leaving the other members of the struct (and the rest of the allocated memory) uninitialized.

Sign up to request clarification or add additional context in comments.

Comments

2

It can be illustrated like this

main:
 data* variable = NULL;   //variable is a pointer
 storage(&variable)       //the address of the pointer is &variable 

the storage(data**) allows the function to take the address of the pointer variable

this allows storage to change what variable points to

In storage, the following statement changes what variable points to by dereferencing (since we did pass the address of variable):

*str = malloc(4 * sizeof(**str) )

The malloc allocates a memory block containing four structs (which each has the size sizeof(struct data) bytes)

A struct is just a convenient way to access a part of memory, the struct describes the layout of the memory. The statement

(*str)[0].age = 12;

is the equivalent of

data* d = *str; 
d[0].age = 12;

or you can write it as a ptr with offset:

data* d = *str;
*(d + 0).age = 12;

edit: a clarification about malloc

malloc returns a block of memory allocated in bytes, the return type of malloc is void* so per definition it has no type and can be assigned to a pointer of arbitrary type:

T* ptr = malloc(n * sizeof(T));

After the assignment to ptr, the memory is treated as one or more elements of type T by using the pointer T*

6 Comments

In your statement, when you say that malloc allocates an array of 4 structs, that is where I get confused. As @EOF pointed in his comments, I understood the part where i can treat a[b] is equivalent to *(a+b) equivalent, I figured I could deduce **str expression to an array to first element, but after reading your answer, i am confused still. Malloc is just allocating memory space, how do you figure it is an array of 4 structs? The way I read it, size is just 4*sizeof(struct)
@Semantics: Well, malloc() doesn't allocate an array. It allocates memory with no declared type. However, by the rules of C11 6.5 Expressions, 6 effective type, once you've used the memory for a particular type, it becomes that type. Therefore, you can treat the pointer returned by malloc() as a pointer to the first element of an array.
@EOF Yes exactly thats what I needed to know. This is the information that I was looking for. Post it as an answer if you can and I will be happy to accept it. or if Anders can include this info as well in his answer
@Semantics the return type of malloc is void* so it has no type, it is just a chunk of memory. The pointer type is the one that decides how the memory should be addressed e.g. calculating the offset of bytes when you do ptr + 1
@AndersK. The memory allocated by malloc() does not become the type the pointer it is assigned to points to. It is only the act of storing an object of the type that causes the effective type of the memory to be determined. See C11 draft standard n1570, 6.5 Expressions, 6 effective type
|
0

Well, I think your code is simply identical to:

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

typedef struct
{
    int age;
} data;

int main()
{
    data *variable = NULL;
    variable = malloc(4 * sizeof(*variable));
    *(variable + 0).age = 12;
    return 0;
}

So variable is malloced with a block of memory, which is large enough to hold 4 datas(from variable[0] to variable[3]). That's all.

Comments

0

This piece of code might help illustrate what's happening, the really interesting line is

assert(sizeof(**str2) == sizeof(data));

Your numbers may vary form mine but first lets create a struct with a rather dull but hard to fake size for testing purposes.

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

typedef struct {
  uint8_t  age;
  uint8_t  here_as_illustartion_only[1728];
} data;

int main() {
    data    str; 
    data *  str1 = &str;  
    data ** str2 = &str1;  
    printf("sizeof(str)    =%*zu\n", 5, sizeof(str));
    printf("sizeof(str1)   =%*zu\n", 5, sizeof(str1));
    printf("sizeof(str2)   =%*zu\n", 5, sizeof(str2));
    printf("sizeof(*str2)  =%*zu\n", 5, sizeof(*str2));
    printf("sizeof(**str2) =%*zu\n", 5, sizeof(**str2));

    assert(sizeof(**str2) == sizeof(data));
    return 0;
}

On my machine this prints the following

sizeof(str)    = 1729
sizeof(str1)   =    8
sizeof(str2)   =    8
sizeof(*str2)  =    8
sizeof(**str2) = 1729

Note the size of the pointer to pointer ie sizeof(**) is the dull number we're looking for.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.