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,
- ... accessible via des adresses,
+------+----------+----------+------+----------+------+------+
| bits | 00110101 | 10010000 | .... | 00110011 | .... | .... |
+======+==========+==========+======+==========+======+======+
| addr | 2000 | 2001 | .... | 4000 | .... | .... |
+------+----------+----------+------+----------+------+------+
- ... gérée par le système d'exploitation.
- ... séparée en deux parties: **la pile** et **le tas**.
* 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 d = *c; // on lit la valeur pointée par c. UB!
- `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**.
# Quiz: Les pointeurs
## [Les pointeurs](https://cyberlearn.hes-so.ch/mod/evoting/view.php?id=1038526)
# 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.
type identificateur(paramètres) {
// variables optionnelles
// type expression == type
return expression;
```C
int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
- 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: nouvelle variable
// valeur de a est une copie de x
// 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
}
```
## 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 (à accéder à la mémoire pointée).
## Exemple
```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
}
```
# Quiz: Les fonctions
## [Les fonctions](https://cyberlearn.hes-so.ch/mod/evoting/view.php?id=1038560)
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<!-- 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
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
350
351
352
353
354
355
356
357
358
359
360
361
362
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)
- Un tableau est le pointeur vers sa première case.
- Pas moyen de connaître sa taille: `sizeof()`{.C} inutile.
- Toujours spécifier la taille d'un tableau passé en argument.
for (int i = 0; i < ?; ++i) {
// on sait pas quoi mettre pour ?
// n doit venir avant tab, [n] opionel
void bar(int n, int tab[n]) {
for (int i = 0; i < n; ++i) {
printf("tab[%d] = %d\n", i, tab[i]);
}
}
```
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# Les tableaux (5/N)
## Quels sont les bugs dans ce code?
```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;
}
```
# Les tableaux (6/N)
## Quels sont les bugs dans ce code?
```C
#include <stdio.h>
int main(void) {
char i;
// 200, .., 500 char overflow
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42; // [10] out of bounds
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
```
<!-- TODO quiz: -->
<!-- que retourne sizeof(tab[]) -->