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.
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";