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.
2_pi
invalide, car commence par un chiffrex_2
validex___3
validex 2
invalide, car comporte un espacepositionRobot
valide, notation camelCasepiece_presente
valide, notation snake_case_commande_vanne
valide-courant_sortie
invalide, un identificateur ne peut pas commencer par le signe-
_alarme_
validepanne#2
invalide, le caractère#
n'est pas autoriséint
invalide,int
est un mot réservé du langagedéfaillance
invalide, uniquement les caractères imprimable ASCII sont autorisésf'
invalide l'apostrophe n'est pas autoriséeINT
valide
Chapitre 3¶
Exercice 3.2 - Affectation de variables¶
Ligne |
Instruction |
|
|
|
|
---|---|---|---|---|---|
1 |
|
5 |
? |
? |
? |
2 |
|
5 |
? |
? |
? |
3 |
|
5 |
? |
5 |
? |
4 |
|
6 |
? |
5 |
? |
5 |
|
6 |
? |
6 |
12 |
6 |
|
6 |
12 |
12 |
12 |
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
i = (k = 2) + (j = 3)
i = 5
j = 3
k = 2
i = (k = 2) + (j = 2) + j * 3 + k * 4
Résultat indéterminé
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 :
Indication
Depuis un terminal Python vous pouvez simplement utiliser int("12030", 4)
Exercice 4.8 - Additions binaires¶
1 + 51
¹¹ 1₂ + 110011₂ (2⁵ + 2⁴ + 2¹+ 2⁰ ≡ 51) ---------- 110100₂
51 - 7
…¹¹¹ ¹¹ …000110011₂ (2⁵ + 2⁴ + 2¹ + 2⁰ ≡ 51) + …111111001₂ (complément à deux) 2³ + 2¹ + 2⁰ ≡ 111₂ → !7 + 1 ≡ …111001₂) ----------- …000101100₂ (2⁵ + 2³ + 2₂ ≡ 44)
204 + 51
11001100₂ + 110011₂ ----------- …011111111₂ (2⁸ - 1 ≡ 255)
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}\)):
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)
);
true
true
false
true
true
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¶
Le développeur s'attend à obtenir le pourcentage de bonnes pièces avec plusieurs décimales après la virgule.
En pratique, il obtient un entier, c'est à dire toujours 0.
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.
Une possible correction consiste à forcer le type d'un des membres de la division :
Chapitre 7¶
Exercice 7.9 - Cas appropriés¶
Le cas est circonscrit à un intervalle de valeur donnée, le
if
est approprié :if (i > min && i < max) { /* ... */ }
Dans ce cas un switch semble le plus approprié
switch(choice) { case 0 : /* ... */ break; case 1 : /* ... */ }
À 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` }
La boucle
for
semble ici la plus adaptéefor (size_t i = 100; i < 200; i += 2) { /* ... */ }
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¶
|
|
|
|
Remarque |
---|---|---|---|---|
1 |
|
|
|
|
2 |
|
|
|
|
3 |
|
|
|
|
4 |
|
|
|
|
5 |
|
|
|
|
6 |
|
|
|
|
7 |
|
|
|
|
8 |
|
|
|
|
9 |
|
|
|
|
10 |
|
|
|
|
11 |
|
|
|
|
12 |
|
|
|
|
13 |
|
|
|
|
14 |
|
|
|
|
15 |
|
|
|
Le chiffre 8 interdit en octal provoque un arrêt |
|
|
|||
16 |
|
|
||
17 |
|
|
||
18 |
|
|
||
19 |
|
|
Exercice 9.5 - Chaînes de formats¶
Saisir 3 caractères consécutifs dans des variables
i
,j
,k
.scanf("%c%c%c", &i, &j, &k);
Saisir 3 nombres de type float séparés par un point-virgule et un nombre quelconque d'espaces dans des variables
x
,y
etz
.scanf("%f ;%f ;%f", &x, &y, &z);
Saisir 3 nombres de type double en affichant avant chaque saisie le nom de la variable et un signe
=
, dans des variablest
,u
etv
.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¶
Ligne 6
C est un langage impératif, l'ordre est séquentiel du haut vers le bas
Les étapes sont les suivantes :
Demande de la valeur de
a
à l'utilisateurDemande de la valeur de
b
à l'utilisateurDemande de la valeur de
x
à l'utilisateurCalcul de l'image affine de
x
(équation de droite)Affichage du résultat
Que verra l'utilisateur à l'écran ?
Il verra
y = 12
poura = 2; x = 5; b = 2
Quelle est l'utilité de ce programme ?
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
etb
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] < mid
où mid
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>
.