Newer
Older
# Le C sur stackoverflow
## Le C est-il un langage encore utilisé (ou juste une vieille croûte inutile)?

## Généralités
- Plusieurs variables qu'on aimerait regrouper dans un seul type: `struct`{.C}.
```C
struct complex { // déclaration
double re;
double im;
};
struct complex num; // déclaration de num
```
- Les champs sont accessible avec le sélecteur "`.`{.C}".
```C
num.re = 1.0;
num.im = -2.0;
```
## Simplifications
- `typedef`{.C} permet de définir un nouveau type.
```C
typedef unsinged int uint;
typedef struct complex complex_t;
double re, im;
} complex_t;
```
- L'initialisation peut aussi se faire avec
```C
complex_t num = {1.0, -2.0}; // re = 1.0, im = -2.0
complex_t num = {.im = 1.0, .re = -2.0};
complex_t num = {.im = 1.0}; // argl! .re non initialisé
complex_t num2 = num; // copie
```
## Pointeurs
- Comme pour tout type, on peut avoir des pointeurs vers un `struct`{.C}.
- Les champs sont accessible avec le sélecteur `->`{.C}
```C
complex_t *num; // on crée un pointeur
num->re = 1.0; // seg fault...
num->im = -1.0; // mémoire pas allouée.
```
{width=100%}
- Avec le passage par **référence** on peut modifier un struct *en place*.
- Les champs sont accessible avec le sélecteur `->`{.C}
void complex_init(complex_t *num,
double re, double im)
{
// num a déjà été allouée
num->re = re;
num->im = im;
}
int main() {
complex_t num; // on alloue un complexe
complex_init(&num, 2.0, -1.0); // on l'initialise
}
* On peut allouer un complexe, l'initialiser et le retourner.
* La valeur retournée peut être copiée dans une nouvelle structure.
complex_t complex_create(double re, double im) {
complex_t num;
num.re = re;
num.im = im;
return num;
}
int main() {
// on crée un complexe et on l'initialise
// en copiant le complexe créé par complex_create
// deux allocation et une copie
complex_t num = complex_create(2.0, -1.0);
}
- Les arguments d'une fonction sont toujours passés **par copie**.
- Les arguments d'une fonction ne peuvent **jamais** être modifiés.
void do_something(complex_t a) { // a: nouvelle variable
// valeur de a est une copie de x
// lorsque la fonction est appelée, ici -1
a.re += 2.0;
a.im -= 2.0;
} // a est détruite
int main() {
complex_t x;
do_something(x); // x est passé en argument
// x sera inchangé
- Pour modifier une variable, il faut passer son **adresse mémoire** (sa référence) en argument.
- L'adresse d'une variable, `a`{.C}, est accédée par `&a`{.C}.
- Un **pointeur** vers une variable entière `a` le type, `int *`{.C}.
- `*a`{.C} sert à **déréférencer** le pointeur (accéder la mémoire pointée).
void do_something(complex_t *a) {
// a: un nouveau pointeur
// valeur de a est une copie de du pointeur
// passé en argument, mais
// les données pointées sont les données originales
a->re += 2.0;
a->im -= 2.0;
} // le pointeur a est détruit,
// *a est toujours là et a été modifié
## Principes généraux de programmation
- Beaucoup de fonctionnalités dans un code $\Rightarrow$ Modularisation.
- Modularisation du code $\Rightarrow$ écriture de fonctions.
- Beaucoup de fonctions $\Rightarrow$ regrouper les fonctions dans des fichiers séparés.
## Mais pourquoi?
- Lisibilité.
- Raisonnement sur le code.
- Débogage.
## Exemple
- Libraire `stdio.h`: `printf()`{.C}, `scanf()`{.C}, ...
- Prototypes de fonctions nécessaires quand:
1. Utilisation de fonctions dans des fichiers séparés.
2. Utilisation de librairies.
- Un prototype indique au compilateur la signature d'une fonction.
- On met les prototypes des fonctions **publiques** dans des fichiers *headers*, extension `.h`.
- Les *implémentations* des fonctions vont dans des fichier `.c`.
## Fichier header
- Porte l'extension `.h`
- Contient:
- définitions des types
- prototypes de fonctions
- macros
- directives préprocesseur (cf. plus loin)
- Utilisé pour décrire **l'interface** d'une librairie ou d'un module.
- Un fichier `C` (extension `.c`) utilise un header en *l'important* avec la directive `#include`{.C}:
```C
#include <stdio.h> // libraire dans LD_LIBRARY_PATH
#include "chemin/du/prototypes.h"// chemin explicite
```
{width=100%}
```bash
gcc proc.c -o prog
```
1. **Précompilation: ** `gcc` appelle `cpp`, le préprocesseur qui effectue de la substitution de texte (`#define`, `#include`, macros, ...) et génère le code `C` à compiler, portant l'extension `.i` (`prog.i`).
2. **Compilation assembleur: ** `gcc` compile le code C en code assembleur, portant l'extension `.s` (`prog.s`).
3. **Compilation code objet: ** `gcc` appelle `as`, l'assembleur, qui compile le code assembleur en code machine (code objet) portant l'extension `.o` (`prog.o`).
4. **Édition des liens: ** `gcc` appelle `ld`, l'éditeur de liens, qui lie le code objet avec les librairies et d'autres codes objet pour produire l'exécutable final (`prog`).
Les différents codes intermédiaires sont effacés.
{width=100%}
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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
::: Main
## `main.c`
```C
#include <stdio.h>
#include "sum.h"
int main() {
int tab[] = {1, 2, 3, 4};
printf("sum: %d\n", sum(tab, 4));
return 0;
}
```
:::
:::::::::::::: {.columns}
::: {.column width="45%"}
## `sum.h`
```C
#ifndef _SUM_H_
#define _SUM_H_
int sum(int tab[], int n);
#endif
```
:::
::: {.column width="55%"}
## `sum.c`
```C
#include "sum.h"
int sum(int tab[], int n) {
int s = 0;
for (int i = 0; i < n; i++) {
s += tab[i];
}
return s;
}
```
:::
::::::::::::::
La compilation séparée se fait en plusieurs étapes.
## Compilation séparée
1. Générer séparément les fichiers `.o` avec l'option `-c`.
2. Éditer les liens avec l'option `-o` pour générer l'exécutable.
## Exemple
- Création des fichiers objets, `main.o` et `sum.o`
```bash
$ gcc -Wall -Wextra -std=c11 -c main.c
$ gcc -Wall -Wextra -std=c11 -c sum.c
```
- Édition des liens
## Généralités
- Première étape de la chaîne de compilation.
- Géré automatiquement par `gcc` ou `clang`.
- Lit et interprète certaines directives:
1. Les commentaires (`//`{.C} et `/* ... */`{.C}).
2. Les commandes commençant par `#`{.C}.
- Le préprocesseur ne compile rien, mais subtitue uniquement du texte.
## La directive `define`{.C}
- Permet de définir un symbole:
```C
#define PI 3.14159
#define _SUM_H_
```
- Permet de définir une macro.
```C
#define NOM_MACRO(arg1, arg2, ...) [code]
```
## La directive `include`{.C}
- Permet d'inclure un fichier.
- Le contenu du fichier est ajouté à l'endroit du `#include`{.C}.
- Inclusion de fichiers "globaux" ou "locaux"
```C
#include <file.h> // LD_LIBRARY_PATH
#include "other_file.h" // local path
```
- Les inclusions multiples peuvent poser problème: définitions multiples. Les headers commencent par:
```C
#ifndef _VAR_
#define _VAR_
/*
commentaires
*/
#endif
```
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
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
451
452
453
454
455
456
457
458
# Les tableaux (1/2)
## Généralités
- `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/2)
## 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
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
```
# La ligne de commande (1/4)
## Point d'entrée d'un programme
- Le point d'entrée est la fonction `main()`{.C}.
- Elle peut être déclarée de 4 façon différentes:
1. `void main()`{.C}.
2. `int main()`{.C}.
3. `void main(int argc, char **argv)`{.C}.
4. `int main(int argc, char **argv)`{.C}.
- `argc`{.C} est le nombre d'arguments passés à la ligne de commande: **le premier est celui du programme lui-même**.
- `argv`{.C} est un tableau de chaînes de caractères passés sur la ligne de commande.
# La ligne de commande (2/4)
## Exemple d'utilisation
Pour la fonction dans le programme `prog`
```C
int main(int argc, char **argv)
```
Pour l'exécution suivante on a
```bash
$ ./prog -b 50 file.txt
```
```C
argc == 4
argv[0] == "prog"
argv[1] == "-b"
argv[2] == "50"
argv[3] == "file.txt"
```
# La ligne de commande (3/4)
## Conversion des arguments
- Les arguments sont toujours stockés comme des **chaînes de caractère**.
- Peu pratique si on veut manipuler des valeurs numériques.
- Fonctions pour faire des conversions:
```C
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
double atof(const char *nptr);
int snprintf(char *str, size_t size,
const char *format, ...);
// str: buffer, size: taille en octets max à copier,
// format: cf printf(), ret: nombre de char lus
```
# La ligne de commande (4/4)
## Exemple d'utilisation
\footnotesize
```C
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
int main(int argc, char **argv) {
if (argc != 3) {
char *progname = basename(argv[0]);
fprintf(stderr, "usage: %s name age\n", progname);
return EXIT_FAILURE;
}
char *name = argv[1];
int age = atoi(argv[2]);
printf("Hello %s, you are %d years old.\n", name, age);
return EXIT_SUCCESS;
}
```
```bash
$ ./prog Paul 29
Hello Paul, you are 29 years old.
```