Search code examples
cpointersstructvoid-pointersmemset

Pointers, structs and memset in C


I have been learning C for a few days now without any other programming experience, so I might not be clear when asking my question. It is mostly about pointers. For convenience purposes I named the variables so no one gets confused.

#include <stdio.h>
#include <string.h>

struct car {            
        char* name;
        int speed;
        float price;
};

void fun(struct car* p); 

int main(void) {

        struct car myStruct = { .name = "FORD", .speed = 55, .price = 67.87 };
        
        fun(&myStruct);

        printf("%d\n", myStruct.name == NULL);
        printf("%d\n", myStruct.speed == 0);
        printf("%d\n", myStruct.price == 0.0);

        return(0);
}


void fun(struct car* p) {
        memset(p, 0, sizeof(p));
}       

This is my code.

I declare the struct car type globally, so it can be seen by other functions.

I write a function prototype that takes an argument of type struct car* and stores a copy of the argument into the parameter p that is local to the function.

Later, I write the actual function body. As you can see, I call the memset function that is in the string.h header. According to Linux man pages, it looks like this void* memset(void* s, int c, size_t n);.

What the memset function does in this case, is it fills the first sizeof(struct car* p) bytes of the memory area pointed to by the struct car* p with the constant byte c, which in this case is 0.

In the main function I initialize the myStruct variable of type struct car and then call the function fun and pass the address of myStruct into the function. Then I want to check whether all of the struct car "data members" were set to 0 by calling the printf function.

The output I get is

1
0
0

It means that only the first "data member" was set to NULL and the rest weren't.

On the other hand, if I call the memset function inside the main function, the output I get is

1
1
1

If I understand pointers correctly (it's been a few days since I've first heard of them, so my knowledge is not optimal), struct car myStruct has its own address in memory, let's say 1 for convenience.

The parameter struct car* p also has its own address in memory, let's say 2 and it stores (points to) the address of the variable struct car myStruct, so to the 1 address, because I passed it to the function here fun(&myStruct);

So by dereferencing the parameter p, for example (*p).name, I can change the value of the "data member" variable and the effects will be seen globally, because even though the p parameter is only a copy of the original myStruct variable, it points to the same address as the myStruct variable and by dereferencing the pointer struct car* p, I retrieve the data that is stored at the address the pointer points to.

So (*p).name will give me "FORD" and (*p).name = "TOYOTA" will change the data both locally in the function fun and globally in other functions as well, which is impossible without creating a pointer variable, if I do p.name = "TOYOTA", it changes only the value of the copy, that has its own address in the memory that is different from the address of the original struct variable, of the "data member" variable name locally, inside the function fun. It happens, because in this case I operate only on the copy of the original myStruct variable and not on the original one.

I think that in C there is only pass by value, so essentially every parameter is only a copy of the original variable, but pointers make it so that you can pass the address of the original variable (so it's like "passing by reference", but the copy is still made regardless, the thing is that then the function operates on the original address instead of on the parameter's address).

What I don't know is, why the memset function only changes the first "data member" variable to NULL and not all of them ?

void fun(struct car* p) {
        memset(p, 0, sizeof(p));
        p->name = NULL;
        p->speed = 0;
        p->price = 0.0;
}       

If I do this then it changes all the values to NULL, 0, 0, but I don't know, if it is a good practice to do that as it is unnecessary in this case, because I explicitly initialize all the "data members" in the struct with some value.

void fun(struct car* p) {
        memset(&p, 0, sizeof(p));
}

This also works and gives NULL, 0, 0. So maybe I should actually pass &s into the function instead of s, but I don't know how this works. The function void* memset(void* s, int c, size_t n); takes void* as the argument and not void**, the latter is understandable, because:

struct car myStruct = { .name = "FORD", .speed = 55, .price = 67.87 }; // It has its own address in memory and stores the struct at this address
struct car* p = &myStruct; // It points to the address of myStruct and retrieves the data from there when dereference happens, so first it goes to address 1 and then gets the data from this address
void** s = &p; // It points to the address of p and retrieves the data from there when double dereference happens, so it first goes to address 2 and gets the data and the data is address 1, then it goes to address 1 and gets the data, so the struct

But void* means pointer to void, so to any data type. It confuses me why void* s = &p; works, even though p itself is a pointer, so s should be a pointer to pointer to void, so void** s instead of void* s.

Also the memset function returns a pointer to the memory area s, so if s = &p and p = &myStruct, then it returns a pointer to the memory area of the struct, so a pointer to &myStruct. Maybe that's why it works.


Solution

  • In this call

    memset(p, 0, sizeof(p));
    

    you are setting to 0 only a part of object of the structure that is equal to the size of the pointer p.

    Instead you need to write

    memset(p, 0, sizeof(*p));
    

    that is to set the whole object of the structure type with 0.

    Pay attention to as the variable p is a pointer then this record

    p.name = "TOYOTA";
    

    is just syntactically incorrect.

    This function

    void fun(struct car* p) {
            memset(&p, 0, sizeof(p));
    }
    

    does not set the passed object of the structure type through the pointer p to zeroes. Instead it sets to zeroes the memory occupied by the local variable p itself.

    As for this question

    But void* means pointer to void, so to any data type. It confuses me why void* s = &p; works, even though p itself is a pointer, so s should be a pointer to pointer to void, so void** s instead of void* s.

    then according to the C Standard (6.3.2.3 Pointers_

    1 A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

    So you can write for example

    struct car myStruct = 
    { 
        .name = "FORD", .speed = 55, .price = 67.87 
    };
    
    struct car *p = &myStruct;
    
    void *q = &p;
    

    and then

    ( *( struct car ** )q )->name = "My Ford";