Newer
Older
% Programmation séquentielle en C
% Base II - Inspirés des slides de F. Glück
% 25 septembre 2019
# Représentation des variables en mémoire (1/N)
## La mémoire
- La mémoire est un ensemble de bits.
- Elle est accessible via des adresses.
+------+----------+----------+------+----------+------+------+
| bits | 00110101 | 10010000 | .... | 00110011 | .... | .... |
+======+==========+==========+======+==========+======+======+
| addr | 2000 | 2001 | .... | 4000 | .... | .... |
+------+----------+----------+------+----------+------+------+
- Elle est gérée par le système d'exploitation...
- et fournie à chaque programme pendant son exécution.
- Elle est séparée en deux parties: **la pile** et **le tas**.
## Une variable
- Une variable, `type a = valeur`{.C}, possède:
- un type (`char`{.C}, `int`{.C}, ...),
- un contenu (une séquence de bits qui encode `valeur`{.C}),
- une adresse mémoire (accessible via `&a`{.C}).
# Représentation des variables en mémoire (2/N)
{#fig:memory width=100%}
# Les pointeurs (1/N)
- Un poiteur est une adresse mémoire.
```C
type *id;
```
- Pour pouvoir interprêter le contenu de ce qu'il pointe, il doit être typé.
- Un pointeur n'est rien d'autre qu'un entier (64bit sur x86-64, soit 8 octets).
- Un poiteur peut être **déréférencé**: on accède à la valeur sur laquelle pointe l'adresse mémoire.
```C
char val = 'a';
char *c = &val; // c contient l'adresse de val (pointe vers 'a')
char d = *c; // d contient une copie de 'a'
```
# Les pointeurs (2/N)
{#fig:memory width=100%}
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# Les fonctions (1/N)
- Chaque partie indépendante d'un programme se met dans une fonction.
- Syntaxe:
```C
type identificateur(paramètres) { // variables optionnelles
instructions;
return expression; // type donne le type d'expression
}
```
- Exemple:
```C
int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
int main() {
int c = max(4, 5);
}
```
# Les fonctions (2/N)
- Il existe un type `void`{.C}, "sans type", en C.
- Il peut être utilisé pour signifier qu'une fonction ne retourne rien, ou qu'elle n'a pas d'arguments.
- `return`{.C} utilisé pour sortir de la fonction.
- Exemple:
```C
void show_text(void) { // second void optionnel
printf("Aucun argument et pas de retour.\n");
return; // optionnel
}
void show_text_again() { // c'est pareil
printf("Aucun argument et pas de retour.\n");
}
```
# Les fonctions (3/N)
## Prototypes de fonctions
- Le prototype donne la **signature** de la fonction, avant qu'on connaisse son implémentation.
- L'appel d'une fonction doit être fait **après** la déclaration du prototype.
```C
int max(int a, int b); // prototype
int max(int a, int b) { // implémentation
if (a > b) {
return a;
} else {
return b;
}
}
```
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# Les fonctions (4/N)
## Arguments de fonctions
- Les arguments d'une fonction sont toujours passés **par copie**.
- Les arguments d'une fonction ne peuvent **jamais** être modifiés.
```C
void set_to_two(int a) { // a est une nouvelle variable
// la valeur de a une copie de celle passée en argument
// lorsque la fonction est appelée, ici -1
a = 2; // la valeur de a est fixée à 2
} // a est détruite
int main() {
int x = -1;
set_to_two(x); // -1 est passé en argument
// x vaudra toujours -1 ici
}
```
# Les fonctions (5/N)
## Arguments de fonctions: pointeurs
- Pour modifier un variable, il faut passer son **adresse mémoire**.
- L'adresse d'une variable, `x`{.C}, est accédé par `&x`{.C}.
- Un **pointeur** vers une variable entière a le type, `int *x`{.C}.
- La sytaxe `*x`{.C} sert à **déréférencer** le pointeur.
```C
void set_to_two(int *a) {
// a contient une copie de l'adresse de la
// variable passée en argument
*a = 2; // on accède à la valeur pointée par a,
// et on lui assigne 2
} // le pointeur est détruit, pas la valeur pointée
int main() {
int x = -1;
set_to_two(&x); // l'adresse de x est passée
// x vaudra 2 ici
}
```
<!-- TODO quiz;
```C
void set_to_two(int *a) {
a = 2;
}
int main() {
int x = -1;
set_to_two(&x);
}
void add_two(int *a) {
*a += 2;
}
int main() {
int x = -1;
add_two(&x);
}
void add_two(int a) {
a += 2;
printf("%d", a);
}
int main() {
int x = -1;
add_two(&x);
}
``` -->
- Point d'entrée du programme.
- Retourne le code d'erreur du programme:
- 0: tout s'est bien passé.
- Pas zéro: problème.
- La valeur de retour peut être lue par le shell qui a exécuté le programme.
- `EXIT_SUCCESS`{.C} et `EXIT_FAILURE`{.C} (de `stdlib.h`) sont des valeurs de retour **portables** de programmes C.
# La fonction `main()` (2/N)
## Exemple
```C
int main() {
// ...
if (error)
return EXIT_FAILURE;
else
return EXIT_SUCCESS;
}
```
- Le code d'erreur est lu dans le shell avec `$?`{.bash}
```bash
$ ./prog
$ echo $?
0 # tout s'est bien passé par exemple
$ if [ $? -eq 0 ]; then echo "OK" ; else echo "ERROR"; fi
ERROR # si tout s'est mal passé
```
- `C` offre uniquement des tableaux statiques
- Un tableau est un "bloc" de mémoire contiguë associé à un nom
- taille fixe déterminée à la déclaration du tableau
- la taille ne peut pas être changée.
- Pas d’assignation de tableaux.
- Un tableau déclaré dans une fonction ou un bloc est détruit à la sortie de celle/celui-ci
- $\Rightarrow$ Un tableau local à une fonction ne doit **jamais être retourné** (aussi valable pour toute variable locale)!
- Les éléments d’un tableau sont accédés avec `[i]`{.C} où `i`{.C} est l’index de l’élément.
- Le premier élément du tableau à l’index `0`{.C}!
- Lorsqu’un tableau est déclaré, la taille de celui-ci doit toujours être spécifiée, sauf s’il est initialisé lors de sa déclaration.
# Les tableaux (2/N)
## Exemple
```C
float tab1[5]; // tableau de floats à 5 éléments
// ses valeurs sont indéfinies
int tab2[] = {1, 2, 3}; // tableau de 3 entiers,
// taille inférée
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
int val = tab2[1]; // val vaut 2 à présent
int w = tab1[5]; // index hors des limites du tableau
// comportement indéfini!
// pas d'erreur du compilateur
```
<!-- TODO QUIZ:
```C
int a1[5]; // OK
int a2[] = { 1, 2, 3 }; // OK
int a3[4][5]; // OK
int [] a4; // Erreur
int a5[]; // Erreur
int[] function(void) { // Erreur
int array[5]; // OK
return array; // Erreur
}
void foo(int a[]) { // OK
a[3] = 0; // OK
}
void bar(void) {
int a[5]; // OK
foo(a); // OK
a = a5; // Erreur
}
``` -->
<!-- ```C
#include <stdio.h>
int main(void) {
char i;
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42;
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
``` -->
```C
int x[10];
for (int i = 0; 0 < 10; ++i) {
x[i] = 0;
}
int j = 0;
while (j < 10) {
x[j] = 1;
j += 1;
}
int j = 0;
do {
x[j] = -1;
j += 1;
} while (j < 9)
## Les tableaux comme argument
- Un tableau n'est rien d'autre que le pointeur vers sa première case.
- Pas moyen de connaître sa taille: `sizeof()`{.C} inutile.
- Quand on passe un tableau en argument à une fonction: toujours spécifier sa taille.
```C
void foo(int tab[]) { // sans taille...
for (int i = 0; i < ?; ++i) { // on sait pas
printf("tab[%d] = %d\n", i, tab[i]);
}
}
// avec taille, [n] pas obligatoire
void bar(int n, int tab[n]) { // n doit venir avant tab ici