Ti posto il codice che funziona, con i commenti.
Allora, innanzitutto se imposti fine come N-1, nella condizione devi mettere "minore o uguale" di fine, altrimenti ti fermi prima della fine.
Secondo, se n<k (non uguale, perché parte da 0) va controllato sempre, perché è una condizione che puoi raggiungere dopo ogni assegnazione.
Terzo, alla fine di ogni ciclo, la variabile "mobile" si trova nella condizione di fine ciclo, quindi fa aggiustata. L'altra va incrementata per spostarsi nella riga (o colonna) successiva (o precedente).
Quarto, i e j vanno resettate correttamente alla fine.
#include<stdio.h>
#include<stdlib.h>
#define N 5
void stampa_matrice(int matrice[N][N]);
int main()
{
int matrice[N][N];
int i=0;
int j=0;
int n=0;
int inizio=0;
int fine= N-1;
int k = N*N;
while(n<k)
{
while(j<=fine && n<k) //Esce dal ciclo con j>fine
{
matrice[i][j++] = n++;
}
j--; //j>fine, va decrementato!
i++; //mi sposto sulla riga successiva
while(i<=fine && n<k) //Esce dal ciclo con i>fine
{
matrice[i++][j] = n++;
}
i--; //i>fine, va decrementato!
j--; //Mi sposto nella colonna precedente
while(j>=inizio && n<k) //Esce dal ciclo con j<inizio
{
matrice[i][j--] = n++;
}
j++; //j<inizio, va incrementato!
i--; //Mi sposto alla riga precedente
while(i>inizio && n<k)
{
matrice[i--][j] = n++;
}
fine--;
inizio++;
//reset i e j
i=inizio;
j=inizio;
}
stampa_matrice(matrice);
return EXIT_SUCCESS;
}
Quando stampi viene meglio con la tabulazione
printf("%d\t", matrice[i][j]);