summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorache <ache@ache.one>2018-11-25 10:01:30 +0100
committerache <ache@ache.one>2018-11-25 10:01:30 +0100
commite6bdf41ecff591298b7f114236f343d6bbf55794 (patch)
tree8bba6fcd8cf0f0689e4d120eb37581b69af058cb
parentFix the scroll (diff)
Update last article
-rw-r--r--article/bizarrerie-du-langage-c.md499
1 files changed, 309 insertions, 190 deletions
diff --git a/article/bizarrerie-du-langage-c.md b/article/bizarrerie-du-langage-c.md
index 8377100..730c1e7 100644
--- a/article/bizarrerie-du-langage-c.md
+++ b/article/bizarrerie-du-langage-c.md
@@ -2,10 +2,10 @@ Les bizarreries du langage C
============================
![The C Programming Language logo](res/c_language.svg)
+
Le C est un langage à la syntaxe simple. Les seules complexités de ce langage viennent du fait qu'il agit de manière proche de la machine.
-Pourtant, une partie des syntaxes autorisées par le C n'est pratiquement jamais enseignée.
-Attaquons nous à ces cas mystérieux !
+Pourtant, une partie des syntaxes autorisées par le C n'est pratiquement jamais enseignée. Attaquons-nous à ces cas mystérieux ! 🧞
>!attention
> Pour comprendre ce billet, il est nécessaire d'avoir des bases dans un langage ayant une syntaxe et un fonctionnement proche du C.
@@ -18,6 +18,7 @@ Il existe deux opérateurs dans le langage C qui ne sont pratiquement jamais uti
En C la virgule sert à séparer factoriser les éléments d'une définition ou séparer les éléments d'une fonction. En bref, c'est un élément de ponctuation.
Mais pas seulement ! C'est également un opérateur.
+
### L'opérateur virgule
L'instruction suivante, bien qu'inutile est tout à fait valide :
@@ -26,11 +27,11 @@ L'instruction suivante, bien qu'inutile est tout à fait valide :
printf("%d", (5,3) );
```
-Elle affiche 3. L'opérateur ',' sert à juxtaposer des expressions.
+Elle affiche 3. L'opérateur `,` sert à juxtaposer des expressions.
La valeur de l'expression complète est égale à la valeur de la dernière expression.
Cet opérateur se révèle très utile dans une boucle `for` pour multiplier les itérations.
-Par exemple pour incrémenter `i` et décrémenter `j` dans la même itération du `for` on peut faire :
+Par exemple pour incrémenter `i` et décrémenter `j` dans la même itération d'une boucle `for` on peut faire :
```c
for( ; i < j ; i++, j-- ) {
// [...]
@@ -44,7 +45,7 @@ if( argc > 2 && argv[2][0] == '0' )
action = 4, color = false;
```
-Ici, on assigne `action` et `color`. Normalement pour faire 2 assignations, on aurait du mettre des accolades au `if`.
+Ici, on assigne `action` et `color`. Normalement pour faire 2 assignations, on aurait dû mettre des accolades au `if`.
On peut également s'en servir pour retirer des parenthèses.
@@ -66,7 +67,16 @@ Cette remarque est d'ailleurs également valide pour le prochain opérateur !
Le ternaire pour les intimes. Le seul opérateur du langage C qui prenne 3 opérandes.
Il sert à simplifier des expressions conditionnelles.
-Par exemple pour afficher le minimum de deux nombres, sans les ternaires, on ferait :
+Par exemple pour afficher le minimum de deux nombres, sans le ternaire, on ferait :
+
+```c
+if (a < b)
+ printf("%d", a);
+else
+ printf("%d", b);
+```
+
+Ou avec une variable temporaire :
```c
int min = a;
@@ -75,25 +85,28 @@ if( b < a)
printf("%d", min);
```
-Alors qu'avec les ternaires on fait :
+Alors qu'avec les ternaires on fait simplement :
+
```c
printf("%d", a<b ? a : b);
```
-Grâce à la ternaire, on a économisé un nom de variable et quelques lignes.
-Mais surtout on a gagné en lisibilité. Quand on sait en lire une ...
+Grâce au ternaire, on a économisé une répétition ainsi que quelques lignes.
+Mais surtout on a gagné en lisibilité. Quand on sait en lire une...
+
+Pour lire une expression ternaire, il faut la découper en 3 parties :
-Pour lire une expression ternaire, il faut lire la découper en 3 parties :
```c
expression_1 ? expression_2 : expression_3
```
+
La valeur d'une expression ternaire est `expression_2` si la valeur de `expression_1` est évaluée à vrai et `expression_3` sinon.
-En somme, cela simplifie un peu la lecture pour de courte expression.
-L'expression `a<b ? a : b` se lit “ Si `a` est inférieur à `b` alors `a` sinon `b` ”.
+En somme, cela simplifie un peu la lecture pour de courtes expressions.
+L'expression `a<b ? a : b` se lit « Si `a` est inférieur à `b` alors `a` sinon `b` ».
-J'insiste encore sur le fait que cet opérateur, s'il est mal utilisé peu nuire à la lisibilité du code.
-D'ailleurs, on peut très bien mettre une expression ternaire dans une expression ternaire :
+J'insiste encore sur le fait que cet opérateur, s'il est mal utilisé, peut nuire à la lisibilité du code.
+Notez que l'on peut très bien utiliser une expression ternaire comme opérande d'une autre expression ternaire :
```c
printf("%d", a<b ? a<c ? a : b<c ? b : c : b < c ? b : c);
@@ -101,31 +114,28 @@ printf("%d", a<b ? a<c ? a : b<c ? b : c : b < c ? b : c);
Désormais, on prend le minimum de trois nombres. C'est aéré, mais impossible à suivre.
Le plus lisible est d'utiliser une macro :
+
```c
-#define MIN(a,b) a<b ?a : b
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
printf("%d", MIN(a,MIN(b,c)));
```
-
Et voilà ! Deux opérateurs qui vont désormais gagner un peu d'intérêt.
-Et surtout être mieux compris !
-Puis d'ailleurs, même les opérateurs qu'on connait déjà, on n'en maîtrise pas forcément la syntaxe.
+Et être mieux compris !
+Puis d'ailleurs, même les opérateurs que l'on connait déjà, on n'en maîtrise pas forcément la syntaxe.
L'accès à un tableau
------------------
-On nous a toujours appris que pour afficher le 3em élément d'un tableau, on faisait :
+On nous a toujours appris que pour afficher le 3ème élément d'un tableau, on faisait :
```c
int tab[5] = {0, 1, 2, 3, 4, 5};
printf("%d", tab[2]);
```
-2 et non 3, car un tableau en C commence à 0.
-Un tableau commence à 0, c'est une histoire d'adresse.
-L'adresse du premier élément est en fait l'adresse du tableau.[^tableau]
-Et par arithmétique des pointeurs, l'adresse du 3em élément est `tab+2`.
+2 et non 3, car un tableau en C commence à 0. Si un tableau commence à 0, c'est une histoire d'adresse[^adresse]. L'adresse du tableau est en fait l'adresse du premier élément du tableau. Et par arithmétique des pointeurs, l'adresse du 3ème élément est `tab+2`.
Donc on aurait très bien pu écrire :
@@ -133,18 +143,18 @@ Donc on aurait très bien pu écrire :
printf("%d", *(tab+2));
```
-Puis comme l'addition est commutative, on `tab+2` ou `2+tab` sont équivalents.
+Puis comme l'addition est commutative, `tab+2` ou `2+tab` sont équivalents.
En fait, on aurait même pu faire :
```c
printf("%d", 2[tab]);
```
-C'est tout à fait valide. Et pour cause ; la syntaxe `E[F]` est strictement équivalente à `*((E)+(F))`.
-Du coup, le titre de cette section est un peu trompeur. Cet opérateur n'a pas grand chose à voir avec les tableaux en fait.
-C'est du sucre syntaxique pour cacher l'arithmétique des pointeurs.
+C'est tout à fait valide. Et pour cause : la syntaxe `E[F]` est strictement équivalente à `*((E)+(F))`.
+Du coup, le titre de cette section est un peu trompeur. Cet opérateur n'a pas grand-chose à voir avec les tableaux en fait.
+C'est du sucre syntaxique pour cacher l'arithmétique des pointeurs.[^sucre]
-Par exemple pour afficher un caractère '=' pour vrai, '!' pour faux et '~' pour aucun des deux.
+Par exemple pour afficher le caractère `=` pour vrai, `!` pour faux et `~` pour aucun des deux.
On pourrait faire :
```c
if( is_good == 1 )
@@ -166,51 +176,44 @@ printf("%c", is_good["!=~"] ); // Affiche '!' si is_good vaut 0
```
En somme, tout le monde écrit `tab[3]` et pas `3[tab]`. Du coup, il n'y a aucun intérêt à écrire `3[tab]`.
-Mais c'est toujours bien de savoir que ça existe. ^^
+Mais c'est toujours bon de savoir que ça existe. ^^
+
+[^adresse]: C'est un peu plus compliqué que cela. À l'époque où il a fallu choisir si on commençait un tableau à l'indice 0 ou 1, le temps de compilation d'un programme avait une importance particulière. Il a été décidé de commencer à 0 afin de ne pas perdre de temps à la compilation en translation d'indices. L'article [Citation Needed](http://exple.tive.org/blarg/2013/10/22/citation-needed/) de Mike Hoye explique cela bien mieux que moi.
+[^sucre]: Pour la petite histoire l'opérateur `[]` est hérité du langage B où le concept de tableau n'existait pas comme en C. Un tableau (ou vecteur comme on l'appelait à l'époque) n'était que l'adresse du premier élément d'une suite d'octets. Seul l'arithmétique des pointeurs permettait d'accéder à tous les éléments d'un tableau, c'est une évolution comparé au BCPL, l'ancêtre du B, qui utilisait la syntaxe `V!4` pour accéder au cinquième élément d'un tableau, mais je m'égare...
L'initialisation
---------------
-L'initialisation, c'est quelque chose qu'on maîtrise en C. C'est le fait de donner une valeur à une variable lors de sa déclaration.
+L'initialisation, c'est quelque chose que l'on maîtrise en C. C'est le fait de donner une valeur à une variable lors de sa déclaration.
En gros, on définit sa valeur.
-Pour un tableau:
+Pour un tableau[^tableau] :
```c
int tab[10] = {0};
tab[2] = 5;
```
->!secret
->
-> `{0}` peut également être utilisé pour un nombre. Toute valeur initialisant une variable simple (pointeur nombre, ...) peut optionnellement prendre des accolades.
->
->```c
->int a = {11};
->float pi = {3.1415926};
->char* s = {"unicorn"};
->```
->
->Ce qui caractérise le tableau du coup est surtout le fait d'utiliser des virgules entre les accolades.
-On initialise le tableau avec des 0 car, lors qu'on initialise un tableau toute valeur non spécifiée est par défaut 0.
+À la première ligne, on initialise le tableau avec des 0 car, lorsqu'on initialise un tableau toute valeur non spécifiée est par défaut 0.
La ligne suivante est une affectation et non une initialisation.
-Si on veut seulement une initialisation ; Puisque l'initialisation d'un tableau se faire suivant l'ordre de ses valeurs, on devrait écrire :
+Si on veut seulement initialiser le troisième élément, puisque l'initialisation d'un tableau se fait suivant l'ordre de ses valeurs, on devrait écrire :
+
```c
int tab[10] = {0, 0, 5};
```
Mais en réalité, il existe une autre syntaxe qui permet de faire plus simple :
+
```c
int tab[10] = {[2] = 5};
```
On dit simplement que la troisième case vaut 5. Le reste est par défaut 0.
+Une syntaxe équivalente existe d'ailleurs pour les structures et les unions.
-Une syntaxe équivalente existe d'ailleurs pour les structures ou les unions.
-
->!comment
+>!information
> Pour l'exemple, on va prendre une structure `point` que je vais utiliser plusieurs fois dans ce billet, idem pour la structure `message`.
On peut ainsi initialiser un point en utilisant ses composantes.
@@ -238,7 +241,7 @@ typedef struct message {
message to_send = {.src="", .dst="23:12:23", .msg="Code 10"};
-// Est bien plus claire que :
+// Est bien plus clair que :
message to_send = {"", "23:12:23", "Code 10"};
@@ -248,13 +251,13 @@ message to_send = {"", "23:12:23", "Code 10"};
message to_send = { .msg="Code 10", .dst="23:12:23", .src=""};
// Et aussi puisque tout champ d'une structure est inialisé à sa valeur nulle s'il n'est pas initialisé explicitement.
-// On peut également omètre src.
+// On peut également omettre src.
message to_send = { .dst="23:12:23", .msg="Code 10"};
```
-Avec ces syntaxes. On peut également alourdir le code, mais généralement, on gagne en lisibilité.
+Avec ces syntaxes on peut également alourdir le code, mais généralement, on gagne en lisibilité.
Parfois, ces syntaxes sont très judicieusement utilisées !
Comme ici, dans ce décodeur de base64 :
>```c
@@ -277,10 +280,21 @@ Comme ici, dans ce décodeur de base64 :
Source: [Taurre](https://openclassrooms.com/forum/sujet/defis-8-tout-en-base64-19054?page=1#message-6921633)
+[^tableau]:
+ `{0}` peut également être utilisé pour un nombre. Toute valeur initialisant une variable simple (pointeur, nombre, ...) peut optionnellement prendre des accolades.
+
+ ```c
+ int a = {11};
+ float pi = {3.1415926};
+ char* s = {"unicorn"};
+ ```
+
+ Ce qui caractérise le tableau du coup est surtout le fait d'utiliser des virgules entre les accolades.
+
Les expressions littéralement composées
--------------------------------------
-Puisque nous parlons des tableaux. Il existe une syntaxe simple pour utiliser des tableaux constants.
+Puisque nous parlons des tableaux. Il existe une syntaxe simple pour utiliser des tableaux à usage unique.
Je voudrais utiliser ce tableau :
```c
@@ -289,14 +303,14 @@ printf("%d", tab[i]); // Avec i égale à quelque chose >=0 et <5
```
Cependant, je ne l'utilise qu'une seule fois ce tableau ...
-C'est pas cool d'avoir à utiliser un identificateur juste pour ça.
+C'est un peu dérangeant d'avoir à utiliser un identificateur juste pour ça.
-Et bien je peux faire ceci :
+*Eh* bien je peux faire ceci :
```c
printf("%d", ((int[]){5,4,5,2,1}) [i] ); // Avec i égale à quelque chose >=0 et <5
```
-Ce n'est pas super lisible pour le coup. Mais il existe pleins de cas où cette syntaxe est très utile :
+Ce n'est pas super lisible pour le coup. Mais il existe plein de cas où cette syntaxe est très utile. Par exemple avec une structure :
```c
@@ -307,181 +321,185 @@ send_msg( (message){ .dst="192.168.11.1", .msg="Code 11"} );
printf("%d", distance( (point){1, 2}, (point){2, 3} ) );
// Ou encore sous Linux, en programmation système
-execvp( "bash" , ((char*[]){"bash", "-c", "ls", NULL}) );
-
+execvp( "bash" , (char*[]){"bash", "-c", "ls", NULL} );
```
-Ce type d'expression est une expression littérale, c'est-à-dire qu'elle est immuable.
-Comme pour les chaînes de caractères littérales.
-
-Ainsi ceci n'est pas valide :
+On appelle ces expressions des *compound literals* (ou littéraux agrégats si l'on s'essaye à traduire).
-```c
-"lache"[0] = 'v' // Est une expression invalide puisque "lache" est une expression littérale
-```
-
-De même ceci n'est pas valide :
-
-```c
-((int[]){1,2,3})[0] = 0;
-```
-
-On appelle donc ces expressions des compound literals.
-Ou une expression littérale composée si l'on s'essaye à traduire.
-
-
-Les VLAs
+Introduction aux VLAs
--------
-Les tableaux à taille variable (ou *Variable Length Arrays* en anglais, soit VLAs) sont des tableaux dont la taille n'est connue qu'à la compilation. Si vous n'avez jamais entendu parler des VLAs, ceci devrait vous choquer :
+Les tableaux à taille variable (ou *Variable Length Arrays* en anglais, soit VLAs) sont des tableaux dont la taille n'est connue qu'à l'exécution. Si vous n'avez jamais entendu parler des VLAs, ceci devrait vous choquer :
```c
int n = 11;
int tab[n];
-for(int i = 0 ; i < n ; i)
+for(int i = 0 ; i < n ; i++)
tab[i] = 0;
```
-Ce code, bien que valide, à dût être réprimendé par de nombreux professeurs. En effet, on nous apprend qu'un tableau doit avoir une taille connue à la compilation.
-Et bien, les VLA sont l'exeption. Apparu avec la norme C99, les VLAs jouissent d'une mauvaise réputation. À cela, il existe plusieurs raisons que je ne détaillerais pas ici.
+Ce code, bien que valide, a dû être réprimandé par de nombreux professeurs. En effet, on nous apprend qu'un tableau doit avoir une taille connue à la compilation.
+*Eh* bien, les VLAs constituent l'exception. Apparu avec la norme C99, les VLAs jouissent d'une mauvaise réputation. À cela, il existe plusieurs raisons que je ne détaillerais pas ici[^raisons]. Je vais simplement parler des comportements non-intuitifs introduits avec les VLAs. Mais tout d'abord, voyons ce qu'est et comment se servir d'un tableau à taille variable.
-Je vais simplement parler des comportements non-intuitifs introduits avec les VLAs. Comme par exemple :
+Les VLAs se définissent avec la même syntaxe qu'un tableau classique. La seule différence est que la taille du tableau est une expression entière non constante.
```c
-printf("%zu", sizeof(int[puts("Je retourne :")]) );
-```
-
-En temps normal, l'expression entre crochet serait constante. Il n'y aurait pas eu besoin de l'exécuter. Cependant, avec les VLAs, on est obligé d'exécuter ce code afin de connaitre la taille du tableau.
+int n = 50;
+int tab[n];
-Du coup, ce code affiche bien :
+double tab2[2*n];
-```shell
-Je retourne :
-56
+unsigned int tab[foo()]; // avec foo une fonction définie ailleurs
```
-56 étant la taille du tableau (`14*sizeof(int)`). 14 étant le nombre de caractères affichés par `puts`.
-
-Ceci est d'autant plus étrange que `sizeof` a pour habitude de ne pas interpréter l'expression que l'on lui en paramètre. Afin de faciliter l'allocation dynamique d'un tableau, il est courant d'utiliser cette syntaxe :
+Un VLA ne peut pas être initialisé, de plus, il ne peut être déclaré `static`. C'est-à-dire que ces deux déclarations sont incorrectes :
```c
-char* str = malloc( n * sizeof *str);
-```
+int n = 30;
-Ici, l'expression `*str` n'a pour l'instant pas de comportement définit (str n'étant pas encore initialiser) mais peu importe car elle n'est pas exécutée. Son type est déterminé à la compilation et `sizeof` là remplace par la taille de son type.
+int tab[n] = {0};
+static tab2[n];
+```
-Le type de `*str` étant `char`, sa taille est donc de 1 byte. Et le code précédant est traduit pas le compilateur par :
+Dans une fonction, on pourrait utiliser :
```c
-char* str = malloc( n );
+void bar(int n, int tab[n]) {
+
+}
```
-On peut même faire plus bizarre encore :
+Cependant, la taille de la première dimension d'un tableau n'a pas beaucoup d'importance, un tableau étant implicitement convertit en un pointeur vers son premier élément. En revanche, pour un tableau à deux dimensions, la taille de la deuxième dimension *doit* être spécifiée :
```c
-void foo(int a[][puts("Avant foo")]) {
- puts("Début de foo");
+void foo( int n, int m, int tab[][m]) {
}
-int main(void) {
- int b[3][3];
- f(b);
-}
```
+À noter qu'il est possible d'utiliser le caractère `\*` (encore une utilisation de plus) en lieu et place de la taille d'une ou plusieurs dimensions d'un VLA, mais *uniquement* au sein d'un prototype.
-Humm, curieux effet de bord. Il y a deux choses assez intéressantes dans ce code. La première, assez évidente, c'est le fait d'avoir un appel de fonction dans la définition du type.
-La deuxième chose intéressante est le fait que foo prenne en argument un tableau à taille variable, mais qu'on lui passe en paramètre un tableau de taille fixe ! En fait, on aurait pu écrire n'importe quelle expression qui retourne un entier.
-Ces deux points sont causés par les VLAs. Voyons ça plus doucement.
+```c
+void foo(int, int, int[][*]);
+```
-La définition d'un type VLA
----------------------------
+Bien, après cette courte introduction aux VLAs, passons aux cas qui nous intéressent. Pour être précis, les bizarreries et excentricités que les VLAs ont introduits.
-Pour définir une fonction qui prend un tableau d'entiers de taille `N` en paramètre, on peut simplement faire :
+[^raisons]: Je peux néanmoins vous donnez deux références [“Is it safe to use variable-length arrays?”](https://stackoverflow.com/questions/7326547/is-it-safe-to-use-variable-length-arrays) de stack overflow et cet article : [“The Linux Kernel Is Now VLA-Free”](https://www.phoronix.com/scan.php?page=news_item&px=Linux-Kills-The-VLA).
-```c
-void foo(int tab[N]);
-```
+L'exception des VLAs
+---------------------------
-Mais en fait, peut importe la taille, la fonction ne fait pas vraiment la différence. Pour elle, c'est simplement un pointeur sur un entier. Du coup, on peut très bien écrire :
+Le comportement déviant le plus connu des VLAs est leur rapport à `sizeof`.
+`sizeof` est un opérateur unaire qui permet de retrouver la taille d'un type à partir d'une expression ou du nom d'un type entouré de parenthèses.
-```c
-// Si on veut gardé l'idée que c'est un tableau (au moins à la base)
-void foo(int tab[]);
+~~~c
+/* Le fonctionnement de l'opérateur sizeof par des exemples */
+float a;
+size_t b = 0;
-// Ou si on préfère traduire l'idée qu'on manipule un pointeur.
-void foo(int* tab);
-```
+printf("%zu", sizeof(char)); // Affiche 1
+printf("%zu", sizeof(int)); // Affiche 4
+printf("%zu", sizeof a); // Affiche 4
+printf("%zu", sizeof(a*2.)); // Affiche 8
+printf("%zu", sizeof b++); // Affiche 8
+~~~
-Tout ceci est possible car par arithmétique des pointeurs, `tab[9]` ne dépends pas de la taille du tableau, mais plutôt de la taille d'un `int`.
-Mais du coup, ça se complexifie si on a des types un peu plus compliqués, par exemple ... Des types dont on ne connait pas la taille à la compilation.
-Oui je parle de VLAs. Pour un double tableau, on doit spécifier une taille à la compilation la déclaration ressemble à :
+Le premier résultat n'est pas très surprenant, la taille d'un `char` est défini à 1 *byte* et `sizeof(char)` doit retourner 1. Le deuxième résultat est la taille d'un `int`[^machine]. Le troisième résultat est la taille d'un `float`[^machine]. Le quatrième est la taille d'un `double`[^machine] qui est le type de l'expression `a\*2.`[^float]. Le dernier résultat est la taille d'un `size_t`[^machine], `size_t` étant le type de l'expression `b++`.
-```c
-void foo(int tab[][5]);
-```
+Ici, je ne me suis pas intéressé à la valeur des expressions et pour cause, `sizeof` ne s'y intéresse pas non plus. Ces valeurs sont déterminées à la compilation. Les opérations que l'on retrouve dans l'expression passé à `sizeof` ne sont pas effectuées. Puisque l'expression doit être valide, son type doit être déterminé à la compilation. Le résultat de `sizeof` étant alors connu à la compilation, il n'y avait aucune raison d'exécuter l'expression.
-On a donc au final un pointeur sur un tableau d'entier de taille 5, soit de taille complète `5*sizeof int`.
-Et donc pour un tableau de taille `n` (VLAs). On doit faire :
+~~~c
+int n = 5;
+printf("%zu", sizeof(char[++n])); // Affiche 6
+~~~
-```c
-void foo(int tab[][*]);
-```
+*Arf* ! Les VLAs rajoutent leur grain de sel. Dans le type `int[++n]`, `++n` est une expression non constante. Donc le tableau est un tableau à taille variable. Pour connaitre la taille totale du tableau, il est nécessaire d'exécuter l'expression entre crochet. Ainsi `n` vaut désormais 6 et `sizeof` nous indique qu'un VLA de `char` déclaré avec cette expression aurait eu pour taille `6`.
-Ouais ... Car ici, c'est une déclaration et pas une définition. `n` n'est pas censé avoir de valeur pour le moment du coup, ça n'aurait pas vraiment de sens. Sauf si `n` était global, mais du coup, elle aurait une valeur fixe pour le moment. Bref, on a donc introduit cette syntaxe.
-Puis pour la définition on utilise bien évidemment `n` :
+Ce n'est que peu intuitif puisque les VLAs ont ici introduit une *exception à la règle* qui est de ne pas exécuter l'expression passée à `sizeof`.
-```c
-void foo(int tab[][n]) { // Où n est une variable globale
+Un autre comportement bizarre introduit par les VLAs est l'exécution des expressions liées à la taille d'un VLA dans la définition d'une fonction. Ainsi :
+
+~~~c
+int foo( char tab[printf("bar")] ) {
+ printf("%zu", sizeof tab);
}
-```
+~~~
-Personne n'aime les variables globales. Du coup on préfèrerait que `n` soit passé en argument :
+En supposant que les affichages ne causent pas d'erreur, appeler cette fonction affichera `bar3`. L'instruction `printf("bar")` est évaluée et ensuite seulement le corps de la fonction est exécuté.
-```c
-void foo(int n, int tab[][n]);
+Il est à noter qu'il existe d'autres exceptions induites pas la standardisation des VLAs comme l'impossibilité d'allouer les VLAs de manière statique (assez logique), ou l'impossibilité d'utiliser des VLAs dans une structure (GNU GCC le support tout de même). Et même certains branchements conditionnels sont interdits lorsque l'on utilise un VLA.
-void foo(int n, int tab[][n]) {
+[^machine]: Sur ma machine
+[^float]: Ici, l'implémentation des nombres flottants suit la norme IEE 754, où la taille d'un nombre flottant simple est de 4 octets et double précision est de 8 octets. `2.` est de type `double`, l'expression `2.*a` est donc du même type que le plus grand des deux types de ses deux opérandes, ici `double`.
-}
-```
+Un tableau flexible
+-------------------
-Puis en fait, pour être tout à fait précis, dans les crochets, un VLA prend en paramètre une expression qui retourne une variable entière. Donc ici, si le tableau faisait $2n$ on aurait pût écrire :
+Vous n'avez peut-être jamais entendu parler des « *flexible arrays member* ». C'est normal, ceux-ci répondent à une problématique très précise et peu courante.
-```c
-void foo(int n, int tab[][*]);
+L'objectif est d'allouer une structure mais dont un champ (un tableau) est de taille inconnue à la compilation et le tout sur un espace contiguë[^pourquoi].
-void foo(int n, int tab[][2*n]);
-```
+Ici, pas de VLAs, car comme on l'a vu, ceux-ci sont interdits en tant que champs de structure. L'allocation dynamique s'impose.
+On pourrait vouloir faire :
-Cool. Non en fait, c'est même vraiment méga cool. Car sans ça, on aurait du bidouiller avec des adresses pour arriver à la même chose. On aurait pût cacher la misère avec des fonctions. Une autre solution, très courante, était d'utiliser le tableau à deux dimensions comme un tableau à une dimension.
-Comme ceci :
+~~~c
+struct foo {
+ int* tab;
+};
+~~~
-```c
-int tab[3][3];
+Et l'utiliser comme ceci :
-tab[0][8] == tab[2][2]; // 8 = (2*3+2)
-```
+~~~c
+ struct foo* contigue = malloc( sizeof(struct foo) );
+ if (contigue) {
+ contigue->tab = malloc( N * sizeof *contigue->tab );
+ if (contigue->tab) {
+ contigue->tab[0] = 11;
+ }
+ }
+~~~
+
+Mais ici, le tableau n'a aucune raison d'être contiguë à la structure. Ce qui aura pour conséquence qu'en cas de copie de la structure, la valeur du champ `tab` sera la même pour la copie et l'origine. Pour éviter cela, il faudra copier la structure, réallouer le tableau et le recopier. Voyons une autre méthode.
+
+~~~c
+struct foo {
+ /* ... Au moins un autre champ car le standard l'impose. */
+ int flexiTab[];
+};
+~~~
+
+Ici, le champ `flexiTab` est un tableau membre flexible. Un tel tableau doit être le dernier élément de la structure et ne pas spécifier de taille[^zero]. On l'utilise comme ceci :
+
+~~~c
+ struct foo* contigue = malloc( sizeof(struct foo) + N * sizeof *flexiTab );
+ if (contigue) {
+ flexiTab[0] = 11;
+ }
+~~~
+
+Cette syntaxe répond autant à un besoin de portabilité sur les architectures imposant un alignement particulier (le tableau est contigu à la structure) qu'au besoin de faire apparaître un lien sémantique entre le tableau et la structure (le tableau appartient à la structure).
-Mais ça reste bien moins pratique que l'utilisation de VLAs.
-Mais alors une dernière question reste en suspens. Peut-on utiliser cette astuce pour des tableaux 2D qui ne sont pas des VLAs. À cela il existe une réponse simple. Un tableau reste un tableau, son agencement en mémoire est le même. Du coup, pas de problème à utiliser un tableau à deux dimensions fixes dans une fonction qui prend en paramètre un VLA.
+[^pourquoi]: On peut vouloir que cet espace soit contiguë pour plusieurs raisons. Une de ces raisons est pour optimiser l'utilisation du cache processeur. Également, la gestion des couches réseaux se portent assez bien à l'utilisation de tableaux flexibles.
+[^zero]: Avant la spécification des « flexible array member » en C99, il était courant d'utiliser des tableaux de taille un pour reproduire le concept.
Une histoire d'étiquettes
------------------------
-En C, s'il y a un truc dont on ne doit pas parler, ce sont bien les *étiquettes*.
-On les utilses avec la structure de contrôle `goto` ! L'interdite !
+En C, s'il y a un truc dont on ne doit pas parler, ce sont bien des *étiquettes*.
+On les utilise avec la structure de contrôle `goto` ! L'interdite !
-Pour les cacher, on remplace les `goto` par des structures de contrôles adapées et plus claires comme `break` ou `continue`.
+Pour les cacher, on remplace les `goto` par des structures de contrôles adaptées et plus claires comme `break` ou `continue`.
De manière à ne jamais avoir à utiliser `goto`.
-Du coup, on n'apprend jamais ce qu'est une étiquette ...
+Du coup, on n'apprend jamais ce qu'est une étiquette...
Voici comment on utilise `goto` et une étiquette :
@@ -495,12 +513,12 @@ end: return 0;
En gros, une étiquette est un nom que l'on donne à une instruction.
-Et bien on en utilise des étiquettes ! Dans les `switch` !
+*Eh* bien on en utilise des étiquettes ! Dans les `switch` !
```c
switch( action ) {
- case 0 :
+ case 0:
do_action0();
case 1:
do_action1();
@@ -513,17 +531,17 @@ switch( action ) {
}
```
-Ici, les `case` et le `default` sont en fait des étiquettes ! Comme pour les goto.
-Sauf qu'elles sont pour les `switch`, et inutilisables par les `goto` ...
+Ici, les `case` et le `default` sont en fait des étiquettes ! Comme pour les `goto`.
+Sauf qu'elles sont pour les `switch`, et inutilisables par les `goto`...
>!question
> Pourquoi tu nous parles de ça ?
-Déjà, c'est bien de savoir que ça s'appele une étiquette. Ensuite, parce que je vais vous parlez d'un classique.
-Le dispositif de Duff.
+Déjà, c'est bien de savoir que ça s'appelle une étiquette. Ensuite, parce que je vais vous parlez d'un classique.
+Le dispositif de *Duff*.
-C'est une sorte de boucle déroulée optimisée. Le but est de réduire le nombre de tests de vérification de fin de boucle (ainsi que le nombre de décrémentation).
+C'est une sorte de boucle déroulée optimisée. Le but est de réduire le nombre de vérifications de fin de boucle (ainsi que le nombre de décrémentations).
Voici la version historique écrite par Tom Duff :
@@ -544,13 +562,13 @@ Voici la version historique écrite par Tom Duff :
}
```
-Peu importe ce que signifie `register`. Aussi, `to` est un pointeur particulié mais ce n'est pas vraiment important.
+Peu importe ce que signifie `register`. Aussi, `to` est un pointeur particulier, mais ce n'est pas vraiment important.
-Ici, ce dont je veux vous parlez, c'est cette boucle `do-while` en plein milieu d'un switch.
+Ici, ce dont je veux vous parler, c'est de cette boucle `do-while` en plein milieu d'un `switch`.
Le test que l'on cherche à effectuer le moins possible est `--n > 0`.
-En temps normal, `n` serrait en fait `count`. Et on devrait faire le test `count` fois.
+En temps normal, `n` serait en fait `count`. Et on devrait faire le test `count` fois.
De même pour sa décrémentation.
C'est-à-dire :
@@ -562,7 +580,7 @@ while( count-- > 0 )
En divisant par 8 (nombre arbitraire) on divise également par 8 le nombre de tests et de décrémentations.
Cependant, si `count` n'est pas divisible par 8, on a un problème, on ne fait pas toutes les instructions.
-Ce serrait bien de pouvoir sauter directement à la 2em instruction, si on a seulement 6 instructions restantes.
+Ce serait bien de pouvoir sauter directement à la 2ème instruction, si on a seulement 6 instructions restantes.
Et c'est là que les étiquettes peuvent nous aider !
Grâce au `switch` on peut sauter directement à la bonne instruction.
@@ -570,40 +588,145 @@ Grâce au `switch` on peut sauter directement à la bonne instruction.
Il suffit d'étiqueter chaque instruction avec le nombre d'instructions qu'il reste à faire dans la boucle.
Puis de sauter dans la boucle avec la structure de contrôle `switch` sur le reste d'instructions à réaliser.
-Ensuite, on execute nos paquets de 8 instructions normalement.
+Ensuite, on exécute nos paquets de 8 instructions normalement.
-Il est très rare d'avoir à utiliser ce type d'astuces.
+Il est très rare d'avoir à utiliser ce type d'astuce.
D'ailleurs, c'est une optimisation d'un autre temps.
-Mais comme je voulais vous parler de syntaxe alors, il était nécessaire de parlez des étiquettes.
+Mais comme je voulais vous parler de syntaxe, il était nécessaire de parler des étiquettes.
+
+Les nombres complexes
+--------------------
+
+Encore une fois nous allons voir une syntaxe introduite en C99. Plus exactement, ce sont 3 types qui ont été introduits, ceux-ci correspondent aux nombres complexes. Le type d'un nombre complexe est `double _Complex` (les deux autres types suivent le même schéma, afin de ne pas me répéter, je vais me concentrer sur le type `double`).
+
+Ainsi en C, il est possible de déclarer un nombre complexe comme ceci :
+
+```c
+double complex point = 2 + 3 * I;
+```
+
+Ici, on retrouve les macros spéciales `complex` et `I` (définie dans l'en-tête `<complex.h>`). Le premier sert à créer un type complexe alors que le second sert à définir la partie imaginaire d'un nombre complexe.
+
+En mémoire une variable complexe occupe autant d'espace que 2 fois le type réel sur lequel elle est basée. On se sert d'une variable complexe comme d'une variable normale. L'arithmétique y est intuitive puisque basées sur celle des réels. Il est à noter qu'il est recommandé d'utiliser le macro `CMPLX` pour initialiser un nombre complexe :
+
+```c
+double complex cplx = CMPLX(2, 3);
+```
+
+Pour une meilleure gestion des cas où la partie imaginaire (celle multipliée par `I` donc) serait `NAN`, `INFINITY` ou encore plus ou moins 0.
+
+L'en-tête `<complex.h>` nous offre une manière réellement simple d'utiliser des nombres imaginaires. En effet, de nombreuses fonctions courantes de manipulations des nombres imaginaires y sont disponibles.
+
+Les macros génériques
+---------------------
+Il existe un moyen en C d'avoir des macros qui soient définies différemment en fonction du type de l'un de ses arguments. Cette syntaxe est cependant « nouvelle » puisqu'elle date du standard C11.
+
+Cette généricité s'obtient avec les sélections génériques basées sur la syntaxe ` _Generic ( /* ... */ )`
+
+Pour comprendre la syntaxe, voyons un exemple simpliste :
+
+```c
+#include <stdio.h>
+#include <limits.h>
+
+#define MAXIMUM_OF(x) _Generic ((x), \
+ char: CHAR_MAX, \
+ int: INT_MAX, \
+ long: LONG_MAX \
+ )
+
+int main(int argc, char* argv[]) {
+ int i = 0;
+ long l = 0;
+ char c = 0;
+
+ printf("%i\n", MAXIMUM_OF(i));
+ printf("%d\n", MAXIMUM_OF(c));
+ printf("%ld\n", MAXIMUM_OF(l));
+ return 0;
+}
+```
+
+Ici, on affiche le maximum que peut stocker chacun des types que nous utilisons. C'est quelque chose qui n'aurait pas été possible sans l'utilisation de ce nouveau mot-clé `_Generic`.
+Pour utiliser cette syntaxe, on utilise le mot clé `_Generic` auquel on passe 2 paramètres. Le premier est une expression dont le type va influencer l'expression finalement exécutée. Le deuxième est une suite d'association de type et d'expression (type : expression) dont les associations sont séparées par des virgules. Au final, seule l'expression désignée par le type de la première expression est finalement évaluée.
+
+
+Un exemple réel d'utilisation pourrait être :
+```c
+int powInt(int,int);
+
+#define POW(x,y) _Generic ((y), double: pow, float: powf, long double: powl, int: powInt)((x), (y))
+```
+
+
+Il n'y a plus grand-chose à dire si ce n'est qu'il est possible d'avoir dans la liste des types le mot `default` qui correspondra alors à tous les types non-cités. Ainsi une définition plus propre de la macro `POW` de tout à l'heure pourrait être :
+
+```c
+int powIntuInt(int a, unsigned int b);
+double powIntInt(int a, int b);
+double powFltInt(double a,int b) { return pow (a,b); }
+double powfFltInt(float a,int b) { return powf(a,b); }
+double powlFltInt(long double a,int b) { return powl(a,b); }
+
+
+#define POWINT(x) _Generic((x), double: powFltInt, \
+ float : powfFltInt, \
+ long double: powlFltInt, \
+ unsigned int: powIntuInt, \
+ default: powIntInt)
+#define POW(x,y) _Generic ((y), double: pow, float: powf, long double: powl, default: POWINT((x)) )((x), (y))
+```
Les caractères trop spéciaux
---------------------------
+
On va remonter encore dans le temps.
Je vais vous citer une dernière chose.
-Un temps où tous les caractères n'étaient pas aussi accessibles que de nos jours sur autant de type de claviers.
+Un temps où tous les caractères n'étaient pas aussi accessibles que de nos jours sur autant de types de claviers.
+
+Les claviers n'avaient pas forcément de touches de composition. Ainsi, il était impossible de taper le caractère `#`.
-Les claviers n'avaient pas forcément de touches de composition. Ainsi, il était impossible de taper le caractère '#'.
+On pouvait alors remplacer le caractère `#` par la séquence `??=`.
+Et pour chaque caractère n'étant pas sur le clavier et utiliser en langage C, il existait une séquence à base de `??` que l'on nomme trigraphe. Une autre version à base de 2 caractères plus lisible se nomme les digraphes.
-On pouvait alors remplacer la caractère '#' par la séquence "??=".
-Et pour chaque caractère composé, il existait une séquence à base de "??".
-Voici une image des séquences de trigraphe et de leur représentation en caractère.
+Voici un tableau récapitulatif des séquences de trigraphes, de digraphes et de leur représentation en caractère.
-![Trigraphes](res/trigraphe_c.png)
++-----------+----------+-----------+
+| Caractère | Digraphe | Trigraphe |
++===========+==========+===========+
+| `#` | `%:` | `??=` |
++-----------+----------+-----------+
+| `[` | `<:` | `??(` |
++-----------+----------+-----------+
+| `]` | `:>` | `??)` |
++-----------+----------+-----------+
+| `{` | `<%` | `??<` |
++-----------+----------+-----------+
+| `}` | `%>` | `??>` |
++-----------+----------+-----------+
+| `\` | | `??/` |
++-----------+ +-----------+
+| `^` | | `??'` |
++-----------+ +-----------+
+| `|` | | `??!` |
++-----------+ +-----------+
+| `~` | | `??-` |
++-----------+----------+-----------+
+| `##` | `%:%:` | |
++-----------+----------+-----------+
-Il existe également des digraphes. C'est à peu près la même chose mais en deux caractères :
-![Digraphes](res/digraphe_c.png)
-La différence **principale** est dans une chaîne de caractère :
+La différence **principale** entre les digraphes et les trigraphes est dans une chaîne de caractère :
```c
puts("??= est un croisillon");
puts("%:");
```
-Ces mécanismes du moyen age sont encore valides de nos jours en C.
+Ces mécanismes du Moyen Âge sont encore valides de nos jours en C.
Du coup, cette ligne de code est tout à fait valide :
```c
@@ -617,17 +740,13 @@ Une combinaison d'un ternaire avec un trigraphe et un digraphe et vous avez un c
printf("%d", a ?5??((tab):>:0);
```
-**À ne jamais utiliser dans un code sérieux** donc.
+**À ne jamais utiliser dans un code sérieux** donc.<Paste>
## Conclusion
-Voilà, c'est fini, j'espère que vous avez appris quelque chose de ce billet.
-Surtout n'oubliez pas d'utiliser ces syntaxes avec parcimonie;
-
-Vous pouvez (re)découvrir de nombreuses codes abusants de la syntaxe du langage C aux [IOCCC](http://ioccc.org/winners.html).
-
-
-[^tableau]: Pour être tout à fait précis. On aurait tout à fait pû prendre 1 comme premier élément du tableau. On aurait alors dû translater chaque accès de 1 de manière transparente. Cepdendant, ce n'est pas ce qui fût retenu car c'était plus simple et *plus rapide à compiler* puis c'est resté. Un article de Mike Hoye nommé [Citation Needed](http://exple.tive.org/blarg/2013/10/22/citation-needed/#content) en parle bien mieux que moi.
+Voilà, c'est fini, j'espère que vous avez appris quelque chose de ce billet. Surtout n'oubliez pas d'utiliser ces syntaxes avec parcimonie.
+Je tiens tout particulièrement à remercier [Taurre](https://zestedesavoir.com/membres/voir/Taurre/) pour avoir validé cet article, mais également pour sa pédagogie sur les forums depuis des années, ainsi que [blo yhg](https://zestedesavoir.com/membres/voir/blo%20yhg/) pour sa relecture attentive.
+À noter que vous pouvez (re)découvrir de nombreux codes abusant de la syntaxe du langage C aux [IOCCC](http://ioccc.org/winners.html). 😈