First of all, let's deal with a fact "In C there is nothing called pass by reference".
But yes we emulate that with what is available in C, that is pass by value.
So in the first case there are things that happened
- You call the function like this - read_matrix(a, n);
 
- A copy of the variable is there in - read_matrix.
 
- We make changes to the copy. 
- Function ends and story ends there. That local variable's scope ends. It is not alive now. 
- You come back to - main(). Do you expect to see any change in- n? Nope. Because all change was there in- read_matrix.
 
Now to change that we write it this way.
void read_matrix(int a[MAX][MAX], int* nn)
{
    if( scanf("%d", nn) != 1){
        fprintf(stderr,"Error in input");
        exit(1);
    }
    for (size_t i = 0; i < *nn; i++)
        for (size_t j = 0; j < *nn; j++)
            if( scanf("%d", &a[i][j]) != 1){
                fprintf(stderr,"Error in input");
                exit(1);
            }
}
Now here also couple of things happened
- You pass the - &nor the address of- n.
 
- Then - nnis a local variable (local to- read_matrix) containing the address of- n.
 
- scanf()wants the address of what it will write something.
 
- We pass the address of - nor content of- nn.
 
- Then - scanf()writes where? In the memory of- n. So- nin- main()changes.
 
- Now we want to get while looping the value of - nor the value in address of- nor the value in the address contained by- nn.
 
- So we use - *on- nn. Or- *nn.
 
- Now you back to - main()and the local variable- nnis not there and it's lifetime ended when the function- read_matrixended.
 
- You see the changed value in - n.
 
Do you see any difference in two method?
In one we pass the value. In another we pass as value the address of n.
Both are pass by value. But in second method due to the side effect of accessing memory we see he change in main(). Story ends.