--- title: Base III date: 30 septembre 2020 --- # Le C sur stackoverflow ## Le C est-il un langage encore utilisé (ou juste une vieille croûte inutile)?  # Types complexes: `struct`{.C} (1/5) ## 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; ``` # Types complexes: `struct`{.C} (2/5) ## Simplifications - `typedef`{.C} permet de définir un nouveau type. ```C typedef unsinged int uint; typedef struct complex complex_t; typedef struct complex { 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 ``` # Types complexes: `struct`{.C} (3/5) ## 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%} # Types complexes: `struct`{.C} (4/5) ## Initialisation - Avec le passage par **référence** on peut modifier un struct *en place*. - Les champs sont accessible avec le sélecteur `->`{.C} ```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 } ``` # Types complexes: `struct`{.C} (5/5) ## Initialisation version copie * On peut allouer un complexe, l'initialiser et le retourner. * La valeur retournée peut être copiée dans une nouvelle structure. ```C 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 fonctions (1/2) ## Arguments de fonctions par copie - Les arguments d'une fonction sont toujours passés **par copie**. - Les arguments d'une fonction ne peuvent **jamais** être modifiés. ```C 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é } ``` * Que pourrait-on faire pour modifier `x`{.C}? # Les fonctions (2/2) ## Arguments de fonctions par référence - 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). ```C 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é ``` # Prototypes de fonctions (1/3) ## 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 (2/3) - 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`. # Prototypes de fonctions (3/3) ## 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 ``` # Génération d'un exécutable (1/5) ## Un seul fichier source {width=100%} # Génération d'un exécutable (2/5) ## Un seul fichier source ```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. # Génération d'un exécutable (3/5) ## Plusieurs fichiers sources {width=100%} # Génération d'un exécutable (4/5) ::: 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; } ``` ::: :::::::::::::: # Génération d'un exécutable (5/5) 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 ```bash $ gcc main.o sum.o -o prog ``` # Préprocesseur (1/2) ## 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] ``` # Préprocesseur (2/2) ## 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 ``` # 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. ```