Solution des exercices

Vous trouvez ici les solutions de la plupart des exercices du livre. Certains ne comprennent pas de solution, volontairement pour qu'ils puissent être faits en classe ou qu'il suscite chez le lecteur davantage d'attention.

Chapitre 1

Exercice 1.1 - Standardisation

Au paragraphe §5.2.4.2.1-1 on peut lire que ULLONG_MAX est encodé sur 64-bits et donc que sa valeur est \(2^{64}-1\) donc 18'446'744'073'709'551'615.

Exercice 1.2 - Eclipse

Eclipse est un IDE. Il n'intègre donc pas de chaîne de compilation et donc aucun compilateur.

Exercice 1.3 - Pêche

Il suffit pour cela de se rendre sur le site de Stackoverflow et d'accéder à la liste des tags. En 2019/07 il y eut 307'669 questions posées.

Seriez-vous capable de répondre à une question posée?

Exercice 1.4 - Auteurs

Brian Kernighan et Dennis Ritchie en 1972

Exercice 1.5 - Standardisation

Le standard industriel, malgré que nous soyons en 2019 est toujours ISO/IEC 9899:1999, car peu de changements majeurs ont été apportés au langage depuis et les entreprises préfèrent migrer sur C++ plutôt que d'adopter un standard plus récent qui n'apporte que peu de changements.

Exercice 1.6 - Paradigmes

C supporte les paradigmes impératifs, structurés et procédural.

Exercice 1.7 - Langage impératif

La programmation impérative consiste en des séquences de commandes ordonnées. C'est-à-dire que les séquences sont exécutées dans un ordre définis les unes à la suite d’autres.

Exercice 1.8 - Coulée de lave

Lorsqu'un code immature est mis en production, l'industriel qui le publie risque un retour de flamme dû aux bogues et mécontentement des clients. Afin d'éviter une coulée de lave il est important qu'un programme soit testé et soumis à une équipe de beta-testing qui s'assure qu'outre le respect des spécifications initiales, le programme soit utilisable facilement par le public cible. Il s'agit aussi d'étudier l'ergonomie du programme.

Un programme peut respecter le cahier des charges, être convenablement testé, fonctionner parfaitement, mais être difficile à l'utilisation, car certaines fonctionnalités sont peu ou pas documentées. La surcharge du service de support par des clients perdus peut également être assimilée à une coulée de lave.

Exercice 1.9 - Cat

cat est un programme normalisé POSIX prenant en entrée un fichier et l'affichant à l'écran. Il est utilisé notamment dans cet ouvrage pour montrer que le contenu du fichier hello.c est bel et bien celui attendu.

Chapitre 2

Exercice 3.1 - Validité des identificateurs

Une excellente approche serait d'utiliser directement l'expression régulière fournie et d'utiliser l'outil en ligne regex101.com.

  1. 2_pi invalide, car commence par un chiffre

  2. x_2 valide

  3. x___3 valide

  4. x 2 invalide, car comporte un espace

  5. positionRobot valide, notation camelCase

  6. piece_presente valide, notation snake_case

  7. _commande_vanne valide

  8. -courant_sortie invalide, un identificateur ne peut pas commencer par le signe -

  9. _alarme_ valide

  10. panne#2 invalide, le caractère # n'est pas autorisé

  11. int invalide, int est un mot réservé du langage

  12. défaillance invalide, uniquement les caractères imprimable ASCII sont autorisés

  13. f' invalide l'apostrophe n'est pas autorisée

  14. INT valide

Chapitre 3

Exercice 3.2 - Affectation de variables

Ligne

Instruction

a

b

c

x

1

a = 5;

5

?

?

?

2

b = c;

5

?

?

?

3

c = a;

5

?

5

?

4

a = a + 1;

6

?

5

?

5

x = a - ++c;

6

?

6

12

6

b = c = x;

6

12

12

12

7

x + 2. = 7.;

Exercice 3.4 - Affectations simples

p ≡ 2
x ≡ 7
n ≡ 7

Exercice 3.5 - Trop d'égalités

Selon la table de priorité des opérateurs, on note :

  • () priorité 1 associativité à droite

  • * priorité 3 associativité à gauche

  • + priorité 4 associativité à droite

  • = priorité 14 associativité à gauche

En revanche rien n'est dit sur les point de séquences. L'opérateur d'affectation n'est pas un point de séquence, autrement dit le standard C99 (Annexe C) ne définit pas l'ordre dans lequel les assignations sont effectuées.

Ainsi, seul le premier point possède une solution, les deux autres sont indéterminés

  1. i = (k = 2) + (j = 3)

    • i = 5

    • j = 3

    • k = 2

  2. i = (k = 2) + (j = 2) + j * 3 + k * 4

    • Résultat indéterminé

  3. i = (i = 3) + (k = 2) + (j = i + 1) + (k = j + 2) + (j = k - 1)

    • Résultat indéterminé

Exercice 3.6 - Verbosité

Une règle de programmation: le nom identifieurs doit être proportionnel à leur contexte. Plus le contexte de la variable est réduit, plus le nom peut être court. Le même programme pourrait être écrit comme suit :

for (size_t i; i < nelems; i++)
    elem[i] = i;

Un consensus assez bien établi est qu'une variable commençant par n peut signifier number of.

Chapitre 4

Exercice 4.1 - Pile ou face

Il transmet un seul 1 bit : équipe A ou pile ou 1, équipe B ou face ou 0. Il faut néanmoins encore définir à quoi correspond cette information.

Exercice 4.2 - Symboles binaires

Le nombre 11001 est composé de 5 positions et de deux symboles possibles par position : 1 et 0. La quantité d'information est donc de 5 bits.

Exercice 4.3 - Deux mains

Deux mains de cinq doigts forment une paire composée de 10 doigts. Il existe donc dix possibilités, la base est donc décimale : 10.

Si plusieurs doigts peuvent être levés à la fois, il faut réduire le système à l'unité de base "le doigt" pouvant prendre deux états : levé ou baissé. Avec dix doigts (dix positions) et 2 symboles par doigts, un nombre binaire est ainsi représenté.

Exercice 4.4 - Base 2

Avec une base binaire 2 et 10 bits, le total représentable est :

\[2^10 = 1024\]

Soit les nombres de 0 à 1023.

Exercice 4.5 - Les chiffres hexadécimaux

0xaaaa    ≡ 43690
0b1100101 ≡   101
0x1010    ≡  4112
129       ≡   129 (n'est-ce pas ?)
0216      ≡   142

Exercice 4.7 - La numération Shadock

Le système Shadock est un système quaternaire similaire au système du génome humain basé sur quatre bases nucléiques. Assignons donc aux symboles Shadocks les symboles du système indo-arabe que nous connaissons mieux :

0 ○ (GA)
1 − (BU)
2 ⨼ (ZO)
3 ◿ (MEU)

Le nombre d'entrée −⨼O◿O peut ainsi s'exprimer :

−⨼○◿○ ≡ 12030₄

En appliquant la méthode du cours, on obtient :

\[1 \cdot 4^4 + 2 \cdot 4^3 + 0 \cdot 4^2 + 3 \cdot 4^1 + 0 \cdot 4^0 = 396_{10}\]

Indication

Depuis un terminal Python vous pouvez simplement utiliser int("12030", 4)

Exercice 4.8 - Additions binaires

  1. 1 + 51

           ¹¹
             1₂
    +   110011₂  (2⁵ + 2⁴ + 2¹+ 2⁰ ≡ 51)
    ----------
        110100₂
    
  2. 51 - 7

      …¹¹¹  ¹¹
      …000110011₂  (2⁵ + 2⁴ + 2¹ + 2⁰ ≡ 51)
    + …111111001₂  (complément à deux) 2³ + 2¹ + 2⁰ ≡ 111₂ → !7 + 1 ≡ …111001₂)
     -----------
      …000101100₂  (2⁵ + 2³ + 2₂ ≡ 44)
    
  3. 204 + 51

        11001100₂
    +     110011₂
     -----------
      …011111111₂  (2⁸ - 1 ≡ 255)
    
  4. 204 + 204 (sur 8-bits)

       ¹|¹  ¹¹
        |11001100₂
    +   |11001100₂
     ---+--------
       1|10011000₂  (152, le résultat complet devrait être 2⁸ + 152 ≡ 408)
    

Exercice 4.9 - De Morgan

Si l'on applique De Morgan (\(\bar{XY} = \bar{X} + \bar{Y}\)):

\[D \cdot E + \bar{D} + \bar{E}\]

Exercice 4.10 - Swap sans valeur intermédiaire

a ^= b;
b ^= a;
a ^= b;

Chapitre 5

Exercice 6.8 - Conversion de types

x = 1e6;
i = x;    // Incorrect, i peut-être limité à -32767..+32767 (C99 §5.2.4.2.1)
j = -20;  // Incorrect, valeur signée dans un conteneur non signé
k = x;
l = k;
k = -20;
l = k;    // Incorrect, valeur signée dans un conteneur non signé

Chapitre 6

Exercice 6.9 - Un casting explicite

p ≡ 2
x = 7.5
n = 8

Exercice 6.10 - Opérateurs de relation et opérateurs logiques

condition = (
    (x >= 0) && (x <= 20) && (y > x))
    ||
    ((y == 50) && (x == 2))
    ||
    (y == 60)
);
  1. true

  2. true

  3. false

  4. true

  5. true

  6. true

Exercice 6.13 - Précision des flottants

Le format float est stocké sur 32-bits avec 23-bits de mantisse et 8-bits d'exposants. Sa précision est donc limitée à environ 6 décimales. Pour représenter 10'000'000.1 il faut plus que 6 décimales et l'addition est donc caduc :

#include <stdio.h>

int main(void) {
    float x = 10000000. + 0.1;
    printf("%f\n", x);
}
$ ./a.out
10000000.000000

Exercice 6.16 - Somme des entiers

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    long long n = atoi(argv[1]);
    long long sum = 0;
    for(size_t i = 0; i < n; i++, sum += i);
    printf("%lld\n", sum);
}

Exercice 6.17 - Système de vision industriel

  1. Le développeur s'attend à obtenir le pourcentage de bonnes pièces avec plusieurs décimales après la virgule.

  2. En pratique, il obtient un entier, c'est à dire toujours 0.

  3. La promotion implicite des entiers peut être découpée comme suit :

    (uint32_t)numerator = (uint32_t)inspected_parts - (uint32_t)bad_parts;
    (uint32_t)percentage = (uint32_t)numerator / (uint32_t)inspected_parts;
    (float)percentage_good_parts = (uint32_t)percentage;
    

    La division est donc appliquée à des entiers et non des flottants.

  4. Une possible correction consiste à forcer le type d'un des membres de la division :

Chapitre 7

Exercice 7.9 - Cas appropriés

  1. Le cas est circonscrit à un intervalle de valeur donnée, le if est approprié :

    if (i > min && i < max) { /* ... */ }
    
  2. Dans ce cas un switch semble le plus approprié

    switch(choice) {
        case 0 :
            /* ... */
            break;
        case 1 :
            /* ... */
    }
    
  3. À reformuler tant que le caractère n'est pas trouvé ou que la fin de la chaîne n'est pas atteinte. On se retrouve donc avec une boucle à deux conditions de sorties.

    size_t pos;
    while (pos < strlen(str) && str[pos] != c) {
        pos++;
    }
    if (pos == strlen(str)) {
        // Not found
    } else {
        // Found `c` in `str` at position `pos`
    }
    
  4. La boucle for semble ici la plus adaptée

    for (size_t i = 100; i < 200; i += 2) {
        /* ... */
    }
    
  5. Il est nécessaire ici d'assurer au moins un tour de boucle :

    const size_t max_line_length = 64;
    char format[32];
    snprintf(format, sizeof(format), "%%%zus", max_line_length - 1);
    unsigned int line = 0;
    char buffer[max_lines][max_line_length];
    do {
        printf("%d. ", line);
    } while (
        scanf(format, buffer[line]) == 1 &&
        strcmp(buffer[line], "STOP") &&
        ++line < max_lines
    );
    

Exercice 7.11 - Esperluette conditionnelle

La priorité de l'opérateur unitaire & est plus élevée que == ce qui se traduit par :

if (x & (mask == bits))

Le développeur voulait probablement appliquer le masque à x puis le comparer au motif bits. La bonne réponse devrait alors être :

if ((x & mask) == bits)

Chapitre 8

Exercice 9.1 - Mot du jour

#include <time.h>
#include <stdlib.h>

char *words[] = {
    "Albédo", "Bigre", "Maringouin", "Pluripotent", "Entrechat",
    "Caracoler" "Palinodie", "Sémillante", "Atavisme", "Cyclothymie",
    "Idiosyncratique", "Entéléchie"};

int main(void)
{
    srand(time(NULL));
    puts(words[rand() % (sizeof(words) / sizeof(char*))]);

    return 0;
}

Chapitre 9

Exercice 9.4 - Saisie de valeurs

Q

i

j

n

Remarque

1

1

2

2

2

1

0

1.

j n'est pas lue car arrêt prématuré sur ,

3

-1

-2

2

4

0

0

0.

i n'est pas lue car arrêt prématuré sur -

5

1

0

1.

6

1

2

2

7

1

23

2

8

1234

56

2

9

123

789

2

10

0

0

0

11

1

2

2

12

1

0

1

13

1

23

2

14

18

42

2

15

10

1

2.

Le chiffre 8 interdit en octal provoque un arrêt

x

n

16

123.

1

17

1.23

1

18

1.23E6

1

19

12

1

Exercice 9.5 - Chaînes de formats

  1. Saisir 3 caractères consécutifs dans des variables i, j, k.

    scanf("%c%c%c", &i, &j, &k);
    
  2. Saisir 3 nombres de type float séparés par un point-virgule et un nombre quelconque d'espaces dans des variables x, y et z.

    scanf("%f ;%f ;%f", &x, &y, &z);
    
  3. Saisir 3 nombres de type double en affichant avant chaque saisie le nom de la variable et un signe =, dans des variables t, u et v.

    printf("t="); scanf("%f", &t);
    printf("u="); scanf("%f", &u);
    printf("v="); scanf("%f", &v);
    

Exercice 9.6 - Bugs

// Incorrect ! Le premier paramètre de printf doit être la chaîne de format.
printf(i);

// Incorrect ! Le premier paramètre de scanf doit être la chaîne de format.
scanf(&i);

// Correct, mais surprenant.
// Cette instruction affichera l’adresse de I, et non pas sa valeur !
printf("%d", &i);

// Incorrect. Le paramètre i est de type short, alors que la chaîne de
// format spécifie un type int. Fonctionnera sur les machines dont le type
// short et int sont identiques
scanf("%d", &i);

// Incorrect, la troisième variable passée en paramètre ne sera pas affichée.
printf("%d%ld", i, j, u);

// Incorrect ! Le premier paramètre est de type short alors que int
// est spécifié dans la chaîne de format.
// Le deuxième paramètre n’est pas passé par adresse, ce qui va
// probablement causer une erreur fatale.
scanf("%d%ld", &i, j);

// Correct, mais étonnant. Affiche l’adresse de la variable u.
printf("%u", &u);

// Incorrect ! Le paramètre est de type unsigned short, alors que
// la chaîne de format spécifie int. Fonctionnera pour les valeurs
// positives sur les machines dont le type short et int sont identiques.
// Pour les valeurs négatives, le résultat sera l’interprétation non
// signée de la valeur en complément à 2.
scanf("%d", &u);

// Correct, mais x est traité comme double.
printf("%f", x);

// Correct.
scanf("%f", &x);

// Correct ! %f est traité comme double par printf !
printf("%f", y);

// Incorrect ! La chaîne de format spécifie float,
// le paramètre passé est l’adresse d’une variable de type double.
scanf("%f", &y);

Exercice 9.7 - Test de saisir correcte

int n;
float x, y, z;
printf("Donnez les valeurs de x, y et z :");
n = scanf("%f%f%f", &x, &y, &z);
if (n != 3)
printf("Erreur de saisie.\n");

Exercice 9.8 - Produit scalaire

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    float x1, y1
    printf("Coordonnées du vecteur v1 séparées par un \";\" :\n");
    scanf("%f ;%f", &x1, &y1);

    float x2, y2;
    printf("Coordonnées du vecteur v2 séparées par un \";\" :\n");
    scanf("%f ;%f", &x2, &y2);

    float dot_product = x1 * x2 + y1 * y2;
    printf("Produit scalaire : %f\n", dot_product);
    if (dot_product == 0.0)
        printf("Les vecteurs sont orthogonaux.\n");
}

Ce programme risque de ne pas bien détecter l’orthogonalité de certains vecteurs, car le test d’égalité à 0 avec les virgules flottantes pourrait mal fonctionner. En effet, pour deux vecteurs orthogonaux, les erreurs de calcul en virgule flottante pourraient amener à un produit scalaire calculé très proche, mais cependant différent de zéro. On peut corriger ce problème en modifiant le test pour vérifier si le produit scalaire est très petit, par exemple compris entre -0.000001 et +0.000001:

if (dot_product >= -1E-6 && dot_product <= 1E-6)

Ce qui peut encore s’écrire en utilisant la fonction valeur absolue :

if (fabs(dot_product) <= 1E-6)

Exercice 9.9 - Crampes de doigts

Une fois la correction effectuée, vous utilisez l'outil de diff pour montrer les différences :

1,3c1,3
<         #include <stdio.h>
<         #include <stdlib.h>
<         int main()
---
>         #include <std_io.h>
>         #jnclude <stdlib.h>
>         INT Main()
5c5
<         int a, b, sum;
---
>         int a, sum;
9c9
<         scanf("%d", &a);
---
>         scanf("%d", a);
14,16c14,16
<         /* Affichage du résultat */
<         sum = a + b;
<         printf("%d + %d = %d\n", a, b, sum);
---
>         /* Affichage du résultat
>         somme = a - b;
>         Printf("%d + %d = %d\n", a, b, sum);
18c18,19
<         return EXIT_SUCCESS;
---
>         return EXIT_FAILURE;
>         }

Exercice 9.10 - Géométrie affine

  1. Ligne 6

  2. C est un langage impératif, l'ordre est séquentiel du haut vers le bas

  3. Les étapes sont les suivantes :

    1. Demande de la valeur de a à l'utilisateur

    2. Demande de la valeur de b à l'utilisateur

    3. Demande de la valeur de x à l'utilisateur

    4. Calcul de l'image affine de x (équation de droite)

    5. Affichage du résultat

  4. Que verra l'utilisateur à l'écran ?

    1. Il verra y = 12 pour a = 2; x = 5; b = 2

  5. Quelle est l'utilité de ce programme ?

    1. Le calcul d'un point d'une droite

Exercice 9.11 - Équation de droite

Citons les défauts de ce programme :

  • Le programme ne peut pas être utilisé avec les arguments, uniquement en mode interactif

  • Les invités de dialogue a = ``, ``b = `` ne sont pas clair, ``a et b sont associés à quoi ?

  • La valeur de retour n'est pas exploitable directement.

  • Le nom des variables utilisé n'est pas clair.

  • Aucune valeur par défaut.

Une solution possible serait :

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    float x;
    float offset;
    float slope;

    if (argc > 2) {
        offset = atof(argv[1]);
        slope = atof(argv[2]);
    } else {
        float offset_default = 0.;
        printf("Offset? [%f]: ", offset_default);
        if (!scanf("%f", &offset)) {
            offset = offset_default;
        }

        float slope_default = 1.;
        printf("Pente? [%f]: ", slope_default);
        if (!scanf("%f", &slope)) {
            slope = slope_default;
        }
    }

    if (argc == 2 || argc > 3) {
        slope = atof(argv[argc == 2 ? 2: 3]);
    } else {
        float x_default = 0;
        printf("x (abscisse) [%f]:", x_default);
        if (!scanf("%f", &x)) {
            x = x_default;
        }
    }

    printf("%f\n", slope * x + offset);

    return 0;
}

Chapitre 10

Exercice 10.1 - Dans la moyenne

double mean(double a, double b, double c) {
    return (a + b + c) / 3.;
}

Exercice 10.2 - Le plus petit

double min(double a, double b, double c) {
    double min_value = a;
    if (b < min_value)
        min_value = b;
    if (c < min_value)
        min_value = c;
    return min_value;
}

Une manière plus compacte, mais moins lisible serait :

double min(double a, double b, double c) {
    return (a = (a < b ? a : b)) < c ? a : c;
}

Exercice 10.3 - Algorithme de retour de monnaie

Voici une solution partielle :

#define TICKET_PRICE 50

void give_coin(unsigned int value) { printf("%d\n", value); }
void give_ticket(void) { printf("ticket\n"); }

bool no_ticket = amount_payed < TICKET_PRICE;

int amount_to_return = amount_payed - TICKET_PRICE;
do {
    while (amount_to_return > 0) {
        if (amount_to_return >= 20 && ncoin_20 > 0) {
            give_coin(20);
            amount_to_return -= 20;
            ncoin_20--;
        } else if (amount_to_return >= 10 && ncoin_10 > 0) {
            give_coin(10);
            amount_to_return -= 10;
            ncoin_10--;
        } else {
            no_ticket = true;
            break;
        }
    }
} while (amount_to_return > 0);

if (!no_ticket) {
    give_ticket();
}

Chapitre 11

Exercice 11.1 - Assignation

int8_t a[50];
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]; i++) {
    a[i] = i;
}

Exercice 11.2 - Première position

int index_of(int *array, size_t size, int search) {
    int i = 0;
    while (i < size && array[i++] != search);
    return i == size ? -1 : i;
}

Exercice 11.4 - Comparaisons

int comp(char a[], char b[], size_t length) {
    int sum_a = 0, sum_b = 0;

    for (size_t i = 0; i < length; i++) {
        sum_a += a[i];
        sum_b += b[i];
    }

    return sum_b - sum_a;
}

Exercice 11.6 - L'index magique

Une solution triviale consiste à itérer tous les éléments jusqu'à trouver l'indice magique :

int magic_index(int[] array) {
    const size_t size = sizeof(array) / sizeof(array[0]);

    size_t i = 0;

    while (i < size && array[i] != i) i++;

    return i == size ? -1 : i;
}

La complexité de cet algorithme est \(O(n)\) or, la donnée du problème indique que le tableau est trié. Cela veut dire que probablement, cette information n'est pas donnée par hasard.

Pour mieux se représenter le problème, prenons l'exemple d'un tableau :

  0   1   2   3   4   5   6   7   8   9   10
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│-90│-33│ -5│ 1 │ 2 │ 4 │ 5 │ 7 │ 10│ 12│ 14│
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                              ^

La première valeur magique est 7. Est-ce qu'une approche dichotomique est possible ?

Prenons le milieu du tableau A[5] = 4. Est-ce qu'une valeur magique peut se trouver à gauche du tableau ? Dans le cas le plus favorable qui serait :

  0   1   2   3   4
┌───┬───┬───┬───┬───┐
│ -1│ 0 │ 1 │ 2 │ 3 │
└───┴───┴───┴───┴───┘

On voit qu'il est impossible que la valeur se trouve à gauche, car les valeurs dans le tableau sont distinctes et il n'y a pas de répétitions. La règle que l'on peut poser est A[mid] < midmid est la valeur médiane.

Il est possible de répéter cette approche de façon dichotomique :

int magic_index(int[] array) {
    return _magic_index(array, 0, sizeof(array) / sizeof(array[0]) - 1);
}

int _magic_index(int[] array, size_t start, size_t end) {
    if (end < start) return -1;
    int mid = (start + end) / 2;
    if (array[mid] == mid) {
        return mid;
    } else if (array[mid] > mid) {
        return _magic_index(array, start, mid - 1);
    } else {
        return _magic_index(array, mid + 1, end);
    }
}

Chapitre 12

Exercice 14.2 - Passage par adresse

a:0, b:105, c:102, d:300, e:102

Chapitre 13

Exercice 15.1 - Arc-cosinus

En cherchant man acos header dans Google, on trouve que la fonction acos est définie dans le header <math.h>.

Une autre solution est d'utiliser sous Linux la commande apropos:

$ apropos acos
acos (3)     - arc cosine function
acosf (3)    - arc cosine function
acosh (3)    - inverse hyperbolic cosine function
acoshf (3)   - inverse hyperbolic cosine function
acoshl (3)   - inverse hyperbolic cosine function
acosl (3)    - arc cosine function
cacos (3)    - complex arc cosine
cacosf (3)   - complex arc cosine
cacosh (3)   - complex arc hyperbolic cosine
cacoshf (3)  - complex arc hyperbolic cosine
cacoshl (3)  - complex arc hyperbolic cosine
cacosl (3)   - complex arc cosine

Le premier résultat permet ensuite de voir :

$ man acos | head -10
ACOS(3)    Linux Programmer's Manual         ACOS(3)

NAME
    acos, acosf, acosl - arc cosine function

SYNOPSIS
    #include <math.h>

    double acos(double x);
    float acosf(float x);

La réponse est donc <math.h>.

Sous Windows avec Visual Studio, il suffit d'écrire acos dans un fichier source et d'appuyer sur F1. L'IDE redirige l'utilisateur sur l'aide Microsoft acos-acosf-acosl qui indique que le header source est <math.h>.