Partage
  • Partager sur Facebook
  • Partager sur Twitter

Discussions sur la FAQ du forum de C

Pas celle sur les bibliothèques tierces.

26 décembre 2011 à 22:16:18

Je vois que tu l'as poster dans la FAQ. Il n'y a plus qu'à attendre le passage de GuilOooo pour qu'il ajoute un lien dans la liste des entrées. Tu peux peut être lui envoyer un mp pour le prévenir ;)
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
26 décembre 2011 à 22:22:05

Je vais attendre qu'il se connecte, je pense qu'il verra qu'il y a un nouveau message dans la FAQ. ^^
  • Partager sur Facebook
  • Partager sur Twitter
14 mars 2012 à 21:47:27

Hep !

Dans la FAC j'ai vu ceci :
[1][4] Comment récupérer des entiers avec "scanf"/"printf" ? des flottants ?


Voici deux extraits d'un tuto langage C en préparations (partie "Présentation de la Bibliothèque standard") :

printf

Codes de format


Je me suis grandement inspirer de la page man de printf pour cette partie.
Chaque code de format a la structure suivante :

% [drapeau] [largeur] [.précision] [modificateur] conversion

Les indicateurs de conversion sont obligatoires !


Chaîne de format


La chaîne de format est composée d'indicateurs : les caractères ordinaires (différents de %), qui sont copiés sans modification sur la sortie, et les spécifications de conversion, qui sont mises en correspondances avec les arguments suivants. Les spécifications de conversion sont introduites par le caractère %, et se terminent par un indicateur de conversion. Entre eux peuvent se trouver (dans l'ordre), zéro ou plusieurs attributs, une valeur optionnelle de largeur minimal de champ, une valeur optionnelle de précision, et un éventuel modificateur de longueur.


Drapeau (caractère d'attribut)


Le caractère % peut être éventuellement suivi par un ou plusieurs attributs suivants :

#
Cet attribut indique que la valeur doit être convertie en une autre forme. Ul n'affecte seulement que les types suivants :
  • o : fait précéder de 0 toute valeur non nulle
  • x ou X : fait précéder de 0x ou 0X la valeur affichée
  • a, A, e, E ou f : le point décimal apparaît toujours
  • g ou G : le point décimal apparaît toujours, de plus, les zéro de droite ne seront pas supprimé

-
Cet attribut indique que la valeur doit être justifiée sur la limite gauche du champ (par défaut elle l'est à droite).


' '
C'est attribut (l'espace) indique qu'un espace doit être laissé avant un nombre positif (ou une chaîne vide) produit par une conversion signée.

+
Cet attribut indique que le signe doit toujours être présent avant un nombre produit par une conversion signée. Un attribut + surcharge un attribut 'espace' si les deux sont fournis.

'
Pour les conversions décimales (i, d, u, f, g, G) indique que les chiffres d'un argument numérique doivent être groupés par millier en fonction de la localisation. Remarquez que de nombreuses versions de gcc n'accepte pas cet attribut et déclencheront un avertissement.


Largeur (largeur de Champ)


Un nombre (ne commençant pas par un zéro), peut indiquer une largeur minimale de champ. Si la valeur convertie occupe moins de caractères que cette largeur, elle sera complétée par des espaces à gauche par défaut. L'argument fournissant la largeur doit être de type int. Une largeur négative est considéré comme l'attribut '-' vu plus haut suivi d'une largeur positive. Une largeur trop petite n’engendra pas la troncature du champ. Si le résultat de la conversion est plus grand que la largeur indiquée, le champ est élargi pour contenir le résultat.


Précision


Une précision, sous la forme d'un point ('.') suivi par un nombre indique le nombre de chiffre il y aura dernière la virgule. Cette précision doit être de type int. Si la précision ne contient que le caractère '.', ou une valeur négative, elle est considérée comme nulle. Cette précision indique un nombre minimum de chiffres à faire apparaître lors des conversions d, i, o, u, x, et X, le nombre de décimales à faire apparaître pour les conversions a, A, e, E, f, et F, le nombre maximum de chiffres significatifs pour g et G, et le nombre maximum de caractères à imprimer depuis une chaîne pour les conversions S et s.


Modificateur (modificateur de longueur)


Une conversion entière correspond aux indicateurs de conversions d, i, o, u, x, ou X.

hh : signed char
La conversion entière suivante correspond à un signed char ou unsigned char, ou la conversion n suivante correspond à un argument pointeur sur un signed char.

h : short int | unsigned short int
La conversion entière suivante correspond à un short int ou unsigned short int, ou la conversion n suivante correspond à un argument pointeur sur un short int.

l : long int
La conversion entière suivante correspond à un long int ou unsigned long int, ou la conversion n suivante correspond à un pointeur sur un long int, ou la conversion c suivante correspond à un argument wint_t, ou encore la conversion s suivante correspond à un pointeur sur un wchar_t.

ll : long long int
La conversion entière suivante correspond à un long long int, ou unsigned long long int, ou la conversion n suivante correspond à un pointeur sur un long long int.

L : long double
La conversion a, A, e, E, f, F, g, ou G suivante correspond à un argument long double. (C99 autorise %LF mais pas SUSv2).

j : intmax_t | uintmax_t
La conversion entière suivante correspond à un argument intmax_t ou uintmax_t.

t : ptrdiff_t
La conversion entière suivante correspond à un argument ptrdiff_t.


Conversion (indicateur de conversion)


C'est la partie la plus importante et surtout obligatoire, d’ailleurs, nous en n'avons déjà vu quelques uns.
Un caractère indique le type de conversion à apporter. Les indicateurs de conversion ainsi que leurs significations sont :

d, i : int
L'argument int est convertie en un chiffre décimal signé. La précision, si elle est mentionné, correspond au nombre minimal de chiffres qui doivent apparaître. Si la conversion fournit moins de chiffres, le résultat est rempli à gauche avec des zéros. Par défaut la précision vaut 1. Lorsque 0 est converti avec une précision valant 0, la sortie est vide.

o, u, x, X : unsigned int
L'argument unsigned int est converti en un chiffre octal non-signé (o), un chiffre décimal non-signé (u), un chiffre héxadécimal non-signé (x et X). Les lettres abcdef sont utilisées pour les conversions avec x, les lettres ABCDEF sont utilisées pour les conversions avec X. La précision, si elle est indiquée, donne un nombre minimal de chiffres à faire apparaître. Si la valeur convertie nécessite moins de chiffres, elle est complétée à gauche avec des zéros. La précision par défaut vaut 1. Lorsque 0 est converti avec une précision valant 0, la sortie est vide.

e, ", ", E : double
L'argument réel, de type double, est arrondi et présenté avec la notation scientifique [-]c.ccce\*(Pmcc dans lequel se trouve un chiffre avant le point, puis un nombre de décimales égal à la précision demandée. Si la précision n'est pas indiquée, l'affichage contiendra 6 décimales. Si la précision vaut zéro, il n'y a pas de point décimal. Une conversion E utilise la lettre E (plutôt que e) pour introduire l'exposant. Celui-ci contient toujours au moins deux chiffres. Si la valeur affichée est nulle, son exposant est 00.

f, F : double
L'argument réel, de type double, est arrondi, et présenté avec la notation classique [-]ccc.ccc, où le nombre de décimales est égal à la précision réclamée. Si la précision n'est pas indiquée, l'affichage se fera avec 6 décimales. Si la précision vaut zéro, aucun point n'est affiché. Lorsque le point est affiché, il y a toujours au moins un chiffre devant.

g, G : double
L'argument réel, de type double, est converti en style f ou e (ou E pour la conversion G) La précision indique le nombre de décimales significatives. Si la précision est absente, une valeur par défaut de 6 est utilisée. Si la précision vaut 0, elle est considérée comme valant 1. La notation scientifique e est utilisée si l'exposant est inférieur à -4 ou supérieur ou égal à la précision démandée. Les zéros en fin de partie décimale sont supprimés. Un point decimal n'est affiché que s'il est suivi d'au moins un chiffre.

a, A : double
(C99 mais pas SUSv2). Pour la conversion a, l'argument de type double est transformé en notation hexadécimale (avec les lettres "abcdef) dans le style [-]0xh.hhhhp\*(Pmd; Pour la conversion A, le préfixe 0X, les lettres ABCDEF et le séparateur d'exposant P sont utilisés. Il y a un chiffre hexadécimal avant la virgule, et le nombre de chiffres ensuite est égal à la précision. La précision par défaut suffit pour une représentation exacte de la valeur, si une représentation exacte est possible en base 2. Sinon elle est suffisament grande pour distinguer les valeurs de type double.

c : char
S'il n'y a pas de modificateur l, l'argument entier, de type int, est converti en un unsigned char, et le caractère correspondant est affiché. Si un modificateur l est présent, l'argument de type wint_t (caractère large) est converti en séquence multi-octet par un appel à wctomb, avec un état de conversion débutant dans l'état initial. La chaîne multi-octet résultante est écrite.

s : const char *
S'il n'y a pas de modificateur l, l'argument de type const char * est supposé être un pointeur sur un tableau de caractères (pointeur sur une chaîne). Les caractères du tableau sont écrits jusqu'au caractère '\0' final, non compris. Si une précision est indiquée, seul ce nombre de caractères sont écrits. Si une précision est fournie, il n'y a pas besoin de caractère nul. Si la précision n'est pas donnée, ou si elle est supérieure à la longueur de la chaîne, le caractère '\0' final est nécessaire.
Si un modificateur l est présent, l'argument de type const wchar_t * est supposé être un pointeur sur un tableau de caractères larges. Les caractères larges du tableau sont convertis en une séquence de caractères multi-octets (chacun par un appel de wctomb, avec un état de conversion dans l'état initial avant le premier caractère large), ceci jusqu'au caractère large '\0' final compris. Les caractères multi-octets résultants sont écris jusqu'à l'octet '\0' final (non compris). Si une précision est fournie, il n'y a pas plus d'octets écrits que la précision indiquée, mais aucun caractère multi-octet n'est écrit partiellement. Remarquez que la précision concerne le nombre d'octets écrits, et non pas le nombre de caractères larges ou de positions d'écrans. La chaîne doit contenir un caractère large '\0' final, sauf si une précision est indiquée, suffisament petite pour que le nombre d'octets écrits la remplisse avant la fin de la chaîne.

p : void * (pointeur)
L'argument pointeur, du type void * est affiché en héxadécimal, comme avec %#x ou %#lx.

n : int *
Le nombre de caractères déjà écrits est stocké dans l'entier indiqué par l'argument pointeur de type int *. Aucun argument n'est converti.

% : %%
Un caractère '%' est écrit. Il n'y a pas de conversion. L'indicateur complet est '%%'.



scanf

Codes de format


Chaque code de format a la structure suivante :

% [*] [largeur] [modificateur] conversion

Les indicateurs de conversion sont obligatoires !


Chaîne de format


La chaîne de format est composée d'indicateurs : les caractères ordinaires (différents de %), qui sont copiés sans modification sur la sortie, et les spécifications de conversion, qui sont mises en correspondances avec les arguments suivants. Les spécifications de conversion sont introduites par le caractère %, et se terminent par un indicateur de conversion. Entre eux peuvent se trouver (dans l'ordre), zéro ou plusieurs attributs, une valeur optionnelle de largeur minimal de champ, une valeur optionnelle de précision, et un éventuel modificateur de longueur.


Largeur (largeur de Champ)


La largeur précise la nombre de caractères n qui seront lus. On peut en lire moins si l'on rencontre un séparateur (espace, tabulation, retour chariot ...) ou un caractère invalide.


Modificateur (modificateur de longueur)


Le modificateur précise la taille de l'objet recevant la valeur.

h : short int
L'élément correspondant est l'adresse d'un short int. Ce modificateur ne s'utilise qu'avec seulement les caractères de conversion suivants : d, i, n, o, u, x.

l : long int | double
L'élément correspondant est l'adresse d'un élément de type :
  • long int pour les caractères de conversion suivants : d, i, n, o, u, x;
  • double pour les caractères de conversion suivants : d, i, n, o, u, x;

L : long double
L'élément correspondant est l'adresse d'un long double. Ce modificateur ne s'utilise qu'avec seulement les caractères de conversion suivants : e, f, g.


Conversion (indicateur de conversion)


C'est la partie la plus importante et surtout obligatoire, d’ailleurs, nous en n'avons déjà vu quelques uns.
Un caractère indique le type de conversion à apporter. Les indicateurs de conversion ainsi que leurs significations sont :

d : int *
Correspond à un entier décimal éventuellement signé, le pointeur correspondant doit être du type int *.

i : int *
Correspond à un entier éventuellement signé. Le pointeur suivant doit être du type int *. L'entier est en base 16 (héxadécimal) s'il commence par `0x' ou `0X', en base 8 (octal) s'il commence par un `0', et en base 10 sinon. Seuls les caractères correspondants à la base concernée sont utilisés.

o : unsigned int *
Correspond à un entier octal non signé. Le pointeur correspondant doit être du type unsigned int *.

u : unsigned int *
Correspond à un entier décimal non signé. Le poin­teur suivant doit être du type unsigned int *.

x : int * | signed int * | unsigned int *
Correspond à un entier hexadécimal non signé. Le pointeur suivant doit être du type int *, signed int * ou encore unsigned int *.

f : float *
Correspond à un nombre réel éventuellement signé. Le pointeur correspondant doit être du type float *.

e : float *
Correspond à un nombre en notation scientifique, avec un chiffre avant la virgule, six ans après la virgule, et un exposant. Le pointeur correspondant doit être du type float *.

g : float *
Correspond à un nombre comme une mantisse à six chiffres, avec un exposant si nécessaire. Le pointeur correspondant doit être du type float *.

c : char *
Correspond suivant la longueur à :
  • Un caractère lorsqu'aucune longueur n'est spécifiée ou que c'elle-ci est égale à 1
  • Une suite de caractère lorsqu'une longueur différente de 1 est spécifiée. Attention, il ne s'agit pas d'un véritable chaîne de caractère puisque le caractère '\0' de sera pas ajouté en fin de chaîne.
Le pointeur correspondant doit être du type char *.

s : char *
Correspond à une séquence de caractères différents des caractères blancs. Le pointeur correspondant doit être du type char *, et la chaîne doit être assez large pour accueillir toute la séquence, ainsi que le caractère '\0 final. La conversion s'arrête au premier caractère blanc, ou à la longueur maximale du champ.

p : void *
Correspond à une valeur de pointeur (comme affichée par '%p' dans printf. Le pointeur correspondant doit être du type void *.

n : int *
Aucune lecture n'est faite. Le nombre de caractères déjà lus est stocké dans le pointeur correspondant, qui doit être de type int *.


Je ne sais pas ce que cela vaut mais si cela peut aider. :)
  • Partager sur Facebook
  • Partager sur Twitter
11 avril 2012 à 23:11:26

Une proposition d’entrée :


[3][9] Comment passer d’un caractère de '0' à '9' au nombre correspondant (et vice-versa) ?


Comme vous devez le savoir, les caractères (stockés dans le type char) sont en fait représentés par des nombres. Les valeurs varient selon votre système (compilateur et machine) mais c’est souvent celles de l’ASCII.

Parmi les caractères disponibles, il y a notamment les chiffres de zéro à neuf : '0', '1', '2',, '9'. Cependant, leur valeur n’est pas 0, 1, 2, …, 9 (en ASCII, '0' vaut 48).
Heureusement, il existe une astuce simple pour passer de l’un à l’autre :

char c;
int i;

/* pour passer du caractère au nombre */
c = '6';          // c vaut maintenant '6' (soit 54 en ASCII)
i = c - '0';      // i vaut maintenant 6, et non 54 (si ASCII)

/* pour passer du nombre au caractère */
i = 7;            // i vaut maintenant 7
c = i + '0';      // c vaut maintenant '7' (soit 55 en ASCII), et non le caractère spécial de valeur 7 (si ASCII)

Écrire simplement « c = i » (ou « i = c » dans l’autre sens) n’aurait pas marché, car dans le 1er cas i aurait valu 54 au lieu de 6, et dans le 2nd cas c aurait valu 7, ce qui correspond en ASCII à un caractère spécial.

Cette astuce se base sur le fait que les caractères chiffres ('0', '1', '2', '3'…) ont des valeurs consécutives ; la 1ère valeur est celle du chiffre zéro, soit '0' dans le cas général.
Il suffit donc d’une simple opéraion de décalage (rajouter ou soustraire la valeur '0') pour passer d’un nombre de 0 à 9 au caractère chiffre correspondant, et vice-versa.


<secret>NB : Si jamais un évêque à plume du forum C (ou n’importe qui de façon générale, n’attendez pas les plumes) vient vous sermonner que cette solution n’est pas portable, vous lui mettrez sous le bec cet extrait de la norme : ;)

Citation : norme C99 (draft n1256) − 5.2.1, §3

Both the basic source and basic execution character sets shall have the following members:
[…]

  • the 10 decimal digits 0 1 2 3 4 5 6 7 8 9

[…]
[…] In both the source and execution basic character sets, the value of each character after 0
in the above list of decimal digits shall be one greater than the value of the previous. […]</span>

</secret>

Cette astuce ne marche pas si vous voulez dépasser 9 (eh oui, les chiffres arabes s’arrêtent à 9). Elle ne marche pas non plus si vous espérez convertir les lettres ('A', 'B', 'C', …) en nombres selon le système hexadécimal (où la lettre A signifie 10, B signifie 11, C 12…). En effet, dans le jeu de caractères du système, il n’est pas garanti que les lettres soient consécutives aux chiffres, ni même qu’elles soient « rangées » dans l’ordre et de façon croissante. Enfin, il faut distinguer les minuscules des majuscules (ce sont deux caractères différents).
Dans la plupart des jeux (dont l’ASCII), les lettres latines sont bien consécutives et triées ; par conséquent, sur ces systèmes, il est possible de recourir à une astuce similaire pour passer de la lettre au nombre :
if(c>='0' && c<='9')    // Si c est un chiffre de 0 à 9   [PORTABLE]
    i = c - '0';
if(c>='A' && c<='Z')    // Si c est une lettre majuscule  [NON PORTABLE]
    i = c - 'A' + 10;
if(c>='a' && c<='z')    // Si c est une lettre minuscule  [NON PORTABLE]
    i = c - 'a' + 10;
Le +10 servant à donner la valeur 10 et non 0 à la lettre A. Toutefois, cette solution n’est pas portable. :'(



Je peux reformuler la toute fin sans l’humour si vous (en particulier la personne concernée) estimez que je ne dois pas dire ça, mais sur le coup je n’ai pas pu résister.
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 7:56:31

Désolé, perso, je ne mettrais pas cette 'touche' d'humour ...
C'est pas de moi qu'il s'agit, mais quand même ...

Ou du moins, comme cela, c'est un peu familier ;) Je ne pense pas qu'il le prenne mal, mais quand même ...

À ta place, j'aurais même pas fait allusion à notre 'évêque à plume' :p

De plus, il serrait utile de rajouter que ça ne marche pas pour les lettres (un avertissement ou truc dans le même genre) ...
  • Partager sur Facebook
  • Partager sur Twitter

🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles  - ♡ Copying is an act of love.

12 avril 2012 à 12:25:48

Citation : Maëlan


NB : Si jamais un évêque à plume du forum C (ou n’importe qui de façon générale, n’attendez pas les plumes) vient vous sermonner que cette solution n’est pas portable, vous lui mettrez sous le bec cet extrait de la norme : ;)



C'est à lui que tu faisais allusion? :p
Plus sérieusement, la touche d'humour ne me dérange pas, maintenant je ne suis pas certains que tous les débutants la comprendront ;)
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 12:27:53

En plus, oui c'est vrai ... Mais on va quand même pas mettre un lien pour qu'ils puissent comprendre ...

Donc le mieux, c'est de la virer ^^

PS: Il (Elle ?) le prendre plutôt bien ...
  • Partager sur Facebook
  • Partager sur Twitter

🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles  - ♡ Copying is an act of love.

12 avril 2012 à 12:52:20

Citation : @che

De plus, il serrait utile de rajouter que ça ne marche pas pour les lettres (un avertissement ou truc dans le même genre) ...


C’est fait (mais je trouve ça un peu long finalement).

Je ne vois pas où est le mal, si Taurre est d’accord. Ce n’est pas méchant, hein, juste de la taquinerie (oh que je suis taquin). Évidemment que la plupart des lecteurs ne comprendront pas, ce serait une « blague privée ». ’fin, s’il y a des gens qui s’y opposent, je laisserai tomber.

Citation : Taurre

C'est à lui que tu faisait allusion? :p


Exactement.
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 12:57:35

Voila j'ai compris la touche d'humour. ^^

Moi je suis pour.
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 13:10:32

Pendant que j’y suis, d’autres propositions en vrac…


[8][12] Que signifie « ++i » ? Quelle différence avec « i++ » ?

Saviez-vous qu’il existe deux opérateurs d’incrémentation, et autant pour la décrémentation ? En fait, on parle d’incrémentation ou de décrémentation postfixée (n++, n--) ou préfixée (++n, --n). La préfixée ressemble beaucoup à la postfixée, mis à part que l’opérateur se place avant son opérande.
Quelle est la différence ? Les opérateurs préfixés modifient la variable, puis renvoient sa nouvelle valeur, alors que les postfixés renvoient la valeur de la variable avant de la modifier.
Exemple :
int a=5,  b=5;
printf("%d\n", ++a);   // affiche 6
// a vaut maintenant 6
printf("%d\n", b++);   // affiche 5
// b vaut maintenant 6


Un point tordu pour finir : si f est une fonction, dans l’expression f(n++), l’argument de f aura l’ancienne valeur de n, mais n sera incrémenté avant l’exécution de f…

L’intérêt de tout ça ? L’opérateur préfixé est plus rapide que le postfixé. En effet, le postfixé impose de créer une variable temporaire pour renvoyer la valeur de la variable avant sa modification. Vous pouvez donc faire une micro-optimisation de vos programmes en remplaçant vos [in/dé]crémentations postfixées par des préfixées dès que possible, par exemple dans vos boucles : for(i=0; i<n; ++i).
Cependant, en pratique, votre compilateur favori est sans doute capable de faire une telle optimisation tout seul. Voyant que vous ne récupérez pas la valeur de retour de l'expression (i++), il sait qu’il peut la remplacer par (++i).
En C++, c’est différent. En effet, dans le cas d’un objet, les opérateurs ++ et -- sont en fait des appels de fonctions maquillés par la surcharge des opérateurs, et le compilateur ne peut donc pas optimiser (contrairement au C où il sait ce que font ces opérateurs).
De plus, en C++, le gain de performances en utilisant la version préfixée peut être significatif car si ça se trouve, les fonctions derrière sont très lourdes et la version préfixée permet de les simplifier largement. Prenez donc l’habitude d’utiliser les bons opérateurs.
Pour plus d’informations à ce sujet, allez voir ici.


Celle-ci aurait peut-être besoin de quelques reformulations (notamment le début) pour s’intégrer dans la FAQ (à la base, c’est un extrait de ma biographie).


[8][13] Que signifient les deux-points « : » à coté d’un membre de structure ?

[ À rédiger si vous n’avez pas d’objection, il s’agit de présenter les bit-fields (je sais que de temps en temps des débutants posent la question sur le forum après être tombé sur un code les utilisant). ]



Que pensez-vous de rajouter un tableau récapitulatif des opérateurs du C (un peu comme cela a été fait pour le C++) ? cf ma bio…
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 15:44:02

Citation : Maëlan


Pendant que j’y suis, d’autres propositions en vrac…


[8][12] Que signifie « ++i » ? Quelle différence avec « i++ » ?

Saviez-vous qu’il existe deux opérateurs d’incrémentation, et autant pour la décrémentation ? En fait, on parle d’incrémentation ou de décrémentation postfixée (n++, n--) ou préfixée (++n, --n). La préfixée ressemble beaucoup à la postfixée, mis à part que l’opérateur se place avant son opérande.
Quelle est la différence ? Les opérateurs préfixés modifient la variable, puis renvoient sa nouvelle valeur, alors que les postfixés renvoient la valeur de la variable avant de la modifier.
Exemple :
int a=5,  b=5;
printf("%d\n", ++a);   // affiche 6
// a vaut maintenant 6
printf("%d\n", b++);   // affiche 5
// b vaut maintenant 6


Un point tordu pour finir : si f est une fonction, dans l’expression f(n++), l’argument de f aura l’ancienne valeur de n, mais n sera incrémenté avant l’exécution de f…

L’intérêt de tout ça ? L’opérateur préfixé est plus rapide que le postfixé. En effet, le postfixé impose de créer une variable temporaire pour renvoyer la valeur de la variable avant sa modification. Vous pouvez donc faire une micro-optimisation de vos programmes en remplaçant vos [in/dé]crémentations postfixées par des préfixées dès que possible, par exemple dans vos boucles : for(i=0; i<n; ++i).
Cependant, en pratique, votre compilateur favori est sans doute capable de faire une telle optimisation tout seul. Voyant que vous ne récupérez pas la valeur de retour de l'expression (i++), il sait qu’il peut la remplacer par (++i).
En C++, c’est différent. En effet, dans le cas d’un objet, les opérateurs ++ et -- sont en fait des appels de fonctions maquillés par la surcharge des opérateurs, et le compilateur ne peut donc pas optimiser (contrairement au C où il sait ce que font ces opérateurs).
De plus, en C++, le gain de performances en utilisant la version préfixée peut être significatif car si ça se trouve, les fonctions derrière sont très lourdes et la version préfixée permet de les simplifier largement. Prenez donc l’habitude d’utiliser les bons opérateurs.
Pour plus d’informations à ce sujet, allez voir ici.




Cette entrée me paraît très bien, je suis pour.
Concernant les champs de bits, voici une petite introduction:


[8][13] Que signifient les deux-points « : » à coté d’un membre de structure ?

Les deux-points à côté d'un membre d'une structure signifie que ce dernier est un champ de bits. Cela permet de spécifier, pour un champ de type int , unsigned int ou _Bool (C99), le nombre de bits que l'on souhaite utiliser (ce nombre ne doit bien évidemment pas excéder le nombre de bits composant le type).

Cette technique peut-être très utile afin de gagner de la place quand vous savez que vous n'utiliserez jamais toute la capacité du type int, unsigned int ou _Bool.


Citation : Maëlan


Que pensez-vous de rajouter un tableau récapitulatif des opérateurs du C (un peu comme cela a été fait pour le C++) ? cf ma bio...



J'y avais pensé également :)
À la base, j'avais dans l'idée d'écrire un tutoriel sur les expressions, mais je me suis petit à petit rendu compte que le sujet était vaste, très vaste et qu'au final le tutoriel risquait d'être assez compliqué pour pas grand chose. Je pensais donc à le morceler en questions pour la FAQ sur des points précis et intéressants (comme les opérateurs, les effets de bords, les points de séquences, ...). Voici ce que j'avais rédigé concernant les opérateurs (à jour par rapport au C11):


Citation : Taurre


Un opérateur peut être défini comme le symbole d'une opération mathématique ou logique. Par exemple, l'opérateur + est le symbole de l'opération mathématique d'addition et l'opérateur && est le symbole de l'opération logique de conjonction. Toutefois, le langage C ne limite pas ses opérateurs aux opérations mathématiques et logiques. En effet, sizeof et & par exemple sont tous deux des opérateurs et permettent respectviement d'obtenir la taille et l'adresse de leurs opérandes.


Mais, le C compte combien d'opérateurs alors? o_O



Hé bien, le C dispose de beaucoup d'opérateurs, en vérité 48 :-°
En voici la liste exhaustive:

Niveau de priorité Opérateur Associativité Nombre d'opérandes
1 [] . -> de gauche à droite 2
() ++ -- de gauche à droite 1
2 ++ -- sizeof _Alignof & * + - ~ ! de droite à gauche 1
3 (type) de droite à gauche 1
4 * / % de gauche à droite 2
5 + - de gauche à droite 2
6 << >> de gauche à droite 2
7 < <= > >= de gauche à droite 2
8 == != de gauche à droite 2
9 & de gauche à droite 2
10 ^ de gauche à droite 2
11 | de gauche à droite 2
12 && de gauche à droite 2
13 || de gauche à droite 2
14 ?: de droite à gauche 3
15 = *= /= %= += -= <<= >>= &= ^= |= de droite à gauche 2
16 , de gauche à droite 2



Le tableau présente les différents opérateurs du C classé selon leur ordre de priorité. Il indique également leur associativité ainsi que le nombres d'opérandes qu'ils attendent.


Remarquez que les opérateurs ++ et -- se retrouvent deux fois dans le tableau, car il faut distinguer leur forme postfixée (qui a une priorité plus grande) de leur forme préfixée.




Priorité des opérateurs



La priorité des opérateurs permet de déterminé l'ordre dans lequel les opérations composant une expression seront effectuées. Prenons un exemple simple, l'expression 5 + 4 * 3. Il y a deux manières de calculer sa valeur:

- effectuer l'addition puis la multiplication;
- effectuer la multiplication puis l'addition.

L'ordre des opérations est extrêmement important puisqu'il va déterminer la valeur de l'expression. Dans le premier cas, on obtiendra 27 et dans l'autre on obtiendra 17. Grâce à la priorité des opérateurs, nous savons que la multiplication doit s'effectuer avant l'addition (la multiplication à un niveau de priorité de 4 alors que l'addition à un niveau de priorité de 5) et que la valeur de l'expression sera donc de 17. En fait, il serait possible de réécrire l'expression comme ceci: 5 + (4 * 3).


Associativité



Malheureusement, la priorité des opérateurs ne suffit pas à lever toute ambiguité. En effet, que faire dans les cas où les opérateurs ont la même priorité? Par exemple, dans l'expression 5 + 4 - 3, qui de l'addition ou de la soustraction doit être réalisée la première?


Ben on s'en fiche non? Puisque le résultat sera le même?



Vous avez raison, l'ordre des opérations n'a ici aucune influence sur la valeur de l'expression. Cependant, il est des cas où cela peut poser problème. Prenez l'expression a = b = c, comment procède-t-on? En affectant la valeur de c à b puis à a ou en affectant la valeur de a à b puis à c? C'est ici que l'associativité intervient, en déterminant un sens de lecture. Pour l'opérateur =, l'associativité est de droite à gauche, c'est donc la valeur de c qui sera affectée à b puis a. L'expression pourrait donc être reformulée comme suit: a = (b = c).




EDIT: correction suivant la remarque de Nathalya.
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
12 avril 2012 à 15:51:23

C'est très bien, je peux aider à la rédaction si tu veux. :)

Pourquoi ne pas rajouter une partie sur les mots-clefs du C ? Avec ceux apportés par C99 / C11. Ça existe déjà sur le forum C++ mais c'est pas top à mon goût. De plus, un débutant C n'a pas le réflexe d'aller voir les forums C++.

Ps : euh j'ai pas compris ton humour Maëlan. Qui c'est cet "évêque à plumes" ?
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 16:14:50

Citation : informaticienzero

Qui c'est cet "évêque à plumes" ?


Cf avatar de Taurre ... Tux en "évêque" => Évêque à plumes ...

En fait, il fallait suivre un autre sujet pour comprendre, j'ai plus le lien par-contre ^^"
  • Partager sur Facebook
  • Partager sur Twitter

🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles  - ♡ Copying is an act of love.

12 avril 2012 à 16:26:37

Citation : informaticienzero


C'est très bien, je peux aider à la rédaction si tu veux. :)



Je les posterais sans doute au fur et à mesure sur ce sujet et toute contribution sera la bienvenue ;)

Citation : informaticienzero


Pourquoi ne pas rajouter une partie sur les mots-clefs du C ? Avec ceux apportés par C99 / C11. Ça existe déjà sur le forum C++ mais c'est pas top à mon goût. De plus, un débutant C n'a pas le réflexe d'aller voir les forums C++.



Pour les mots-clés, j'en parle dans le dernier chapitre de mon tutoriel sur les identificateurs (il faudrait d'ailleurs que je le mette à jour par rapport au C11). Maintenant, c'est vrai qu'un débutant n'ira pas forcément le lire étant donné sa difficulté. On pourrait donc effectivement les lister dans la FAQ.

Citation : @che


En fait, il fallait suivre un autre sujet pour comprendre, j'ai plus le lien par-contre ^^"



Pour le sujet, c'était celui-ci ^^
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 16:32:41

Niveau de priorité Opérateur Associativité Nombre d'opérandes
1 [] () . -> ++ -- de gauche à droite 2


l'arité de [] . -> est bien 2, mais l'arité de () ++ -- c'est 1. Pour les parenthèses, je considère que c'est 1, et que les appels de fonction sont un cas à part.
  • Partager sur Facebook
  • Partager sur Twitter
64kB de mémoire, c'est tout ce dont j'ai besoin
12 avril 2012 à 16:38:20

Effectivement, belle erreur de ma part >_<
Merci pour la correction, j'édite ma réponse ;)
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
12 avril 2012 à 16:40:41

Citation : @che

Citation : informaticienzero

Qui c'est cet "évêque à plumes" ?


Cf avatar de Taurre ... Tux en "évêque" => Évêque à plumes ...

En fait, il fallait suivre un autre sujet pour comprendre, j'ai plus le lien par-contre ^^"



Citation : Taurre

Pour le sujet, c'était celui-ci ^^



Ah d'accord. Oui je n'y avais jamais vraiment prêté attention. ^^

Citation : Taurre

Pour les mots-clés, j'en parle dans le dernier chapitre de mon tutoriel sur les identificateurs (il faudrait d'ailleurs que je le mette à jour par rapport au C11). Maintenant, c'est vrai qu'un débutant n'ira pas forcément le lire étant donné sa difficulté. On pourrait donc effectivement les listés dans la FAQ.



Je pensais plus à faire une liste de tous les mot-clefs avec des explications sur chacun d'eux.
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 18:06:29

Citation : informaticienzero


Je pensais plus à faire une liste de tous les mot-clefs avec des explications sur chacun d'eux.



Le problème, c'est que certains mot-clés nécessite de longues explications. Je pense notamment aux qualificateurs restrict et volatile, ainsi qu'aux spécificateurs de classe de stockage static et extern. Je serait plutôt pour simplement les lister et laisser leur explication à des tutoriels spécifiques.
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
12 avril 2012 à 18:30:18

C'est vrai, mais je pense qu'un tutoriel expliquant les mots-clefs ne passerait pas en validation, à moins d'être vraiment le plus complet et exhaustif possible.
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 18:49:14

Citation : informaticienzero


C'est vrai, mais je pense qu'un tutoriel expliquant les mots-clefs ne passerait pas en validation, à moins d'être vraiment le plus complet et exhaustif possible.



Je suis du même avis et de toute manière il serait bien trop long :-°
En fait, je pensais plutôt à des tutoriels spécifiques par mot-clés. Par exemple, mon tutoriel sur les identificateurs explique le contexte d'utilisation des spécificateurs static et extern. Le qualificateur restrict mériterait franchement un tutoriel à lui tout seul. Quant à volatile, il aurait sa place dans un tutoriel sur les sauts non locaux (setjmp et longjmp).
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 18:52:16

Pour les opérateurs (priorité, associativité et arité − même si je n’introduit pas ce terme), j’avais déjà rédigé quelque chose dans ma bio.


Le langage C intègre de nombreux opérateurs : 47 en tout ! Certains sont plus fréquents que d'autres, mais tous ont leur utilité. Le tutoriel officiel de M@teo21, comme de nombreux cours pour débutants, ne les présente pas tous car ils ne sont pas utiles pour débuter.

Le tableau ci-dessous les représente tous. Je ne les expliquerai pas ici : soit vous les connaissez, soit vous ne les connaissez pas. Il a surtout pour but de vous offrir une vision d'ensemble et de rappeler leur priorité et leur associativité. En cas de doute, il peut servir de mémo.

Quelques notions préalables :
  • Chaque opérateur a une ou des opérandes. Les opérandes, ce sont les expressions à partir desquelles on effectue l'opérateur. Par exemple, l'opérateur + nécessite deux opérandes (une avant et une après) : dans A+B, ce sont A et B.

    On dit qu'un opérateur est « unaire » s'il n'a qu'une seule opérande. Par exemple, ++ (comme dans A++) et sizeof (comme dans sizeof(Type)) sont des opérateurs unaires. De même, les opérateurs ayant deux opérandes sont dits « binaires » (rien à voir avec la base 2), et ceux en ayant trois « ternaires » (il n’en existe qu'un en C, l'opérateur ? : qui s’utilise ainsi : A?B:C et qui est simplement appelé opérateur ternaire).

    Certains opérateurs s’écrivent de façon identique (même symbole) mais se différencient par leur nombre d'opérandes (1 ou 2). Il y en a 4 en tout, en voici la liste :
    Symbole Opérateur unaire Opérateur binaire
    Usage Description Usage Description
    + +A signe : détermine le signe
    de l'expression A
    A + B addition ou soustraction : retourne
    la somme ou la différence de A et B
    -A A - B
    * *A indirection : retourne
    la valeur dont l'adresse est A
    A * B multiplication : retourne le produit
    de A et B
    & &A référence : retourne l'adresse
    de la variable A
    A & B ET binaire (opérateur bit-à-bit) :
    retourne l'entier tel que chaque bit
    est à 1 si et seulement si le bit
    correspondant de chaque opérande est à 1
    </span>
     
  • La priorité des opérateurs, c’est l’ordre dans lequel il faut les combiner. Par exemple, A + B * C donnera (A + (B * C)) et non ((A + B) * C) car la multiplication est prioritaire sur l’addition et sera donc effectuée avant.
    Pour contourner les priorités (dans notre exemple, si l’on veut effectuer l’addition avant la multiplication), on met des parenthèses : (A + B) * C.

    Le tableau ci-dessous est organisé de telle sorte qu’un opérateur prioritaire sur un autre sera placé plus haut que ce dernier ; deux opérateurs de même priorité seront sur la même ligne.
     
  • L’associativité des opérateurs, c'est le sens dans lequel il faut les lire : de gauche à droite (left to right, ltr) ou de droite à gauche (right to left, rtl). Par exemple, A + B + C + D donnera (((A + B) + C) + D) et non (A + (B + (C + D))) car l’addition est associative à gauche (ltr). Autre exemple : A = B = C (il est possible d'écrire ceci, bien que peu fréquent) est équivalent à (A = (B = C) et non à ((A = B) = C) car l’opérateur d’affectation a une associativité à droite.


Trêve de bavardages, voici enfin le tableau tant attendu !

Priorité et associativité des opérateurs en C
Catégories d'opérateurs Opérateurs Assoc.
appel de fonction, indiçage,
membre de structure,
membre de structure pointée
( ) [ ] . -> ltr →
opérateurs unaires (Type) sizeof --1 ++1 ! ~ - + * & rtl
multiplication, division, modulo * / % ltr →
addition, soustraction + - ltr →
opérateurs binaires de décalage << >> ltr →
opérateurs de comparaison < <= >= > ltr →
== !=
opérateurs binaires & ltr →
^
|
opérateurs logiques && ltr →
||
opérateur conditionnel ? : rtl
opérateurs d'affectation = += -= *= /= %= <<= >>= &= ^= |= rtl
opérateur virgule , 2 ltr →

Remarques :
1 Il existe en fait deux opérateurs d'incrémentation et deux de décrémentation : les opérateurs postfixés (i--, i++) mais aussi les opérateurs préfixés (--i, ++i) qui sont moins connus (voir le chapitre 2 de cette biographie pour plus de détails).
2 L'opérateur virgule (,) est très méconnu des débutants ; le chapitre 3 de cette biographie vous le présente.


Cette page très complète du Wikipedia anglais apporte plus de détail. Elle présente tous les opérateurs du C et du C++, en indiquant s’ils sont surchargeables et comment, ainsi que leur priorité et leur associativité.


Encore une fois, formulations à revoir : usage de « je », références à ma bio (qui, d’ailleurs, pourraient devenir des références à la FAQ), etc.

Je préfère mon tableau, mais tes explications (Taurre) sont peut-être plus claires (surtout sur l’associativité). Je pense aussi que c’est le moment ou jamais de parler des opérateurs qui s’écrivent pareil. Au final, on pourrait faire une soupe de nos deux rédactions ?
Par contre, on ne dit pas la même chose concernant ++ et -- préfixés / postfixés (ces derniers sont prioritaires et ont une associativité contraire). N’ayant jamais fait attention à ça, je te fais confiance. :-°
Et toi, tu as le 48ème opérateur du C11 en plus.


Concernant la liste des mots-clé, ça a déjà été fait très bien sur le forum C++, on ne pourrait pas se contenter de donner le lien (en précisant ceux qui sont présents en C) ?


La suite de l’entrée sur les champs de bits… dans mon prochain message ! (Je dois y aller.)
  • Partager sur Facebook
  • Partager sur Twitter
12 avril 2012 à 18:59:46

Citation : Maëlan

L’intérêt de tout ça ? L’opérateur préfixé est plus rapide que le postfixé. En effet, le postfixé impose de créer une variable temporaire pour renvoyer la valeur de la variable avant sa modification. Vous pouvez donc faire une micro-optimisation de vos programmes en remplaçant vos [in/dé]crémentations postfixées par des préfixées dès que possible, par exemple dans vos boucles : for(i=0; i<n; ++i).



Je ne sais pas si c'est réellement pertinent, vu que c'est une optimisation couramment intégrée (cf. documentation de gcc, clang, etc.). Une petite phrase entre balises sympathiques pourquoi pas, mais là quand j'ai lu ton billet (qui pourrait être sympathique, cela dit), c'est ce qui a pris le plus de place dans ma lecture.

Une autre petite chose hors sujet : une foire aux questions n'est-t-elle pas, justement, un endroit où l'on retrouve les questions les plus fréquemment posées ? Ma foi, je vois rarement des questions sur les opérateurs. Quitte à faire un ensemble de ressources complètes et références, autant ajouter le plus de choses possibles (c'est ce qui me gêne, j'ai l'impression que les nouveaux arrivants se noient dans les informations de la FAQ, qui ne sont pourtant pas bien importantes. Ou alors ne daignent-ils même pas la regarder ?). Ah, et une dernière chose : on ne pourrait pas mettre un peu plus en valeur la section sur le vidage de buffer ? Le nombre de sujets où l'on se retrouve avec cette question...
  • Partager sur Facebook
  • Partager sur Twitter
Staff désormais retraité.
12 avril 2012 à 21:12:05

Citation : Maëlan


Je préfère mon tableau, mais tes explications (Taurre) sont peut-être plus claires (surtout sur l’associativité). Je pense aussi que c’est le moment ou jamais de parler des opérateurs qui s’écrivent pareil. Au final, on pourrait faire une soupe de nos deux rédactions ?



La soupe me paraît une bonne idée ;)

Citation : Maëlan


Par contre, on ne dit pas la même chose concernant ++ et -- préfixés / postfixés (ces derniers sont prioritaires et ont une associativité contraire). N’ayant jamais fait attention à ça, je te fais confiance. :-°



En fait, la Norme spécifie que la priorité des opérateurs correspond à l'ordre des points du paragraphe 6.5:

Citation : Norme C11 6.5 § 2 note 85 p 76


The syntax specifies the precedence of operators in the evaluation of an expression, which is the same
as the order of the major subclauses of this subclause, highest precedence first.



Or, les opérateurs ++ et -- postfixés (6.5.2) sont présentés avant les opérateurs préfixés (6.5.3). Pour l'associativité, j'ai mis pas mal de temps avant de comprendre que cette dernière se déduisait de la syntaxe présentée au début de chaque groupe d'opérateurs (alors pourtant que c'était écrit noir sur blanc dans la Norme :-° ):

Citation : Norme C11 6.5 § 3 p 76


The grouping of operators and operands is indicated by the syntax.



Ainsi, si je prends le groupe des opérateurs postfixés (6.5.2):


Syntax
      postfix-expression:
               primary-expression
               postfix-expression [ expression ]
               postfix-expression ( argument-expression-listopt )
               postfix-expression . identifier
               postfix-expression -> identifier
               postfix-expression ++
               postfix-expression --
               ( type-name ) { initializer-list }
               ( type-name ) { initializer-list , }

      argument-expression-list:
               assignment-expression
               argument-expression-list , assignment-expression



on peut voir que la syntaxe est récursive car une postfix-expression est définie à partir d'elle-même. Cette récursion se situant à gauche, l'associativité est de gauche à droite.

Citation : Maëlan


Concernant la liste des mots-clé, ça a déjà été fait très bien sur le forum C++, on ne pourrait pas se contenter de donner le lien (en précisant ceux qui sont présents en C)?



Le problème c'est que la liste comprends les mots-clés du C++ et que les exemples sont en C++. Pour les débutants qui souhaitent se renseigner sur le C, c'est pas top (en plus la liste n'est pas à jour par rapport au C11 :-° ).


Citation : lucas-84


Une autre petite chose hors sujet : une foire aux questions n'est-t-elle pas, justement, un endroit où l'on retrouve les questions les plus fréquemment posées ? Ma foi, je vois rarement des questions sur les opérateurs. Quitte à faire un ensemble de ressources complètes et références, autant ajouter le plus de choses possibles.



C'est moi où tu te contredit dans ce paragraphe? :o

Citation : lucas-84


(c'est ce qui me gêne, j'ai l'impression que les nouveaux arrivants se noient dans les informations de la FAQ, qui ne sont pourtant pas bien importantes. Ou alors ne daignent-ils même pas la regarder ?).



À mon avis, le problème vient plutôt du fait que les débutants ne la lisent pas. Ce n'est pas forcément qu'ils sont paresseux, mais plutôt qu'ils n'ont pas le réflexe de chercher l'information par eux-mêmes avant de poster. Mais bon, de ce côté on ne peut rien faire si ce n'est les renvoyer à la FAQ pour qu'ils y pensent la prochaine fois.

Citation : lucas-84


Ah, et une dernière chose : on ne pourrait pas mettre un peu plus en valeur la section sur le vidage de buffer ? Le nombre de sujets où l'on se retrouve avec cette question...



Entièrement d'accord, cette question revient extrêmement souvent. En fait, je me demande s'il ne serait pas intéressant de crée une entrée expliquant une bonne fois pour toute ce qu'est un tampon, les différents types existant et comment les modifier via les fonctions setbuf et setvbuf.
  • Partager sur Facebook
  • Partager sur Twitter
15 avril 2012 à 14:56:11

J’ai posté l’entrée sur ++i.


Voici la suite de la rédaction sur les champs de bits (désolé pour le délai).

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−


[8][13] Que signifient les deux-points « : » à coté d’un membre de structure ?


struct Exemple {
    int  a  :2,
         b  :3,
         c  :1,
         d  :1,
         e  :1;
    char f;
};


Peut-être avez-vous déjà rencontré des codes qui ressemblent à ça.

Les deux-points à côté d’un membre d'une structure (ou d’une union) signifient que ce dernier est un « champ de bits » (“bit-field” en anglais). Cela permet de spécifier, pour un champ de type int , unsigned int ou _Bool (C99), le nombre de bits que l’on souhaite utiliser (ce nombre ne doit bien évidemment pas excéder le nombre de bits composant le type).

Cette technique peut-être très utile afin de gagner de la place quand vous savez que vous n’utiliserez jamais toute la capacité du type int, unsigned int ou _Bool : dans un seul int « normal », vous faites tenir plusieurs membres de ce type.

Pour faire cela, il vous faut être sûr des valeurs que pourront prendre vos membres. Cette technique restreint en effet les valeurs disponibles. Dans un champ de m bits, vous pouvez écrire 2m valeurs différentes (de 0 à 2m-1 dans le cas d’un nombre non signé).
Ainsi, vous pouvez par exemple faire des booléens ou des flags qui ne prennent que les valeurs 0 ou 1 et n’ont donc besoin que d’un bit, tout ça sans utiliser d’opérations bit-à-bit (c’est le compilateur qui s’en charge pour vous). Par exemple :
/** Structure contenant 8 booléens **/
struct Booleans {
    _Bool  b1  :1,    /* (le type _Bool est apparu en C99) */
           b2  :1,
           b3  :1,    /* Ces membres n’utilisent qu’un bit et ne peuvent */
           b4  :1,    /*  donc valoir que 0 ou 1. */
           b5  :1,
           b6  :1,
           b7  :1,
           b8  :1;
};

/** Exemple d’utilisation **/
struct Booleans  myBools =  {1, 1, 0, 1, 0, 1, 1, 0};
myBools.b3 =  1;
myBools.b7 =  myBools.b2;
if(myBools.b1)    { … }
          /* ⇒ On s’en sert comme des membres normaux. */

/*/ Taille totale de la structure **/
printf("sizeof Booleans: %u\n", sizeof(struct Booleans) );
    // ↑ probablement 1
printf("sizeof _Bool[8]: %u\n", sizeof(_Bool[8]) );
    // ↑ probalement 8
          /* ⇒ Si on n’avait pas eu recours aux champs de bits pour stocker
            8 booléens (un tableau par exemple), on aurait utilisé plus de
            place. */


Il est possible de spécifier un champ de bit sans nom, par exemple :
struct StructureAvecBourrage {
    int  a  :3,
            :2,    /* champ anonyme de 2 bits */
         b  :3;
};

Les champs anonymes ne peuvent évidemment pas être utilisés. Ils permettent de régler l’alignement des autres champs en laissant de l’espace précis entre eux.
Dernière possibilité, on peut spécifier un champ anonyme de longueur 0. Cela indique au compilateur de remplir le rste de l’unité de stockage et de faire commencer le champ suivant à la prochaine unité. Par exemple :
struct StructureAvecAlignement {
    int  a  :3,
         b  :2,
            :0,    /* champ anonyme s’étendant jusqu’à la fin du « int » englobant */
         c  :3;    /* champ commençant au « int » suivant */
};



Point norme :
  • Vous ne pouvez faire ceci que pour des membres de structure ou union.
  • Cela se limite aux types entiers int, signed int, unsigned int ou _Bool (depuis C99). Les champs de bits sont du même type que leur unité « englobante ». Dans le cas de int, le fait que les champs de bits soient signés ou non est au choix de l’implémentation.
  • La norme n’impose pas d’ordre pour les champs de bits (des bits de poids forts vers les bits de poids faibles ou le contraire) : c’est à l’implémentation de trancher.
    Attention donc si vous tentez de gérer un ensemble de flags avec une union de cette façon :
    typedef union {
        uint8_t flags;        /* ensemble de 8 flags */
        struct {              /* accès individuel aux divers flags */
            _Bool  hasBuffer    :1,
                   usesTruck    :1,
                   hasDrunk     :1,
                   knowsTruth   :1,
                   hasReadDraft :1,
                   hasSeenWolf  :1,
                   canFly       :1,
                   isLastMohic  :1;
        } flag;
    } Flags;
    

    Pour les mêmes valeurs de flags, l’entier les englobant tous (le membre flags) pourra avoir une valeur différente d’un système à l’autre, selon l’ordre dans lequel sont rangés les champs de bits.

  • S’il ne reste plus assez de place dans l’unité de stockage pour le prochain champ de bits, celui-ci peut être décalé pour commencer avec l’unité suivante (donc ajout de bits inutilisés), ou à cheval sur les deux unités (ce qui diminue les performances) ; à l’implémentation de choisir.

/−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−




La fin est plutôt technique (à partir des champs anonymes en fait), mais j’ai voulu faire quelque chose de suffisamment complet pour que tout le monde s’y retrouve.

Qu’en pensez-vous ?


Édit : Correction d’une erreur dans un code.
  • Partager sur Facebook
  • Partager sur Twitter
15 avril 2012 à 15:49:56

Cela m'a l'air très bien :)
Au passage, j'ai appris pas mal de choses à leurs sujets, merci beaucoup ;)

Sinon, j'ai juste une petite remarque à faire:

printf("sizeof Booleans: %u\n", sizeof(struct Booleans) );
printf("sizeof _Bool[8]: %u\n", sizeof(struct _Bool[8]) );


pour afficher la valeur d'une expression de type size_t, il y a le format "%zu" depuis le C99.

Aussi, j'ai une question qui me taraude en lisant ceci: imaginons que l'on ait un champ de bits de type unsigned int composé de 3 bits. Ce dernier peut contenir des valeurs de 0 à 7, jusque là tout va bien. Mais, que se passe-t-il si je dépasse sa capacité? J'ai un comportement déterminé comme pour tout type non signé (Norme C11 6.3.1.3 § 2 p 51) ou bien c'est un comportement indéterminé?
  • Partager sur Facebook
  • Partager sur Twitter
15 avril 2012 à 17:09:57

Citation : Taurre

Au passage, j'ai appris pas mal de choses à leurs sujets, merci beaucoup ;)


Ah. Bah, de rien. :)

Pour "%zu", je prend note mais je préfère laisser tel quel pour ne pas compliquer. Déjà que j’ai hésité à utiliser "%u" (un débutant complet ne connaissant souvent que "%d"…).

Au fait, j’ai fait une grosse erreur dans le bout de code que tu as cité. Apparemment, elle t’a échappé aussi. :-°

Citation : Taurre

Aussi, j'ai une question qui me taraude en lisant ceci: imaginons que l'on ait un champ de bits de type unsigned int composé de 3 bits. Ce dernier peut contenir des valeurs de 0 à 7, jusque là tout va bien. Mais, que se passe-t-il si je dépasse sa capacité? J'ai un comportement déterminé comme pour tout type non signé (Norme C11 6.3.1.3 § 2 p 51) ou bien c'est un comportement indéterminé?



J’avais aussi quelques questions de cet acabit en rédigeant : en l’absence de mention contraire dans le draft (n1256 pour moi ^^ ), les champs de bits se comportent-ils comme des variables du type dont ils sont dérivés ? Cette question englobe plusieurs points :
  • la représentation des entiers signés (il est dit explicitement que les champs de bits non signés utilisent la « notation binaire pure ») : les entiers signés « normaux » utilisent le complément à 2, le complément à 1 ou le « signe et magnitude » (implementation-defined) ;
  • le dépassement de capacité : pour les entiers non signés normaux, on fait simplement un modulo, pour les signés c’est selon l’implémentation il me semble ;


Je pense que la réponse est « oui », mais ce n’est qu’une supposition. Il faudrait vérifier en relisant la norme plus précisément.


Bon ben, je m’attaque au gloubi-boulga sur les opérateurs, puisque tu es d’accord.
  • Partager sur Facebook
  • Partager sur Twitter
16 avril 2012 à 21:24:33

Citation : Maëlan


Au fait, j’ai fait une grosse erreur dans le bout de code que tu as cité. Apparemment, elle t’a échappé aussi. :-°



En effet, je l'ai complètement loupée :-°

Sinon, pour la question sur les champs de bits, je viens de me rappeler que j'avais déjà poser la question sur fr.comp.lang.c (le sujet est ici), mais je n'avais à l'époque pas tout suivit. Maintenant que je le relis, cela me paraît limpide. Donc, visiblement, le comportement d'un champ de bits de type unsigned int est garantit par le standard de part ce passage:

Citation : Norme C11 6.7.2.1 § 10 p 114


A bit-field is interpreted as having a signed or unsigned integer type consisting of the
specified number of bits.



et celui-ci:

Citation : Norme C11 6.3.1.3 § 2 p 51


Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or
subtracting one more than the maximum value that can be represented in the new type
until the value is in the range of the new type.



mais, il subsiste visiblement quelques soucis d'implémentation du côté des compilateurs (avec GCC cela a l'air d'aller). Moralité: c'est théoriquement portable mais dans la pratique, pas pour l'instant :-°

Sinon, je me demande s'il ne serait pas bon de supprimer le passage concernant le C++ dans l'entrée traitant des opérateurs de post/pré incrémentation/décrémentation. En effet, au final c'est la FAQ du forum C, il n'y a donc pas lieu de parler du C++.
  • Partager sur Facebook
  • Partager sur Twitter
16 avril 2012 à 22:10:48

Merci pour les précisions. :)

Citation : Taurre

Sinon, je me demande s'il ne serait pas bon de supprimer le passage concernant le C++ dans l'entrée traitant des opérateurs de post/pré incrémentation/décrémentation. En effet, au final c'est la FAQ du forum C, il n'y a donc pas lieu de parler du C++.



Je ne sais pas. J’avais trouvé que la remarque était intéressante quand même, et que des C++istes pourraient peut-être lire quand même cette FAQ, mais c’est vrai que ça alourdit l’entrée. Peut-être avec un <secret> ?

Sinon, j’ai rajouté l’entrée sur la conversion entier ↔ caractère. Un modérateur peut-il se charger de mettre à jour l’index ?



Édit : (vu que je ne peux pas reposter…)

Et si je rajoutais cette phrase à la fin de l’entrée sur les champs de bits ?

Citation

Conclusion : les champs de bits sont sympathiques et parfois bien pratiques, mais si vous visez la portabilité, mieux vaut les oublier. :( Si vous avez vraiment besoin de condenser vos données, utilisez manuellement les opérations binaires (&, |, ^).



J’ai rédigé le mélange sur les opérateurs.

[?][?] Quels sont les opérateurs du langage C ? Comment se combinent-ils ?


Un opérateur peut être défini comme le symbole d'une opération mathématique ou logique. Par exemple, l'opérateur + est le symbole de l’opération mathématique d’addition et l’opérateur && celui de l’opération logique de conjonction. Toutefois, le langage C ne se limite pas à ses deux types d’opérations. En effet, sizeof et & par exemple sont tous deux des opérateurs et permettent respectivement d’obtenir la taille et l’adresse de leurs opérandes.


Mais, le C compte combien d’opérateurs alors ? o_O



Hé bien, le langage C intègre de nombreux opérateurs : 47 en tout (48 depuis C11) ! Certains sont plus fréquents que d'autres, mais tous ont leur utilité. Le tutoriel officiel de M@teo21, comme de nombreux cours pour débutants, ne les présente pas tous car ils ne sont pas utiles pour commencer.

Ci-dessous, un grand tableau vous les représente tous. Le but n’est pas de tous les expliquer ici, mais d’en offrir une vue d’ensemble et de rappeler leur caractéristiques : arité, priorité et associativité.

Rhôô, les gros mots ! Ça veut dire quoi tout ça ?

On va tout vous expliquer :
  • Chaque opérateur a une ou des opérandes. Les opérandes, ce sont les expressions sur lesquelles on effectue l'opération. Par exemple, l'opérateur + nécessite deux opérandes (une avant et une après) : dans A+B, ce sont A et B.

    On appelle arité le nombre d’opérandes d’un opérateur. On dit qu'un opérateur est « unaire » s'il n'a qu'une seule opérande. Par exemple, ++ (comme dans A++) et sizeof (comme dans sizeof A) sont des opérateurs unaires. De même, les opérateurs ayant deux opérandes sont dits « binaires » (rien à voir avec la base 2), et ceux en ayant trois « ternaires » (il n’en existe qu'un en C, l'opérateur ? : qui s’utilise ainsi : A?B:C et qui est simplement appelé opérateur ternaire).

    Certains opérateurs s’écrivent de façon identique (même symbole) mais se différencient par leur arité (1 ou 2). Il y en a 4 en tout, en voici la liste :
    Symbole Opérateur unaire Opérateur binaire
    Usage Description Usage Description
    + +A signe : détermine le signe de l’expression A A + B addition ou soustraction : retourne la somme ou la différence de A et B
    -A A - B
    * *A indirection : retourne la valeur pointée par A A * B multiplication : retourne le produit de A et B
    & &A référence : retourne l’adresse de la variable A A & B ET binaire (opérateur bit-à-bit) : retourne l’entier tel que chaque bit
    est à 1 si et seulement si le bit correspondant de chaque opérande est à 1
    </span>
  • La priorité des opérateurs permet de déterminer l’ordre dans lequel les opérations composant une expression seront effectuées. Prenons un exemple simple, l’expression 5 + 4 * 3. Il y a deux manières de calculer sa valeur :

    — effectuer l'addition puis la multiplication : ( (5 + 4) * 3 ) ;
    — effectuer la multiplication puis l'addition : ( 5 + (4 * 3) ).

    L’ordre des opérations est extrêmement important puisqu’il va déterminer la valeur de l’expression. Dans le premier cas on obtiendra 27, et dans l’autre on obtiendra 17. Grâce à la priorité, nous savons que la multiplication doit s’effectuer avant l’addition (la multiplication est prioritaire sur l’addition) et que la valeur de l’expression sera donc de 17.
    Pour contourner les priorités (dans notre exemple, si l’on veut effectuer l’addition avant la multiplication), on met des parenthèses : (5 + 4) * 3 nous donnera bien 27.

    Le tableau ci-dessous est organisé de telle sorte qu’un opérateur prioritaire sur un autre sera placé plus haut que ce dernier ; deux opérateurs de même priorité seront sur la même ligne.

  • Malheureusement, la priorité des opérateurs ne suffit pas à lever toute ambigüité. En effet, que faire dans les cas où les opérateurs ont la même priorité ? Par exemple, dans l’expression 5 + 4 - 3, qui de l’addition ou de la soustraction doit être réalisée la première (sachant qu’elles ont la même priorité) ?
    Ben on s'en fiche non ? Puisque le résultat sera le même ?

    Vous avez raison, l’ordre des opérations n’a ici aucune influence sur la valeur de l’expression. Cependant, il est des cas où cela peut poser problème. Prenez l’expression A = B = C, comment procède-t-on ?
    — Comme ceci : (A = (B = C))
    — … ou comme cela : ((A = B) = C) (ce qui est invalide en C) ?
    C'est ici que l’associativité intervient, en déterminant un sens de lecture. Pour l’opérateur =, l’associativité est de droite à gauche, donc on lit l’expression de la droite vers la gauche ; on rentre donc dans le premier cas. L'expression pourrait être reformulée comme suit : A = (B = C).

    Évidemment, il y a deux associativités possibles : de la gauche vers la droite (left to right, ltr) ou de la droite vers la gauche (right to left, rtl).


Trêve de bavardages, voici enfin le tableau tant attendu !

Les opérateurs du langage C : priorité, associativité et arité
Catégories d'opérateurs Opérateurs Assoc. Arité
appel de fonction, indiçage,
membre de structure,
membre de structure pointée
( ) [ ] . -> -- 1 ++ 1 ltr → 2
(1 pour ++
et --)
opérateurs unaires (Type) sizeof _Alignof 2 * & ! ~ - + -- 1 ++ 1 rtl 1
multiplication, division, modulo * / % ltr → 2
addition, soustraction + -
décalage binaire << >>
comparaison < <= >= >
== !=
opérations binaires &
^
|
opérations logiques &&
||
opérateur conditionnel ? : rtl 3
affectation = += -= *= /= %= <<= >>= &= ^= |= 2
virgule , 3 ltr →

Remarques :
1 Il existe en fait deux opérateurs d’incrémentation et deux de décrémentation : les opérateurs postfixés (i--, i++) mais aussi les opérateurs préfixés (--i, ++i) qui sont moins connus des débutants (voir l’entrée [8][12] de cette FAQ). Les postfixés sont prioritaires sur les préfixés et ont une associativité contraire.
2 L’opérateur _Alignof a été introduit en C11.
3 L’opérateur virgule (,) est très méconnu des débutants.


Cette page très complète du Wikipedia anglais apporte plus de détail. Elle présente tous les opérateurs du C et du C++, en indiquant s’ils sont surchargeables et comment, ainsi que leur priorité et leur associativité.
  • Partager sur Facebook
  • Partager sur Twitter
17 avril 2012 à 22:10:01

Citation : Maëlan


Conclusion : les champs de bits sont sympathiques et parfois bien pratiques, mais si vous visez la portabilité, mieux vaut les oublier. :( Si vous avez vraiment besoin de condenser vos données, utilisez manuellement les opérations binaires (&, |, ^).



C'est peut-être un peu radicale de dire que leurs utilisations n'est pas portable (si l'on utilise un champs de bits de type unsigned int, on est certains de pouvoir stocker les valeurs de 0 à 2n-1). Mais, c'est vrai qu'il est sans doute mieux de leur proposer de réaliser eux-mêmes les opérations bits à bits.


Citation : Maëlan


Je ne sais pas. J’avais trouvé que la remarque était intéressante quand même, et que des C++istes pourraient peut-être lire quand même cette FAQ, mais c’est vrai que ça alourdit l’entrée. Peut-être avec un <secret>?



Moui, je reste quand même d'avis que cela n'a pas sa place dans la FAQ C, en tous les cas pas en <attention>.

Sinon, pour le pasage sur les opérateurs, cela m'a l'air pas mal :)
  • Partager sur Facebook
  • Partager sur Twitter