How to Actually Learn C (2027 Edition)

Cette vidéo propose un guide pratique pour réécrire la commande ls en C, en implémentant les fonctionnalités de base (ls, ls -a, ls -l) tout en expliquant les concepts fondamentaux du langage C et les appels système Linux.

Voir la source

SYNTHÈSE STRUCTURÉE

Structure de base d’un programme C

Le présentateur commence par créer un programme minimal : int main(void) { return 0; }. Il explique que main retourne un entier, et que 0 signifie succès par convention. La compilation se fait avec gcc -Wall -Wextra -o lsbtw ls.c, les flags -Wall et -Wextra activant les avertissements du compilateur pour détecter les erreurs potentielles. Il insiste : « Ces deux flags de warning font que le compilateur nous dit quand on fait quelque chose de stupide. »

Gestion des arguments avec argc et argv

Pour que le programme accepte des arguments, la signature de main devient int main(int argc, char *argv[]). argc compte le nombre de mots tapés par l’utilisateur, et argv est un tableau de chaînes. Le présentateur montre comment gérer le cas où aucun argument n’est fourni : si argc > 1, le chemin est argv[1], sinon il utilise "." (le répertoire courant). Il souligne que argv[0] est toujours le nom du programme.

Ouverture d’un répertoire avec opendir

Pour lister un répertoire, il faut utiliser opendir() de <dirent.h>. Cette fonction retourne un pointeur de type DIR *, décrit comme « un ticket de vestiaire de l’OS » : on n’a pas besoin de connaître son contenu interne. Le code vérifie si opendir a échoué avec if (!dir) { perror("opendir"); return 1; }. La fonction perror() lit la variable globale errno et affiche un message d’erreur lisible.

Lecture des entrées avec readdir

La fonction readdir() retourne une structure struct dirent * contenant le champ d_name (le nom du fichier). On l’appelle en boucle jusqu’à ce qu’elle retourne NULL :

struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
    printf("%s\n", entry->d_name);
}

Le présentateur note que la flèche -> est un raccourci pour déréférencer un pointeur et accéder à un champ : « C’est tellement fréquent en C qu’ils ont inventé cette notation pour qu’on arrête de taper les parenthèses. »

Filtrage des fichiers cachés

Par défaut, ls n’affiche pas les fichiers commençant par un point. Le code ajoute donc une condition dans la boucle :

if (entry->d_name[0] == '.')
    continue;

Cela saute les entrées dont le premier caractère est un point.

Fermeture du répertoire

Pour éviter une fuite de descripteurs de fichiers, il faut appeler closedir(dir) après la boucle. Le présentateur prévient : « Si on oublie de le faire, le programme va fuir des descripteurs de fichiers. C’est une petite fuite, mais une mauvaise habitude. »

Parsing des options avec getopt

Pour supporter les flags comme -a et -l, le présentateur utilise getopt() de <unistd.h>. Cette fonction parse les arguments en boucle et retourne le caractère de l’option trouvée. Un switch case gère chaque option :

while ((opt = getopt(argc, argv, "al")) != -1) {
    switch (opt) {
        case 'a':
            show_all = 1;
            break;
        case 'l':
            long_format = 1;
            break;
        default:
            fprintf(stderr, "Usage: %s [-a] [-l] [path]\n", argv[0]);
            return 1;
    }
}

La variable globale optind donne l’index du premier argument non-option, ce qui remplace la vérification manuelle de argc.

Implémentation de ls -l avec stat

Le flag -l nécessite d’afficher les permissions, le nombre de liens, le propriétaire, le groupe, la taille, la date et le nom. Tout cela provient de l’appel système stat() (ou lstat() pour les liens symboliques). Le présentateur crée une fonction print_long() qui :

  1. Construit le chemin complet avec snprintf() (pour éviter les débordements de buffer).
  2. Appelle lstat() pour remplir une structure struct stat.
  3. Convertit le champ st_mode en chaîne de permissions via une fonction mode_string() utilisant des macros comme S_ISDIR(), S_IRUSR, etc.
  4. Récupère le nom d’utilisateur avec getpwuid(st.st_uid) et le groupe avec getgrgid(st.st_gid), avec un fallback sur "?" si la recherche échoue.
  5. Formate la date avec strftime() après avoir converti st_mtim.tv_sec via localtime().

Gestion des erreurs avec perror

Chaque appel système (opendir, lstat) est suivi d’une vérification d’erreur. En cas d’échec, perror() affiche un message explicite et le programme retourne un code non nul. Par exemple, si on tape ./lsbtw /inexistant, le programme affiche opendir: No such file or directory.

CONCEPTS CLÉS

  • DIR * : Type opaque représentant un flux de répertoire, manipulé uniquement via opendir, readdir, closedir.
  • struct dirent : Structure contenant les informations d’une entrée de répertoire, notamment d_name.
  • struct stat : Structure remplie par stat() ou lstat(), contenant les métadonnées d’un fichier (type, permissions, taille, timestamps, etc.).
  • getopt() : Fonction standard pour parser les options en ligne de commande, évitant d’écrire un parseur manuel.
  • perror() : Fonction qui affiche un message d’erreur système basé sur la variable errno.
  • lstat() vs stat() : lstat() ne suit pas les liens symboliques, contrairement à stat(). Pour ls -l, on utilise lstat() pour afficher les informations du lien lui-même.

CONCLUSION

Cette vidéo montre qu’implémenter une commande système comme ls en C est accessible, même pour un débutant, à condition de maîtriser les appels système fondamentaux (opendir, readdir, stat) et les fonctions de la bibliothèque standard (getopt, perror, strftime). Le présentateur insiste sur l’importance de lire les pages de manuel (« C’est la page la plus importante de toute la vidéo ») et de gérer correctement les erreurs et les fuites de ressources. Le message principal est que la réécriture d’outils classiques en C (ou en Rust) n’est pas un exercice vain : elle permet de comprendre en profondeur le fonctionnement du système et d’améliorer ses compétences en programmation système.

Du même canal

Tout voir