Buckle in, this is going to take a few minutes.
When you call a function with arguments (parameters), there are several different evaluation strategies for how the function handles those arguments. The one used by C is known as "call by value". When you call a function like
foo( x, y, z );
each of the expressions x, y, and z is fully evaluated and the results of those evaluations are what get passed to the function. In the function definition
void foo( int a, int b, int c )
{
a = b + c;
}
each of the formal parameters a, b, and c are different objects in memory from x, y, and z - they receive the values of x, y, and z, but the update to a does not affect x in any way. Here's an example that illustrates this directly:
#include <stdio.h>
void foo( int a, int b, int c )
{
a = b + c;
printf( "a = %d\n", a );
}
int main( void )
{
int x = 1;
int y = 2;
printf( "before foo: x = %d, y = %d\n" );
foo( x, y, x + y );
printf( "after foo: x = %d, y = %d\n" );
return 0;
}
When run, this code gives the output:
before foo: x = 1, y = 2
a = 5
after foo: x = 1, y = 2
As you can see, updating a has no effect on the value of x.
In order for a function to change the value of a parameter, we must pass a pointer to that parameter. If we want changes in a to be reflected in x, we must pass the address of x in the function call using the unary & (address-of) operator and dereference it in the function definition using the unary * (dereference) operator:
#include <stdio.h>
void foo( int *a, int b, int c )
{
*a = b + c;
printf( "*a = %d\n", *a );
}
int main( void )
{
int x = 1;
int y = 2;
printf( "before foo: x = %d, y = %d\n", x, y );
foo( &x, y, x + y );
printf( "after foo: x = %d, y = %d\n", x, y );
return 0;
}
In this case, a doesn't receive the value of x, but rather its address, giving us this situation:
a == &x // int * == int *
*a == x // int == int
The expression *a in foo acts as kind of a synonym for x in main, so writing a new value to *a is equivalent to writing a new value to x.
And this, ultimately, is why you need to pass pointers to the things you want to update when you call scanf (or any other function that needs to update a parameter).
NOTE - arrays are weird. Unless it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" will be converted, or "decay", to an expression of type "pointer to T" and the value of the expression will be the address of the first element of the array. In other words, if you have code like this:
int arr[10];
...
bar( arr );
the compiler replaces the expression arr in the function call with an expression equivalent to &arr[0], so instead of bar getting a copy of the entire array, it gets the address of the first element:
void bar( int *a )
{
...
}
This is why you don't use the & operator when reading strings with scanf - you're passing an array expression as an argument, which is implicitly converted to a pointer before the call:
int val;
char name[10];
scanf( "%d %s", &val, name );
There is a reason for this behavior, but that's a bit beyond the scope of this discussion. Just remember that arrays are weird.