From 6c2ebfac6bef175ee37e27d9044eb2f91dd7bec9 Mon Sep 17 00:00:00 2001 From: ache Date: Thu, 13 Dec 2018 20:28:32 +0100 Subject: Remplace type of code block --- article/bizarrerie-du-langage-c.md | 172 ++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/article/bizarrerie-du-langage-c.md b/article/bizarrerie-du-langage-c.md index 730c1e7..5bc164f 100644 --- a/article/bizarrerie-du-langage-c.md +++ b/article/bizarrerie-du-langage-c.md @@ -23,33 +23,33 @@ Mais pas seulement ! C'est également un opérateur. L'instruction suivante, bien qu'inutile est tout à fait valide : -```c +~~~c printf("%d", (5,3) ); -``` +~~~ 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 d'une boucle `for` on peut faire : -```c +~~~c for( ; i < j ; i++, j-- ) { // [...] } -``` +~~~ Ou encore, dans de petits `if` pour les simplifier : -```c +~~~c if( argc > 2 && argv[2][0] == '0' ) action = 4, color = false; -``` +~~~ 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. -```c +~~~c while( c = getchar(), c != EOF && c != '\n' ) { // [...] } @@ -57,7 +57,7 @@ while( c = getchar(), c != EOF && c != '\n' ) { while( (c = getchar()) != EOF && c != '\n' ) { // [...] } -``` +~~~ Mais surtout, ne pas abuser de cet opérateur ! On peut, de manière assez rapide, obtenir des choses illisibles. Cette remarque est d'ailleurs également valide pour le prochain opérateur ! @@ -69,36 +69,36 @@ Il sert à simplifier des expressions conditionnelles. Par exemple pour afficher le minimum de deux nombres, sans le ternaire, on ferait : -```c +~~~c if (a < b) printf("%d", a); else printf("%d", b); -``` +~~~ Ou avec une variable temporaire : -```c +~~~c int min = a; if( b < a) min = b; printf("%d", min); -``` +~~~ Alors qu'avec les ternaires on fait simplement : -```c +~~~c printf("%d", a```c +>~~~c >static int b64_d[] = { > ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, > ['F'] = 5, ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, @@ -276,18 +276,18 @@ Comme ici, dans ce décodeur de base64 : > ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, > ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63, ['='] = 64 >}; ->``` +>~~~ 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 + ~~~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. @@ -297,22 +297,22 @@ Les expressions littéralement composées Puisque nous parlons des tableaux. Il existe une syntaxe simple pour utiliser des tableaux à usage unique. Je voudrais utiliser ce tableau : -```c +~~~c int tab[5] = {5, 4, 5, 2, 1}; 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 un peu dérangeant d'avoir à utiliser un identificateur juste pour ça. *Eh* bien je peux faire ceci : -```c +~~~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 plein de cas où cette syntaxe est très utile. Par exemple avec une structure : -```c +~~~c // Pour envoyer notre message : send_msg( (message){ .dst="192.168.11.1", .msg="Code 11"} ); @@ -322,7 +322,7 @@ printf("%d", distance( (point){1, 2}, (point){2, 3} ) ); // Ou encore sous Linux, en programmation système execvp( "bash" , (char*[]){"bash", "-c", "ls", NULL} ); -``` +~~~ On appelle ces expressions des *compound literals* (ou littéraux agrégats si l'on s'essaye à traduire). @@ -331,59 +331,59 @@ 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'à l'exécution. Si vous n'avez jamais entendu parler des VLAs, ceci devrait vous choquer : -```c +~~~c int n = 11; int tab[n]; for(int i = 0 ; i < n ; i++) tab[i] = 0; -``` +~~~ 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. 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 +~~~c int n = 50; int tab[n]; double tab2[2*n]; unsigned int tab[foo()]; // avec foo une fonction définie ailleurs -``` +~~~ 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 +~~~c int n = 30; int tab[n] = {0}; static tab2[n]; -``` +~~~ Dans une fonction, on pourrait utiliser : -```c +~~~c void bar(int n, int tab[n]) { } -``` +~~~ 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 +~~~c void foo( int n, int m, int tab[][m]) { } -``` +~~~ À 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. -```c +~~~c void foo(int, int, int[][*]); -``` +~~~ 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. @@ -503,19 +503,19 @@ Du coup, on n'apprend jamais ce qu'est une étiquette... Voici comment on utilise `goto` et une étiquette : -```c +~~~c goto end; end: return 0; } -``` +~~~ En gros, une étiquette est un nom que l'on donne à une instruction. *Eh* bien on en utilise des étiquettes ! Dans les `switch` ! -```c +~~~c switch( action ) { case 0: @@ -529,7 +529,7 @@ switch( action ) { default: do_action3(); } -``` +~~~ 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`... @@ -545,7 +545,7 @@ C'est une sorte de boucle déroulée optimisée. Le but est de réduire le nombr Voici la version historique écrite par Tom Duff : -```c +~~~c { register n = (count + 7) / 8; switch (count % 8) { @@ -560,7 +560,7 @@ Voici la version historique écrite par Tom Duff : } while (--n > 0); } } -``` +~~~ Peu importe ce que signifie `register`. Aussi, `to` est un pointeur particulier, mais ce n'est pas vraiment important. @@ -573,10 +573,10 @@ De même pour sa décrémentation. C'est-à-dire : -```c +~~~c while( count-- > 0 ) *to = *from++; -``` +~~~ 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. @@ -601,17 +601,17 @@ Encore une fois nous allons voir une syntaxe introduite en C99. Plus exactement, Ainsi en C, il est possible de déclarer un nombre complexe comme ceci : -```c +~~~c double complex point = 2 + 3 * I; -``` +~~~ Ici, on retrouve les macros spéciales `complex` et `I` (définie dans l'en-tête ``). 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 +~~~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. @@ -626,7 +626,7 @@ Cette généricité s'obtient avec les sélections génériques basées sur la s Pour comprendre la syntaxe, voyons un exemple simpliste : -```c +~~~c #include #include @@ -646,23 +646,23 @@ int main(int argc, char* argv[]) { 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 +~~~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 +~~~c int powIntuInt(int a, unsigned int b); double powIntInt(int a, int b); double powFltInt(double a,int b) { return pow (a,b); } @@ -676,7 +676,7 @@ double powlFltInt(long double a,int b) { return powl(a,b); } 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 --------------------------- @@ -721,24 +721,24 @@ Voici un tableau récapitulatif des séquences de trigraphes, de digraphes et de La différence **principale** entre les digraphes et les trigraphes est dans une chaîne de caractère : -```c +~~~c puts("??= est un croisillon"); puts("%:"); -``` +~~~ 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 +~~~c ??=define FIRST tab<:0] -``` +~~~ La seule utilité de cette syntaxe de nos jours est d'obfusquer un code source très facilement. Une combinaison d'un ternaire avec un trigraphe et un digraphe et vous avez un code absolument illisible. ;) -```c +~~~c printf("%d", a ?5??((tab):>:0); -``` +~~~ **À ne jamais utiliser dans un code sérieux** donc. -- cgit v1.2.3-54-g00ecf