The 2024 Wheel Reinvention Jam just concluded. See the results.

I still don't fully understand & and *

I am not sure if this is the right place to post this.

I thought I understood * but apparently sometimes you ought to use two asterisks which, I mean, why? I get that the ampersand is the address of some instance of a variable; the address being the location in memory. I just don't understand when you'd want to use the ampersand. I was confused already at day 002 when Casey was registering the window. I believe it was something like

1
RegisterClass(&WindowClass)


There's also some general code that goes something like

1
int main(int argc,  char** argv)


Where the char uses two pointers and also, it's not in the name "argv" but rather directly following the char which also confuses me.

Am I just simply stupid and should I just give up?

Anyway, please help me understand. Thank you.

Edited by Theatrical on Reason: Initial post
You're correct that & gets the address of a variable.
This is probably most useful when you want to pass the variable as an argument to a function, for a few different reasons:

1. You want the function to modify the variable.

Everything in C is passed by value, The following function will not change a:
1
2
3
4
5
6
7
void func(int arg){
    arg += 4;
}
int main(int argc, char** argv){
    int a = 0;
    func(a);
}
But you can work around this by passing a pointer to the variable - func can dereference the pointer to get a modifyable "lvalue".
1
2
3
4
5
6
7
8
void func(int* arg){
    *arg += 4;
}
int main(int argc, char** argv){
    int a = 0;
    func(&a);
    printf("%d\n", a); // it's 4
}

2. You're passing a big struct and don't want all that memory to be copied onto the stack frame.

Since everything is passed by value, if you have a struct with 100 uint32_t's inside, that's gonna effectively do a 400 byte memcpy every time you call the function. If you pass a pointer to the struct it'll probably do no copying at all, because the pointer can be passed in a register.

3. Combining 1 and 2, you have a struct and want the func to modify some of its fields.

As for argv, it's an array of character pointers. And since arrays in C are closely related to pointers (and you can't pass a plain array as an argument - they "decay" to pointers), argv appears as a "pointer to pointers to char".

Here is a better visualization for running a program called 'ab' given one argument 'c':


Also whitespace between the *, typename and identifier doesn't matter, it's personal preference whether you use "int* a", "int *a", "int * a" or "int*a", though the last two are ususal.


Edited by Alex Baines on
Thank you so much. One last thing, how come you have to specify that the arg inside the function is a pointer when you did that within the parenthesis?

1
2
3
4
5
6
7
8
void func(int* arg){
    *arg += 4;
}
int main(int argc, char** argv){
    int a = 0;
    func(&a);
    printf("%d\n", a); // it's 4
}


Also, does that mean that rather than

1
void func(int* arg){


I can do

1
 void func(int *arg){


Thank you.
whitespace doesn't matter, so both are valid as is

1
void foo(int*a)
Thank you so much. One last thing, how come you have to specify that the arg inside the function is a pointer when you did that within the parenthesis?

The * inside the function is not specifying that arg is a pointer, but it's used to dereference the pointer, so that the thing you're adding 4 to is the variable whose address is stored in arg.

If you did arg += 4 without the asterisk, it would add 4 to arg itself, meaning arg would point to an address "4 ints further" in memory, that is, assuming type int occupies 4 bytes, you would then point to an address 16 bytes after the location of variable a.
C follows a syntax idea known as "declaration follows use". It's a neat idea--C is old, and they were still figuring out how best to do languages then--but it does cause confusion like this. I think everyone gets confused by this at one point.

So, this is why C programmers tend to prefer
1
int func(int *arg)

and C++ programmers tend to prefer
1
int func(int* arg)


int *arg is declaration follows use: to get an int from the arg variable, you have to dereference it once:
1
int a = *arg;


*arg appears in both places, because the fact that the expression (*arg) gives an int determines the syntax in the declaration.

C++ programmers, who as a rule try to be more modern in their thinking, think of the arg argument as having the type "pointer to int". So, they put the asterisk with the type.

This explains the array declaration syntax, too.
1
int arr[100];

To get an int from arr, you bracket it after arr:
1
int a = arr[0];


Compare this to go:
1
2
3
var arr [100]int

a := arr[0]


The array is with the type, not the variable name.

Newer languages think: you have a variable, its an array, it has a type sized-array-of-sub-type.

C thinks: theres a big load of type somewhere, here's how you get at them.