> Interfaçage assembleur C : conventions d'appel, asm inline GCC, lier un fichier .s avec du code C et passage d'arguments par registres, avec exemples.

*Source : https://coder-studio.com/tutoriels/interfacage-assembleur-c/*

---

[// Assembleur](https://coder-studio.com/tutoriels/)

# Interfaçage entre assembleur et C

Par **Kévin Papot** · publié le 16 juin 2026

Mélanger **assembleur et C** dans un même programme reste utile aujourd’hui : optimiser une boucle critique, accéder à une instruction matérielle précise, ou comprendre ce que génère réellement le compilateur. Ce tutoriel, modernisé à partir d’un article historique de Coder Studio, explique comment les deux mondes communiquent : conventions d’appel, assembleur inline GCC, et liaison d’un fichier `.s` avec du code C. Les exemples ciblent x86-64 sous Linux avec GCC.

## La clé : les conventions d’appel

Pour qu’une fonction C appelle une routine en assembleur (ou l’inverse), les deux doivent s’accorder sur **où passent les arguments et où revient le résultat**. C’est le rôle de la _convention d’appel_ (ABI). Sur x86-64 System V (Linux, macOS), les règles essentielles sont :

-   les **6 premiers arguments entiers/pointeurs** passent dans les registres, dans l’ordre : `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9` ;
-   la **valeur de retour** entière revient dans `rax` ;
-   les registres `rbx`, `rbp`, `r12`–`r15` doivent être **préservés** par la fonction appelée (_callee-saved_) : si vous les modifiez, vous devez les restaurer avant de rendre la main ;
-   les autres (`rax`, `rcx`, `rdx`, `rsi`, `rdi`, `r8`–`r11`) sont _caller-saved_ : l’appelant ne compte pas sur leur préservation.

Ignorer ces règles, c’est écraser une valeur dont le C avait besoin et provoquer un plantage difficile à diagnostiquer.

## Méthode 1 : l’assembleur inline avec GCC

La façon la plus légère d’insérer de l’assembleur est de l’écrire **directement dans le C** via `asm`. La syntaxe étendue de GCC permet de relier des variables C aux registres, sans gérer soi-même la convention d’appel.

```
#include <stdio.h>

int main(void) {
    int a = 20, b = 22, somme;

    // addition en assembleur inline
    asm volatile (
        "addl %[y], %[x]"     // x = x + y
        : [x] "+r" (a)         // sortie : a, en registre, lu et écrit (+)
        : [y] "r"  (b)         // entrée : b, en registre
        :                      // pas de registre supplémentaire écrasé
    );
    somme = a;

    printf("Somme = %d\n", somme);  // 42
    return 0;
}
```

Décryptons la structure : `asm volatile ( "instructions" : sorties : entrées : clobbers )`.

-   Le mot-clé **`volatile`** empêche le compilateur de déplacer ou supprimer le bloc s’il croit qu’il est inutile (essentiel dès qu’il y a un effet de bord).
-   Les **opérandes** `[nom] "contrainte" (variable_C)` font le pont. `"r"` demande un registre ; `"+r"` signifie « registre lu **et** écrit ».
-   La liste des **clobbers** (après le troisième `:`) déclare les registres que votre code modifie hors des opérandes. Si vous touchez à la mémoire de façon opaque, ajoutez `"memory"`.

Cette approche est parfaite pour quelques instructions ponctuelles.

## Méthode 2 : lier un fichier .s séparé

Pour une routine plus conséquente, on l’écrit dans un fichier assembleur `.s` autonome, puis on la déclare côté C comme une fonction externe. Le C ne voit qu’un **prototype** ; l’éditeur de liens fait le reste.

Le fichier assembleur, `carre.s` :

```
    .text
    .globl carre          # rend le symbole visible pour l'éditeur de liens
carre:                    # int carre(int n)
    movl %edi, %eax       # 1er argument (n) dans edi -> eax
    imull %eax, %eax      # eax = eax * eax
    ret                   # valeur de retour dans eax
```

Le fichier C, `main.c` :

```
#include <stdio.h>

// prototype : l'implémentation est en assembleur
int carre(int n);

int main(void) {
    printf("carre(7) = %d\n", carre(7));  // 49
    return 0;
}
```

La compilation et l’édition de liens en une commande :

```
gcc main.c carre.s -o programme
```

GCC assemble `carre.s`, compile `main.c`, puis lie les deux. Le symbole `carre` déclaré `.globl` dans l’assembleur correspond exactement au prototype C.

## Le passage d’arguments en détail

Reprenons `carre` à la lumière des conventions. La fonction reçoit son unique argument `n` dans **`edi`** (la moitié basse 32 bits de `rdi`, puisque `n` est un `int`). Le résultat doit repartir dans **`eax`** (moitié basse de `rax`). D’où :

```
movl %edi, %eax   # copie n (edi) vers eax
imull %eax, %eax  # eax devient n*n
ret               # eax est la valeur de retour
```

Pour une fonction à deux arguments `int f(int a, int b)`, `a` arriverait dans `edi` et `b` dans `esi`. Cette correspondance registre ↔ argument est le contrat qui rend l’interfaçage fiable — et c’est aussi ce qui explique pourquoi un désassemblage vous montre ces registres partout.

## Un piège fréquent : préserver les registres

Si votre routine assembleur utilise un registre _callee-saved_ (par exemple `rbx`), vous **devez** le sauvegarder puis le restaurer :

```
ma_routine:
    pushq %rbx        # on sauvegarde rbx avant de l'utiliser
    # ... calculs utilisant rbx ...
    popq  %rbx        # on le restaure avant de rendre la main
    ret
```

Oublier ce `push`/`pop`, c’est corrompre un registre dont l’appelant C attendait la valeur intacte — un bug typique et déroutant de l’interfaçage bas niveau.

## Quand y recourir (et quand s’abstenir)

L’interfaçage assembleur-C garde son intérêt pour : accéder à des instructions spécialisées (cryptographie, SIMD), optimiser un point chaud mesuré, ou écrire du code très proche du matériel. En revanche, pour du code ordinaire, les compilateurs modernes optimisent souvent mieux qu’un humain : n’écrivez de l’assembleur qu’après avoir **mesuré** un besoin réel, et documentez-le soigneusement — c’est le code le plus difficile à maintenir.

* * *

**À lire ensuite**

-   [Le langage assembleur](https://coder-studio.com/tutoriels/langage-assembleur/) — comprendre registres, pile et instructions avant de les interfacer
-   [Les bases du langage C](https://coder-studio.com/tutoriels/langage-c/) — pointeurs et compilation, le socle côté C
-   [Le préprocesseur C et les énumérations](https://coder-studio.com/tutoriels/preprocesseur-c-enumerations/) — d’autres mécanismes bas niveau du C
