summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorache <ache@ache.one>2018-12-13 20:28:32 +0100
committerache <ache@ache.one>2018-12-13 20:28:32 +0100
commit6c2ebfac6bef175ee37e27d9044eb2f91dd7bec9 (patch)
treed53c3d6a7a6c026d7a283f1db17a19704048e91d
parentZen mode (diff)
Remplace type of code block
-rw-r--r--article/bizarrerie-du-langage-c.md172
1 files 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<b ? a : b);
-```
+~~~
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 :
-```c
+~~~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.
@@ -108,18 +108,18 @@ L'expression `a<b ? a : b` se lit « Si `a` est inférieur à `b` alors `a` sino
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
+~~~c
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
+~~~c
#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 être mieux compris !
@@ -130,25 +130,25 @@ L'accès à un tableau
On nous a toujours appris que pour afficher le 3ème élément d'un tableau, on faisait :
-```c
+~~~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. 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 :
-```c
+~~~c
printf("%d", *(tab+2));
-```
+~~~
Puis comme l'addition est commutative, `tab+2` ou `2+tab` sont équivalents.
En fait, on aurait même pu faire :
-```c
+~~~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.
@@ -156,24 +156,24 @@ C'est du sucre syntaxique pour cacher l'arithmétique des pointeurs.[^sucre]
Par exemple pour afficher le caractère `=` pour vrai, `!` pour faux et `~` pour aucun des deux.
On pourrait faire :
-```c
+~~~c
if( is_good == 1 )
printf("%c", '=');
else if( is_good == 0 )
printf("%c", '!');
else
printf("%c", '~');
-```
+~~~
Mais il existe plus simple :
-```c
+~~~c
printf("%c", "!=~"[is_good]);
// Ou comme on l'a vu :
printf("%c", is_good["!=~"] ); // Affiche '!' si is_good vaut 0
// '=' si is_good vaut 1
// '~' si is_good vaut 2
-```
+~~~
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 bon de savoir que ça existe. ^^
@@ -189,10 +189,10 @@ L'initialisation, c'est quelque chose que l'on maîtrise en C. C'est le fait de
En gros, on définit sa valeur.
Pour un tableau[^tableau] :
-```c
+~~~c
int tab[10] = {0};
tab[2] = 5;
-```
+~~~
À 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.
@@ -200,15 +200,15 @@ La ligne suivante est une affectation et non une initialisation.
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
+~~~c
int tab[10] = {0, 0, 5};
-```
+~~~
Mais en réalité, il existe une autre syntaxe qui permet de faire plus simple :
-```c
+~~~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.
@@ -218,21 +218,21 @@ Une syntaxe équivalente existe d'ailleurs pour les structures et les unions.
On peut ainsi initialiser un point en utilisant ses composantes.
-```c
+~~~c
typedef struct point {
int x,y;
} point;
point A = {.x = 1, .y = 2};
-```
+~~~
Ici, il n'y avait pas d'ambigüité.
Mais pour une structure plus complexe, cette syntaxe est vraiment avantageuse.
Tenez :
-```c
+~~~c
typedef struct message {
char src[20], dst[20], msg[200];
} message;
@@ -254,13 +254,13 @@ message to_send = { .msg="Code 10", .dst="23:12:23", .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é.
Parfois, ces syntaxes sont très judicieusement utilisées !
Comme ici, dans ce décodeur de base64 :
->```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 `<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
+~~~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 <stdio.h>
#include <limits.h>
@@ -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.<Paste>