Programmation C/Opérateurs
Les opérateurs du C.
Les opérateurs du C permettent de former des expressions, expressions qui diront quoi faire à votre programme. On peut voir un programme C comme étant composé de trois catégories d'instructions :
Les déclarations de variables ont en partie été décrites dans les chapitres précédents. Les expressions seront en partie décrites dans celui-ci (les opérateurs liés aux pointeurs, tableaux, structures et fonctions seront décrits dans des chapitres dédiés), et les instructions seront décrites au cours des chapitres suivants.
Les expressions en C sont en fait très génériques, elles peuvent inclure des opérations arithmétiques classiques (addition, soustraction, division, modulo, ...), des expressions booléennes (OU logique, ET logique, OU exclusif, ...), des comparaisons (égalité, inégalité, différence, ...) et même des affectations (copie, auto-incrément, ...). Toutes ces opérations peuvent s'effectuer en une seule expression, il suffit d'appliquer les bons opérateurs sur les bons opérandes.
Les opérateurs binaires et ternaires utilisent une notation infixe (l'opérateur se trouve entre les 2 ou 3 opérandes). Les opérateurs unaires s'écrivent de manière préfixé (avant l'opérande), à l'exception des opérateurs codice_1 et codice_2 qui peuvent s'écrire de manière préfixée ou suffixée (avec une différence subtile, décrite ci-après).
La priorité (quel opérateur est appliqué avant, en l'absence de parenthèses explicites) et l'associativité (dans quel ordre sont traités les arguments des opérateurs ayant la même priorité) sont résumées dans la table suivante (par ordre décroissant de priorité - les opérateurs décrits dans un autre chapitre ont un lien dédié) :
Post/pré incrémentation/décrémentation.
C'est un concept quelque peu sibyllin du C. Incrémenter ou décrémenter de un est une opération extrêmement courante. Écrire à chaque fois 'codice_3' peut-être très pénible à la longue. Le langage C a donc introduit des opérateurs raccourcis pour décrémenter ou incrémenter n'importe quel type atomique (gérable directement par le processeur : c'est à dire pas par un tableau, ni une structure ou une union). Il s'agit des opérateurs 'codice_1' et 'codice_2', qui peuvent être utilisés de manière préfixée ou suffixée (avant ou après la variable).
Utilisé de manière préfixée, l'opérateur incrémente/décrémente la variable, puis retourne la valeur de celle-ci. En fait, les expressions codice_6 et codice_7 sont équivalentes respectivement aux expressions codice_8 et codice_9.
Par contre, utilisé de manière suffixée, l'opérateur retourne la valeur originale avant de modifier la valeur de la variable.
int i = 0, j;
j = i++; /* j vaut 0 et i vaut 1 */
j = --i; /* j vaut 0 et i vaut 0 */
Il est important de noter que le langage ne fait que garantir qu'une variable post-incrémentée ou post-décrémentée acquiert sa nouvelle valeur entre le résultat de l'opérateur et le prochain point de séquencement atteint (généralement la fin d'une instruction). Mais on ne sait pas vraiment quand entre les deux. Ainsi, si l'objet sur lequel s'applique un tel opérateur apparaît plusieurs fois avant un point de séquencement, alors le résultat est imprévisible et son comportement peut changer simplement en changeant les options d'un même compilateur :
/* Le code qui suit est imprévisible */
int i = 0;
i = i++;
/* Et celui-là d'après vous ? */
int i = 0;
i = ++i;
Contrairement à ce à quoi vous pouviez vous attendre, le bout de code du deuxième bloc de code n'est pas mieux défini que celui du premier bloc de code. La valeur de i y est modifiée une première fois lors de son incrémentation, puis une deuxième fois par l'opérateur d'affectation codice_10. Or, un autre moyen de générer un comportement indéfini est de modifier la valeur d'une variable plusieurs fois entre deux points de séquencement.
Promotion entière.
La promotion entière, à ne pas confondre avec la conversion automatique de type, fait que tous les types plus petits ou égaux à codice_11 (codice_12, codice_13, champs de bits, type énuméré) sont convertis (promus) en codice_11 ou codice_15 avant toute opération. Ainsi, dans l'exemple suivant, codice_16 est calculé avec des codice_11 et le résultat est de type codice_11 :
short sum(short a, short b) {
 return a + b; /* équivaut à : return (int)a + (int)b; */
Le compilateur peut émettre un avertissement du fait que le résultat codice_11 est converti en codice_13 pour être retourné, et cette conversion peut causer une perte de précision (par exemple si codice_11 a une largeur de 32 bits et codice_13 de 16 bits).
La promotion se fait vers codice_15 lorsqu'un codice_11 ne peut pas représenter la valeur promue.
Conversion automatique.
La conversion est un mécanisme qui permet de convertir implicitement les nombres dans le format le plus grand utilisé dans l'expression.
L'exemple classique est le mélange des nombres réels avec des nombres entiers. Par exemple l'expression 'codice_25' est de type double et vaudra effectivement deux tiers (0,666666...). L'expression 'codice_26' calculera les deux tiers de la variable 'codice_27', et arrondira automatiquement à l'entier par défaut, les calculs ne faisant intervenir que des instructions sur les entiers (en supposant que 'codice_27' soit un entier). À noter que l'expression 'codice_29' vaudra... toujours zéro !
Plus subtile, l'expression 'codice_30', en supposant toujours que "a" soit un entier, effectuera une multiplication entière entre 2 et a, puis promouvra le résultat en réel (codice_31) puis effectuera la division réelle avec trois, pour obtenir un résultat réel lui-aussi.
Enfin un cas où les promotions peuvent surprendre, c'est lorsqu'on mélange des entiers signés et non-signés, plus particulièrement dans les comparaisons. Considérez le code suivant :
/* Ce code contient un effet de bord sibyllin */
unsigned long a = 23;
signed char b = -23;
printf( "a %c b\n", a < b ? '<' : (a == b ? '=' : '>') );
De toute évidence codice_27 est supérieur à codice_33 dans cet exemple. Et pourtant, si ce code est exécuté sur certaines architectures, il affichera codice_34, justement à cause de cette conversion.
Tout d'abord dans la comparaison codice_34, le type de codice_27 est « le plus grand », donc codice_33 est promu en codice_38. C'est en fait ça le problème : -23 est une valeur négative, et la conversion d'une valeur négative en un type non signé se fait modulo la valeur maximale représentable par le type non signé + 1. Si la valeur maximale pour le type codice_38 est 232-1, alors -23 est converti modulo 232, et devient 4294967273 (soit 232-23). Dans ce cas là effectivement 23 < 4294967273, d'où ce résultat surprenant.
À noter qu'on aurait eu le même problème si le type de codice_33 était codice_41. En fait les types signés sont promus en non-signés, s'il y a au moins une variable non-signée dans l'expression, et que le type non-signé est "plus grand" que le type signé.
Dans ce cas présent, il faut effectuer soi-même le transtypage :
printf( "a %c b\n", (long) a < b ? '<' : ((long)a == b ? '=' : '>') );
La norme définit précisément les conversions arithmétiques et les promotions entières, se référer à elle pour les détails.
Évaluation des expressions booléennes.
Le C ne possède pas de type booléen dédié. Dans ce langage, n'importe quelle valeur différente de zéro est considérée vraie, zéro étant considéré comme faux. Ce qui veut dire que n'importe quelle expression peut être utilisée à l'intérieur des tests (entier, réels, pointeurs, tableaux, etc.). Cela peut conduire à des expressions pas toujours très claires, comme :
int a;
a = une_fonction();
if (a)
 /* ... */
Ce type d'écriture simplifiée a ses adeptes, mais elle peut aussi s'écrire de la manière suivante:
int a;
a = une_fonction();
if (a != 0)
 /* ... */
Cette seconde écriture, sémantiquement équivalente à la première, rend explicite le test qui est effectué (on compare la valeur de la variable codice_27 à zéro).
Enfin si une_fonction est naturellement un booléen comme is_empty on écrira :
if (is_empty())
 /* ... */
Par ailleurs, cette absence de type booléen rend aussi valide le code suivant:
int a = 0;
int b = 2;
if (a = b)
 /* Le code qui suit sera toujours exécuté car b est différent de 0 ... */
Dans cet exemple, il y a un seul caractère codice_10 entre codice_27 et codice_33, donc on ne teste pas si codice_27 est égal à codice_33, mais on affecte la valeur de la variable codice_33 à la variable codice_27. Le résultat de l'expression étant 2, elle est donc toujours considérée comme vraie (elle serait toujours considérée comme fausse si b avait la valeur 0). Ce genre de raccourci est en général à éviter. En effet, il est facile de faire une faute de frappe et d'oublier de taper un caractère codice_10. Comme le code résultant est toujours valide au sens du C, cette erreur peut ne pas être vue immédiatement. Pour aider les développeurs à détecter ce genre d'erreurs, de nombreux compilateurs émettent un avertissement quand un tel code leur est présenté. Une manière de faire taire ces avertissements, "une fois qu'on s'est assuré qu'il ne s'agit pas d'une erreur", est de mettre entre parenthèses l'affectation:
/* On veut faire une affectation ici. */
/* doublement des parenthèses pour supprimer l'avertissement du compilateur*/
if ((a = b))
 /* ... */
Comme un développeur devant maintenir un programme contenant un test codice_51 se demandera si le développeur précédent n'a pas fait une faute de frappe, il est préférable d'éviter autant que possible ce genre de situations et, s'il est nécessaire (ce qui a très peu de chances d'arriver), de la commenter. En effet, même un code codice_52 non commenté doit être lu avec attention, car le développeur précédent peut avoir ajouté les parenthèses juste pour faire taire le compilateur, sans se rendre compte d'une erreur. Dans tous les cas, la manière la plus sûre est de décomposer ainsi:
a = b;
if (a != 0)
 /* ... */
Une autre technique classique, lorsqu'une comparaison fait intervenir une constante, est de mettre la constante à gauche. De cette manière, si la comparaison se transforme par mégarde en affectation, cela provoquera une erreur à la compilation :
if (0 == b)
 /* Une instruction "0 = b" ne passerait pas */
Les opérateurs logiques de comparaisons (codice_53 et codice_54, similaires sémantiquement à leur équivalent binaire codice_55 et codice_56) évaluent leurs opérandes en circuit court. Dans le cas du ET logique (codice_53), si l'opérande gauche s'évalue à faux, on sait déjà que le résultat du ET sera faux et donc ce n'est pas la peine d'évaluer l'opérande droite. De la même manière si l'opérande gauche d'un OU logique (codice_54) est évalué à vrai, le résultat sera aussi vrai et donc l'évaluation de l'opérande droite est inutile. Ceci permet d'écrire des expressions de ce genre, sans générer d'exception de l'unité arithmétique du processeur :
if (z != 0 && a / z < 10)
 printf("Tout va bien\n");
Remarquez aussi que les opérateurs logiques contrairement à ceux bit-à-bit sont non commutatifs. Dans la littérature des années 70 et 80 on les nomme parfois comme opérateurs de McCarthy.
Et voila !
Dans l'exemple ci-dessous, on veut affecter à la variable "len" la longueur de la chaîne pointée par la variable "string", mais en vérifiant d'abord que celle-ci ne contient pas le pointeur NULL (dont la valeur est 0), auquel cas "len" prendra la valeur 0.
if (string)
 len = strlen(string);
else
 len = 0;
On peut réduire cette instruction par :
len = string ? strlen(string) : 0;
C'est-à-dire que "len" reçoit si "string" est non-NULL la longueur de "string", sinon 0