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}),
- une portée.
{#fig:memory width=100%}
- Pour 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 pointeur peut être **déréférencé**: on accède à la valeur située à l'adresse mémoire sur laquelle il pointe.
char *c; // déclaration pointeur de char
*c = 'a'; // assignation de 'a' dans la valeur pointée par c
c = 1000; // on modifie l'adresse pointée par c
char d = *c; // on essaie de lire la valeur pointée par c... argl!
- `NULL`{.C} (ou `0`{.C}) est la seule adresse **toujours** invalide.
# Les pointeurs (2/N)
{#fig:memory width=100%}
# Les pointeurs (3/N)
- Permettent d'accéder à une valeur avec une indirection.
```C
int a = 2;
int *b = &a;
*b = 7; // on met 7 dans la case pointée par b
// ici a == 7 aussi
a = -2; // ici *b == -2 aussi
```
- Permettent d'avoir plusieurs chemins d'accès à une valeur.
- Lire **et** écrire en même temps dans un bout de mémoire devient possible: **danger**.
# La fonction `sizeof()` (1/N)
- La fonction `sizeof()`{.C} permet de connaître la taille en octets:
- d'une valeur,
- d'un type,
- d'une variable.
- Soit `int a = 2`{.C}, sur l'architecture x86_64 que vaut:
- `sizeof(a)`{.C}?
- `sizeof(&a)`{.C}?
- Soit `char b = 2`{.C},
- `sizeof(b)`{.C}?
- `sizeof(&b)`{.C}?
# La fonction `sizeof()` (2/N)
- Réponses:
- `sizeof(a) == 4`{.C}, `int`{.C} entier 32 bits.
- `sizeof(&a) == 8`{.C}, une adresse est de 64 bits.
- `sizeof(b) == 1`{.C}, `char`{.C} entier 8 bits.
- `sizeof(&b) == 8`{.C}, une adresse est de 64 bits.
- Les parties indépendantes d'un programme.
- Permettent de modulariser et compartimenter le code.
- 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);
}
```
- 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");
}
```
## 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;
}
}
```
## 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
}
```
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
## 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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
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