Oui, ça marche aussi sans pointeur, mais pour le coup, c'est la méthode que je suis le moins suceptible d'utiliser en C (alors que dans l'absolu, c'est celle là que j'utilise le plus vu que je code beaucoup en ML/Haskell ces derniers temps).
Bref, ça nous donne 3 méthodes :
1) void empiler(pile*, element)
très efficace en mémoire car tu n'as qu'une seule copie de ta pile (= son état courant)
gestion de la mémoire facile (il n'y a qu'une pile à désallouer à la fin, le reste aura été géré par empiler/depiler au fur et à mesure)
pas de persistance (= si tu te rends à un moment que tu n'as pas empiler/dépiler ce qu'il faut, tu ne peux pas revenir en arrière facilement/gratuitement)
2) pile* empiler(pile*, element)
persistance gratuite, vu que tu renvoies une nouvelle pile à chaque opération
possibilité de faire du partage de données entre les piles pour éviter d'exploser en mémoire et en temps à cause des moultes copies
gestion de la mémoire complexe (ça n'a plus de sens de faire du nettoyage dans depiler car ça casserait la possibilité d'annuler l'opération, donc la persistance... du coup, il faut mettre en place une gestion de la mémoire beaucoup plus évoluée)
3) pile empiler(pile, element)
soit pile a été défini (via un typedef) comme étant un type de pointeur vers quelque chose, et dans ce cas c'est juste la même chose que pour le 2)
soit on manipule vraiment les valeurs et non des adresses (références), et dans ce cas c'est moralement mal, et potentiellement couteux à cause de copies induites lors des appels de fonctions.
c'est comme ça qu'on coderait dans un langage fonctionnel évolué comme Ocaml ou Haskell parce que le compilo se charge tout seul de mettre des pointeurs/références là où il faut, que le partage de données est fait automatiquement, et que la gestion de la mémoire est faite à l'exécution par un garbage collector.
ça reste malgré tout un peu moins efficace que le 1) lorsqu'on fait une utilisation intensive sans persistance de piles.