The p in main is a different object in memory than the p in foo. Writing to one has no effect on the other. If you want foo to update the value of p in main, then you must pass a pointer to p:
#include <stdio.h>
int main()
{
int i = 97, *p = &i;
foo(&p);
printf("%d ", *p);
}
void foo(int **p)
{
int j = 2;
*p = &j;
printf("%d ", **p);
}
WARNING - doing this will invoke undefined behavior, since p will point to an object that no longer exists - once foo exits, j no longer exists, and p will be an invalid pointer. You may get the output you expect, or you may not.
In order for a function to write to a parameter, you must pass a pointer to that parameter:
void foo( T *ptr )
{
*ptr = new_value(); // updates the thing ptr points to
}
void bar( void )
{
T var; // var is an instance of T
foo( &var ); // have foo update the value of var
}
This is true for any non-array type T, including pointer types. If we replace T with the pointer type P *, we get
void foo( P * *ptr )
{
*ptr = new_value(); // updates the thing ptr points to
}
void bar( void )
{
P * var; // var is an instance of P *
foo( &var ); // have foo update the value of var
}
The semantics are exactly the same - the only thing that's changed is that var starts out as a pointer type.
Things get weird with array types, which we're not going to go into quite yet.