Section courante

A propos

Section administrative du site

Style

Le style est la vision proposé par Aztec C pour communiquer les caractères spéciaux de C et les pratiques de programmation pour lesquelles il est le mieux adapté. Ainsi, il donne du sens aux particularités de la syntaxe C, afin d'éviter les erreurs qui, autrement, ne disparaîtront qu'avec l'expérience.

Qu'est-ce qu'il y a pour moi ?

Voici les avantages à récolter en suivant les méthodes présentées ici :

Le but du programmeur responsable est d'écrire du code simple, ce qui rend ses programmes plus accessibles aux autres. Le style vise à souligner quelles habitudes de programmation propices à la réussite des programmes C et lesquelles sont particulièrement susceptibles de causer des problèmes. Les nombreux avantages de C peuvent être abusés. Puisque C est un langage de programmation laconique et subtil, il est facile d'écrire du code n'étant pas clair. Ceci est contraire à la philosophie de C et d'autres langages de programmation structurés, selon lesquels la structure d'un programme doit être clairement définie et facilement reconnaissable.

Rester simple

Il existe plusieurs éléments de style de programmation rendant C plus facile à utiliser. L'un d'eux est la simplicité. La simplicité signifie rester simple. Vous devriez être en mesure de voir exactement ce que fera votre code, de sorte que lorsque ce n'est pas le cas, vous puissiez comprendre pourquoi. Un peu de suspicion peut également être utile. Les zones à problèmes particulières sont des points à vérifier quand le code semble correct mais ne fonctionne pas. Une petite omission peut provoquer de nombreuses erreurs.

Apprendre les idiomes C

Le langage de programmation C devient plus précieux et plus flexible avec le temps. Évidemment, les problèmes élémentaires de syntaxe disparaîtront. Mais plus important encore, C peut être décrit comme «idiomatique». Ce terme signifie que certaines expressions font partie d'un vocabulaire standard utilisé à plusieurs reprises. Par exemple :

while((c = getchar()) != EOF)

est facilement reconnu et écrit par n'importe quel programmeur C. Ceci est souvent utilisé comme le début d'une boucle obtenant un caractère à la fois à partir d'une source d'entrée. De plus, l'ensemble intérieur de parenthèses, souvent omis par un nouveau programmeur C, est rarement oublié après que cette construction a été utilisée plusieurs fois.

Soyez flexible dans l'utilisation de la bibliothèque

La bibliothèque standard contient un choix de fonctions pour effectuer la même tâche. Certaines combinaisons présentent des avantages, de sorte qu'elles sont utilisées en routine. Par exemple, la bibliothèque standard contient une fonction, scanf, pouvant être utilisée pour saisir des données d'un format donné. Dans cet exemple, la fonction analyse l'entrée pour un nombre à virgule flottante :

scanf("%f", &flt_num);

Il y a plusieurs inconvénients à cette fonction. Un défaut important est qu'il nécessite beaucoup de code. En outre, la manière dont cette fonction gère certaines chaînes de caractères d'entrée n'est pas toujours claire. On pourrait passer beaucoup de temps à rechercher le comportement de cette fonction. Cependant, l'équivalent de ce qui précède se fait par ce qui suit :

flt_num = atof(gets(inp_buf));

Celle-ci nécessite beaucoup moins de code et est un peu plus simple. La fonction gets met une ligne d'entrée dans le tampon, Le inp_buf et atof la convertit en une valeur à virgule flottante. Il n'y a aucun doute sur ce que la fonction d'entrée recherche et ce qu'elle doit trouver. De plus, la deuxième méthode d'obtention des données offre une plus grande flexibilité. Par exemple, si l'utilisateur du programme peut entrer une commande spéciale ("e" pour quitter) ou une valeur à virgule flottante, ce qui suit est possible :

gets(inp_buf);
if(inp_buf[0] == 'e') exit(0);
flt_num = atof(inp_buf);

Ici, le premier caractère de l'entrée est vérifié pour un "e", avant que l'entrée soit convertie en un nombre à virgule flottante. La longueur relative de la description de bibliothèque de la fonction scanf est une indication des problèmes pouvant survenir avec cette fonction et les fonctions associées.

Écrire du code lisible

La lisibilité peut être grandement améliorée en adhérant à ce que le bon sens dicte. Par exemple, la plupart des lignes peuvent facilement contenir une seule instruction. Bien que le compilateur accepte des instructions étant regroupées sans distinction, la logique derrière le code sera perdue. Par conséquent, il est logique de ne pas écrire plus d'une instruction par ligne. Dans le même ordre d'idées, il est souhaitable d'être généreux avec les espaces. Un espace vide doit séparer les opérateurs arithmétiques et d'affectation des autres symboles, tels que les noms de variables. Et lorsque les parenthèses sont imbriquées, les diviser par des espaces n'est pas trop de prudence. Par exemple :

if((fp=fopen("filename","r")==NULL))

n'est pas la même chose que

if ( (fp = fopen("filename", "r")) == NULL)

La première ligne contient une parenthèse mal placée changeant complètement le sens de l'énoncé. (Un fichier est ouvert mais le pointeur de fichier sera nul.) Si la déclaration était développée, comme dans la deuxième ligne, le problème pourrait être facilement repéré, sinon évité complètement.

Utilisez des expressions logiques simples

Les conditions sont susceptibles de devenir de longues expressions. Ils doivent être courts. Les conditions s'étendant jusqu'à la ligne suivante doivent être divisées afin que la logique de l'instruction puisse être visualisée en un coup d'oeil. Une autre solution pourrait être de reconsidérer la logique du code lui-même.

Apprenez les règles d'évaluation des expressions

Gardez à l'esprit que l'évaluation d'une expression dépend de l'ordre dans lequel les opérateurs sont évalués. Ceci est déterminé à partir de leur priorité relative. La règle en Aztec C est qu'un booléen ne sera évalué que jusqu'à ce que la valeur de l'expression puisse être déterminée. En général, si une expression dépend de l'ordre dans lequel elle est évaluée, les résultats peuvent être douteux. Bien que le résultat puisse être strictement défini, vous devez être certain de connaître cette définition.

Une question de goût

Il existe plusieurs styles populaires d'indentation et de placement des accolades entourant les instructions composées. Quel que soit le format que vous adoptez, il est important d'être cohérent. L'indentation est le moyen accepté de transmettre l'imbrication voulue des instructions de programme à d'autres programmeurs. Cependant, le compilateur ne comprend que les accolades. Les rendre aussi visibles que possible aidera à repérer les erreurs d'imbrication plus tard. Quel que soit le temps consacré à l'écriture de code lisible, le langage de programmation Aztec C est suffisamment bas niveau pour permettre des expressions très particulières.

/* Il est important d'insérer des commentaires régulièrement ! */

Les commentaires sont particulièrement utiles comme brèves introductions aux définitions de fonctions. En général, une observation modérée de ces suggestions réduira le nombre de trucs que le langage de programmation Aztec C jouera sur vous - même après avoir maîtrisé sa syntaxe.

Programmation structurée

La «programmation structurée» est une tentative d'encourager une programmation caractérisée par la méthode et la clarté. Il découle de la théorie selon laquelle toute tâche de programmation peut être divisée en composantes plus simples. Les trois parties de base sont les instructions, les boucles et les conditions. En Aztec C, ces parties sont, respectivement, tout ce qui est entouré d'accolades ou se terminant par un point-virgule: for, while et do-whi1e; if-else.

Modularité et structure des blocs

Le concept de modularité est au coeur de la programmation structurée. En un sens, tout fichier source compilé par lui-même est un module. Cependant, le terme est utilisé ici avec une signification plus spécifique. Dans ce contexte, la modularité fait référence à l'indépendance ou à l'isolement d'une routine par rapport à une autre. Par exemple, une routine telle que main) peut appeler une fonction pour effectuer une tâche donnée même si elle ne sait pas comment la tâche est accomplie ou quelles valeurs intermédiaires sont utilisées pour atteindre le résultat final. Les sections d'un programme mises de côté par des accolades sont appelées blocs. La confidentialité de la structure de bloc de C garantit que les variables de chaque bloc ne sont pas partagées par inadvertance par d'autres blocs. Toute accolade gauche ({) signale le début d'un bloc, comme le corps d'une fonction ou une boucle for. Comme chaque bloc peut avoir son propre ensemble de variables, une accolade gauche marque une opportunité de déclarer une variable temporaire. Une fonction en C est un bloc spécial car elle est appelée et passe le contrôle de l'exécution. Une fonction est appelée, s'exécute et retourne. Essentiellement, un programme C est une telle routine, à savoir, main. Un appel de fonction représente une tâche à accomplir. Les instructions de programme pouvant autrement apparaître comme plusieurs lignes obscures peuvent être mises de côté dans une fonction satisfaisant un objectif souhaité. Par exemple, getchar est utilisé pour obtenir un seul caractère à partir d'une entrée standard. Lorsqu'une section de code doit être modifiée, il est plus simple de remplacer un seul bloc modulaire que de supprimer une section d'un programme non structuré dont les limites peuvent au mieux ne pas être claires. En général, plus un bloc de programme est défini avec précision, plus il peut être modifié facilement.

Programmation descendante

La programmation «descendante» est une méthode tirant parti des fonctionnalités de programmation structurée comme celles décrites ci-dessus. C'est une méthode de conception, d'écriture et de test d'un programme depuis la fonction la plus générale (c'est-à-dire (main()) jusqu'aux fonctions les plus spécifiques (comme getchar()). Tous les programmes C commencent par une fonction appelée main(). Le main() peut être considéré comme un superviseur ou un gestionnaire faisant appel à d'autres fonctions pour effectuer des tâches spécifiques, en faisant peu du travail lui-même. Si l'objectif global du programme peut être considéré en quatre parties (par exemple, entrée, traitement, vérification des erreurs et sortie), alors main() doit appeler au moins quatre autres fonctions.

La première étape

La première étape de la conception d'un programme est d'identifier ce qui doit être fait et comment cela peut être accompli de manière programmable. La routine principale devrait être grandement simplifiée. Il doit appeler une fonction pour effectuer les étapes cruciales du programme. Par exemple, il peut appeler une fonction, init(), prenant en charge toutes les initialisations de démarrage nécessaires. À ce stade, le programmeur n'a même pas besoin d'être certain de toutes les initialisations ayant lieu dans init(). Toutes les fonctions se composant de trois parties : une liste de paramètres, un corps et une valeur de retour. La conception d'une fonction doit se concentrer sur chacun de ces trois éléments. Lors de cette première étape de conception, chaque fonction peut être considérée comme une boîte noire. Nous ne nous préoccupons que de ce qui entre et de ce qui sort, pas de ce qui se passe à l'intérieur. Ne vous laissez pas distraire par les détails de la mise en oeuvre à ce stade. Les organigrammes, pseudocodes, tables de décision et autres sont utiles à ce stade de la mise en oeuvre. Une liste détaillée des données étant transmises entre les fonctions est importante et ne doit pas être négligée. L'interface entre les fonctions est cruciale. Bien que toutes les fonctions soient écrites dans un but précis, il est facile de fusionner involontairement deux tâches en une seule. Parfois, cela peut être fait dans l'intérêt de produire une fonction de programme compacte et efficace. Cependant, le résultat habituel est une fonction encombrante et ingérable. Si une fonction devient très volumineuse ou si sa logique devient difficile à comprendre, elle doit être réduite en introduisant des appels de fonction supplémentaires.

Deuxième étape

Il arrive un moment où un programme doit passer de la phase de conception à la phase de codage. Vous pouvez trouver l'approche descendante du codage trop restrictive. Selon ce schéma, les fonctions les plus petites et les plus spécifiques seraient codées en dernier. Il est de notre nature de s'attaquer d'abord aux problèmes les plus redoutables, ce qui signifie généralement coder les fonctions de bas niveau. Alors que l'approche descendante est la méthode préférée pour concevoir un logiciel, l'approche ascendante est souvent la méthode la plus pratique pour écrire un logiciel. Avec une bonne conception, l'une ou l'autre des méthodes de mise en oeuvre devrait produire des résultats tout aussi bons. Un atout de l'écriture descendante est la possibilité de fournir des tests immédiats sur les routines de niveau supérieur. Les appels de fonction non résolus peuvent être satisfaits par des fonctions «factices» renvoyant une intervalle de valeurs de test. Lorsque de nouvelles fonctions sont ajoutées, elles peuvent fonctionner dans un environnement ayant déjà été testé. Les fonctions C sont plus efficaces lorsqu'elles sont aussi indépendantes que possible. Cette indépendance est encouragée par le fait qu'il n'y a normalement qu'une seule manière d'entrer et de sortir d'une fonction : en l'appelant avec des paramètres spécifiques et en renvoyant une valeur significative. Toute fonction peut être modifiée ou remplacée tant que ses points d'entrée et de sortie sont cohérents avec la fonction appelante.

Programmation défensive

La «programmation défensive» obéit au même édit que la conduite défensive : ne faites confiance à personne pour faire ce que vous attendez. Il y a deux côtés à cette règle de base. Défendez à la fois la possibilité de mauvaises données ou une mauvaise utilisation du programme par l'utilisateur, et la possibilité de mauvaises données générées par un mauvais code. Les pointeurs, par exemple, sont une source principale de variables égarées. Même si la «théorie» des pointeurs peut être bien comprise, les utiliser de manière nouvelle (ou pour la première fois) nécessite un examen attentif à chaque étape. Les pointeurs présentent le moins de problèmes lorsqu'ils apparaissent dans des environnements familiers.

Face à l'inconnu

Lorsque vous essayez quelque chose de nouveau, écrivez d'abord quelques programmes de test pour vous assurer que la syntaxe que vous utilisez est correcte. Par exemple, considérons un tampon, str_buf, rempli de chaînes de caractères terminées par NULL. Supposons que nous voulions afficher la chaîne de caractères commençant au début du déplacement dans le tampon. Est-ce la manière de procéder ?

printf("%s",str_buf[begin]):

Une petite enquête montre que str_buf[begin] est un caractère, pas un pointeur vers une chaîne de caractères, étant ce qui est demandé. La déclaration correcte est :

printf("%s", str_buf + begin):

Ce type d'erreur peut ne pas être évident lorsque vous le voyez pour la première fois. Il existe d'autres sujets pouvant être gênants lors de la première exposition. La promotion des types de données dans les expressions en est un exemple. Même si vous êtes sûr du comportement d'une nouvelle construction, il ne fait jamais de mal de revérifier avec un programme de test. Certaines habitudes de programmation faciliteront la syntaxe. Le plus important est la simplicité du style. La programmation descendante vise à produire des fonctions brèves et par conséquent simples. Cette simplicité ne doit pas disparaître lorsque le dessin est codé. Le code doit apparaître aussi "idiomatique" que possible. les pointeurs peuvent à nouveau fournir un exemple : c'est un fait de la syntaxe C que les tableaux et les pointeurs sont une seule et même chose. Voici donc :

array[offset]

est la même chose que

*(array + offset)

La seule différence est qu'un nom de tableau n'est pas une valeur; c'est qu'il est fixe. Mais mélanger les deux façons de référencer un objet peut être source de confusion, comme dans le dernier exemple. Le choix d'un certain idiome, étant connu pour se comporter d'une certaine manière, peut aider à éviter de nombreuses erreurs d'utilisation.

Quand des bugs frappent

L'hypothèse doit être que vous devrez retourner au code source pour apporter des modifications, probablement à cause de ce qu'on appelle un bogue. Les bogues se caractérisent par leur persistance et leur tendance à se multiplier rapidement. Des erreurs peuvent se produire au moment de la compilation ou de l'exécution. Les erreurs de compilation sont un peu plus faciles à résoudre car ce sont généralement des erreurs de syntaxe que le compilateur signalera.

Depuis le compilateur

Si le compilateur détecte une erreur dans le code source, il enverra un code d'erreur à l'écran et essaiera de spécifier où l'erreur s'est produite. Il existe plusieurs particularités concernant les rapports d'erreurs devant être évoquées immédiatement. La plus notable de ces particularités est le nombre de fausses erreurs que le compilateur peut signaler. Cet intervalle d'incohérence est appelé la récupération du compilateur. Le moyen le plus sûr de traiter une liste d'erreurs inhabituellement longue est de corriger la première erreur, puis de la recompiler avant de continuer. Le compilateur précisera où il a remarqué que quelque chose n'allait pas. Cette situation n'indique pas nécessairement où vous devez apporter une modification au code. Le numéro d'erreur est un indice plus précis, car il montre ce que le compilateur recherchait lorsque l'erreur s'est produite.

Si jamais cette situation vous arrive

Un exemple courant de ceci est l'erreur 69 : point-virgule manquant. Ce code d'erreur sera émis si le compilateur attend un point-virgule lorsqu'il trouve un autre caractère. Étant donné que cette erreur survient le plus souvent à la fin d'une ligne, elle peut ne pas être signalée avant le premier caractère de la ligne suivante - rappelez-vous que les espaces, tels qu'un caractère de nouvelle ligne, sont ignorés. Une telle erreur peut être particulièrement déloyale dans certaines situations. Par exemple, un point-virgule manquant à la fin d'un fichier #include peut être signalé lorsque le compilateur revient à lire l'entrée dans le fichier d'origine. En général, il est utile d'examiner une erreur de syntaxe du point de vue du compilateur. Considérez cette erreur :

struct structag {
char c;
int i;
}
int j;

Cela devrait générer une erreur 16 : conflit de type de données. La flèche dans le message d'erreur doit indiquer que l'erreur a été détectée juste après le int dans la déclaration de j. Cette situation signifie que l'erreur a à voir avec quelque chose avant cette ligne, car il n'y a rien d'illégal dans le mot clef int. Par inspection, nous pouvons voir que le point-virgule est absent de la ligne précédente. Si ce fait échappe à notre avis, nous savons toujours que l'erreur 16 signifie ceci : le compilateur a trouvé une déclaration du format :

[type de données] [type de données] [nom du symbole]

où les deux types de données étaient incompatibles. Ainsi, si short int est un bon type de données, double int ne l'est pas. Un petit saut intuitif nous amène à supposer que le compilateur a lu notre source comme une sorte de déclaration "struct int" : struct est le seul mot-clef précédant l'int ayant pu provoquer cette erreur. Puisque le compilateur lit les deux déclarations comme une seule instruction, nous devons manquer un délimiteur.

Erreurs d'exécution

Il faut un peu plus d'ingéniosité pour localiser les erreurs se produisant au moment de l'exécution. Dans les calculs numériques, seuls les résultats les plus anormaux attireront l'attention sur eux-mêmes. D'autres bogues généreront une sortie semblant provenir d'un programme entièrement différent. Un bogue est plus utile lorsqu'il est répétable. Les bogues n'apparaissant que parfois sont simplement vexants. Ils peuvent être causés par un fichier disque corrompu ou une mauvaise commande de l'utilisateur. Lorsqu'une erreur peut être produite de manière cohérente, sa source peut être localisée plus facilement. La nature d'une erreur est un bon indice quant à sa source. Une grande partie de votre temps et de votre santé mentale sera préservée en mettant de côté quelques minutes pour réfléchir au problème. Quels modules sont impliqués dans le calcul ou le processus ? De nombreuses possibilités peuvent être éliminées dès le départ, telles que des morceaux de code n'étant pas liés à l'erreur. Le premier objectif est de déterminer, à partir d'un certain nombre de possibilités, quel module pourrait être à l'origine du bogue.

Vérification des données d'entrée

Les entrées dans le programme peuvent être vérifiées à faible coût. La vérification des erreurs de ce type doit être incluse sur une base de routine. Par exemple, «if ((fp=fopen("file","r"))==NULL)» devrait être un réflexe qu'un fichier est ouvert. Toute gestion d'erreur utile peut suivre dans le corps du fichier if. Il est facile de vérifier vos données lorsque vous les mettez la main pour la première fois. Si une erreur survient après cela, vous avez un bogue dans votre programme.

Afficher

Il est utile de savoir où les données tournent mal. Un moyen brutal de traquer le bogue est d'insérer des instructions printf partout où les données sont référencées. Lorsqu'une valeur inattendue apparaît, un seul module peut être choisi pour une enquête plus approfondie. La recherche avec printf sera plus efficace lorsqu'elle sera effectuée avec plus de raffinement. Choisissez un module suspect. Il n'y a que deux points clefs à vérifier : l'entrée et le retour de la fonction. Afficher les données en question dès que la fonction est entrée. Si les valeurs sont déjà incorrectes, vous voudrez vous assurer que les données correctes ont été transmises lors de l'appel de fonction. Si une valeur incorrecte est renvoyée, la recherche se limite à la fonction coupable. Même si la fonction renvoie une bonne valeur, vous souhaiterez peut-être vous assurer qu'elle est gérée correctement par la fonction appelante. Si tout semble fonctionner, passez au module délicat suivant et effectuez une autre vérification. Lorsque vous trouvez un mauvais résultat, vous devrez quand même revenir en arrière pour découvrir précisément où les données ont été gâtées.

Appels de fonction

Sachez que les données peuvent être déformées lors d'un appel de fonction. Les paramètres de fonction doivent être déclarés lorsqu'ils ne sont pas des entiers à deux octets. Par exemple, si une fonction est appelée :

fseek(fp, 0, 0)

afin de chercher au début d'un fichier, mais la fonction est définie de cette façon :

fseek(fp, offset, origin)
FILE *fp;
long offset;

il y aura des conséquences malheureuses. Le deuxième paramètre devrait être un entier long (quatre octets), mais ce qui est passé est un entier court (deux octets). Dans un appel de fonction, les paramètres sont simplement poussés sur la pile; lorsque la fonction est entrée, ils sont à nouveau retirés. Dans l'exemple, deux octets sont activés, mais quatre octets (quels que soient les quatre octets présents) sont retirés. La solution est simplement de faire du deuxième paramètre un long, avec un suffixe (0L) ou par l'opérateur de cast (comme dans «(long)i»). Un problème similaire se produit lorsqu'une valeur de retour non entière n'est pas déclarée dans la fonction appelante. Par exemple, si sqrt est appelé, il doit être déclaré comme renvoyant un double :

double sqrt();

Cette méthode de débogage démontre l'utilité d'avoir une conception solide avant qu'une fonction ne soit codée. Si vous savez ce qui devrait entrer dans une fonction et ce qui devrait en sortir, le processus de vérification de ces données est beaucoup plus simple.

Le trouvé

Lorsque la fonction coupable est isolée, la difficulté de trouver le bogue est proportionnelle à la simplicité du code. Cependant, la recherche peut se poursuivre de la même manière. Vous devriez avoir une bonne idée de l'objectif de chaque bloc, comme une boucle. En insérant un printf dans une boucle, vous pouvez observer l'effet de chaque passage sur les données. Les printf peuvent également indiquer quels blocs sont réellement exécutés. Passer à travers un test, comme un if ou un interrupteur, peut être une source subtile de problèmes. Les conditions ne doivent pas laisser les cas non testés. Un autre, ou une valeur par défaut dans un commutateur, peut sauver le code d'une entrée inattendue. Et si vous n'êtes pas sûr du fonctionnement d'un morceau de code, il vaut généralement la peine de mettre en place de petits programmes de test et d'observer ce qui se passe. Ceci est instructif et peut révéler un bug ou deux.

Choses à surveiller

Certaines erreurs surviennent encore et encore. Tous ne s'en vont pas avec l'expérience. La liste suivante vous donnera une idée du genre de choses pouvant mal tourner.

1. point-virgule ou accolade manquant

Le compilateur vous dira quand un point-virgule ou une accolade manquant a introduit une mauvaise syntaxe dans le code. Cependant, souvent une telle erreur n'affectera que la structure logique du programme : le code peut compiler et même s'exécuter. Lorsque cette erreur n'est pas révélée par l'inspection, elle est généralement mise en évidence par un test printf étant exécuté trop souvent ou pas assez. Voir l'erreur 69 du compilateur.

2. affectation (=) vs comparaison (==)

Étant donné que des valeurs sont affectées aux variables plus souvent qu'elles ne sont testées pour l'égalité, le premier opérateur a reçu une seule frappe : «=». Notez que tous les tests de comparaison avec égalité sont composés de deux caractères : <=, >= et ==.

3. point-virgule mal placé

Lors de la saisie dans un programme, gardez à l'esprit que toutes les lignes source ne se terminent pas automatiquement par un point-virgule. Les lignes de contrôle sont particulièrement sensibles à un point-virgule indésirable :

for(i=0; i < 100; i++);
printf("%d",i);

Cet exemple affiche uniquement le nombre 100.

4. division (/) vs séquence d'échappement (\)

Le langage de programmation Aztec C fait clairement la distinction entre ces caractères. Le signe de division se trouve sous le point d'interrogation sur une console standard; la barre oblique inverse est généralement plus difficile à trouver.

5. constante de caractères (') versus chaîne de caractères (")

Les constantes de caractère sont en fait des entiers égaux aux valeurs ASCII du caractère respectif. Une chaîne de caractères est une série de caractères terminée par un caractère nul (\0). Le délimiteur approprié est le guillemet simple et le guillemet double, respectivement.

6. variable non initialisée

À un moment donné, toutes les variables doivent recevoir des valeurs avant d'être utilisées. Le compilateur définira les variables globales et statiques sur zéro, mais les variables automatiques sont garanties de contenir des déchets à chaque fois qu'elles sont créées.

7. évaluation des expressions

Pour la plupart des opérations en C, l'ordre d'évaluation est défini de manière rigide. Par exemple, le résultat de l'exemple suivant est 8. Les opérateurs de priorité égale sont évalués de gauche à droite ou de droite à gauche, tels qu'ils sont définis.

int a = 2, b = 3, c = 4, d;
d = a + b / a * c;
/* est évalué */
d = a + ( (b / a) * c);

Prenons cet exemple :

if((c = 0) || (c = 1) printf("%d", c);

"1" sera affiché: puisque la première moitié de la condition est évaluée à zéro, la seconde moitié doit également être évaluée. Mais dans cet exemple :

if((c = 0) && (c = 1) ) printf("%d", c):

un "0" est affiché. Étant donné que la première moitié est évaluée à zéro, la valeur du conditionnel doit être zéro ou fausse et l'évaluation s'arrête. C'est une propriété des opérateurs logiques.

8. Ordre d'évaluation indéfini

Malheureusement, tous les opérateurs n'ont pas reçu un ensemble complet d'instructions sur la manière dont ils devraient être évalués. Un bon exemple est l'opérateur d'incrémentation (ou de décrémentation). Par exemple, ce qui suit n'est pas défini :

i = ++i + --i/++i - i++;

La façon dont une telle expression est évaluée par une mise en oeuvre particulière est appelée «effet secondaire». En général, les effets secondaires doivent être évités.

9. évaluation des expressions booléennes

Les «et», «ou» et «négations» invitent le programmeur à écrire de longues conditionnelles dont le but même est perdu dans le code. Les booléens doivent être brefs et précis. En outre, les opérateurs logiques au niveau du bit doivent être entièrement entre parenthèses. Le tableau de la page [] montre leur priorité par rapport aux autres opérateurs. Voici un exemple extrême de la façon dont un booléen long peut être réduit :

if((cc = getchar(» != EOF && c >= 'a' && c <= 'z' && (c = getchar()) >;= '1' && c <= '9') printf("Bonne sortie\n");
if((c = getchar() != EOF)
if (c >= 'a' && c <= 'z')
if ((c = getchar()) >= '0' && c <= '9')
printf("Bonne sortie\n");

10. Commentaires mal formés

La théorie de la syntaxe des commentaires est simplement que tout ce qui se passe entre un /* gauche et un droit */ est ignoré par le compilateur. Néanmoins, un */ manquant ne doit pas être négligé comme une erreur possible. Notez que les commentaires ne peuvent pas être imbriqués, c'est-à-dire :

/* /* cela provoquera une erreur */ */

Et cela pourrait vous arriver aussi :

/* le reste de ce fichier est ignoré jusqu'à un autre commentaire /*

11. Erreur d'imbrication

N'oubliez pas que l'imbrication est déterminée par des accolades et non par des indentations dans le texte de la source. Les instructions imbriquées si méritent une attention particulière car elles sont souvent associées à une autre.

12. utilisation d'autre

Tout le reste doit être associé à un if. Lorsqu'un autre est resté inexplicablement non apparié, la cause est souvent liée à la première erreur de cette liste.

13. tomber à travers les boîtiers dans un interrupteur

Pour conserver le plus de contrôle possible sur les cas dans une instruction switch, il est conseillé de terminer chaque cas par une pause, y compris le dernier cas du commutateur.

14. boucles étranges

Le comportement des boucles peut être exploré en insérant des instructions printf dans le corps de la boucle. De toute évidence, cela indiquera si la boucle a même été entrée au cours d'une exécution. Un compteur indiquera combien de fois la boucle a été exécutée; une petite erreur provoquera une boucle à traverser une fois trop souvent ou rarement. La condition de sortie de la boucle doit être vérifiée deux fois pour la précision.

15. utilisation de chaîne de caractères

Toutes les chaînes de caractères doivent être terminées par un caractère nul en mémoire. Ainsi, la chaîne de caractères "bonjour" occupera un tableau de 8 éléments; le huitième élément est '\0'. Cette convention est essentielle lors du passage d'une chaîne de caractères à une fonction de bibliothèque standard. Le compilateur ajoutera automatiquement le caractère nul aux constantes de chaîne de caractères.

16. pointeur vs objet d'un pointeur

La plus grande difficulté dans l'utilisation des pointeurs est d'être sûr de ce qui est nécessaire et de ce qui est utilisé. Les fonctions acceptant un paramètre de pointeur nécessitent une adresse en mémoire. La meilleure façon de s'assurer que la valeur correcte est transmise est de garder une trace de ce qui est pointé par quel pointeur.

17. indice de tableau

Le premier élément d'un tableau C a un indice de zéro. Le nom du tableau sans indice est en fait un pointeur vers cet élément. De toute évidence, de nombreux problèmes peuvent se développer à partir d'un indice incorrect. Le plus dommageable peut être l'indexation hors limites, car cela accédera à la mémoire du tableau et écrasera toutes les données s'y trouvant. Si des éléments de tableau ou des données entreposées avec des tableaux sont perdus, cette erreur est un bon candidat.

18. interface de fonction

Pendant la phase de conception, les composantes d'un programme doivent être associés à des fonctions. Il est important que les données transmises ou partagées par ces fonctions soient explicitement définies dans la conception préliminaire du programme. Cette situation facilitera grandement le codage du programme car l'interface entre les fonctions doit être précise à plusieurs égards. Tout d'abord, si les paramètres d'une fonction sont établis, un appel peut être effectué sans réserve qu'il sera modifié ultérieurement. Il y a moins de chances que les paramètres soient du mauvais type ou spécifiés dans le mauvais ordre. Une fonction ne reçoit qu'une copie privée des variables lui étant transmises. C'est une bonne raison pour décider lors de la conception du programme comment les fonctions doivent accéder aux données dont elles ont besoin. Vous pourrez détailler les paramètres à passer dans un appel de fonction, les données globales que la fonction va modifier, la valeur que la fonction retournera et quelles déclarations seront appropriées - le tout sans vous soucier de la façon dont la fonction sera codée. Les déclarations de paramètres devraient être une question assez simple une fois que ces choses sont connues. Notez que cette liste de déclarations doit se trouver avant l'accolade gauche du corps de la fonction. Le type de la fonction est le même que le type de la valeur qu'elle renvoie. Les fonctions doivent être déclarées comme n'importe quelle variable. Et tout comme les variables, les fonctions seront par défaut de type int, c'est-à-dire que le compilateur supposera qu'une fonction renvoie un entier si vous ne lui dites pas le contraire avec une déclaration. Ainsi, si la fonction f appelle la fonction g renvoyant une variable de type de données double, la déclaration suivante est nécessaire :

function f() {
double g(), bigfloat;
}

g(bigfloat);

double g(arg)
double arg;
{
return(arg);
}

19. être sûr de ce qu'une fonction renvoie

Vous saurez probablement très bien ce que renvoie une fonction que vous avez écrite vous-même. Mais il faut être prudent lors de l'utilisation de fonctions codées par quelqu'un d'autre. Cela est particulièrement vrai pour les fonctions de bibliothèque standard. La plupart des fonctions de bibliothèque fournies renverront un pointeur int ou char où vous pourriez vous attendre à un char. Par exemple, getchar() renvoie un int, pas un char. Les fonctions fournies par Manx Software dans Aztec C adhèrent au modèle UNIX dans tous les cas, sauf dans quelques cas. Bien sûr, ce qui précède s'applique également aux paramètres d'une fonction.

20. données partagées

Les variables déclarées globalement sont accessibles par toutes les fonctions du fichier. Ce n'est pas un moyen très sûr de transmettre des données aux fonctions car une fois qu'une variable globale est modifiée, il n'est pas possible de la ramener à son état antérieur sans une méthode élaborée de sauvegarde des données. De plus, les données mondiales doivent être gérées avec soin; une fonction peut traiter la mauvaise variable et par conséquent inhiber toute autre fonction dépendant de ces données. Puisque C fournit et même encourage les données privées, cela ne devrait certainement pas être un bogue commun.



Dernière mise à jour : Dimanche, le 15 octobre 2017