Bien programmer en langage C
Date de publication : 28 mai 2008
II. Codage en langage C
II-A. Conventions de nommage
II-A-1. Constantes
II-A-1-a. Types
II-A-1-b. Conventions typographiques
II-A-1-c. Objets (Variables)
II-A-1-c-i. Conventions typographiques
II-A-1-c-ii. Identificateur
II-A-1-d. Fonctions
II-A-1-d-i. Conventions typographiques
II-A-1-d-ii. Identificateur
II-B. Organisation du code source
II-B-1. Fichiers sources (*.c)
II-B-1-a. Liste ordonnée des éléments pouvant être contenus dans un fichier source
II-B-2. Fichiers d'en-tête (*.h)
II-B-2-a. Règles d'or régissant la définition des fichiers d'en-tête
II-B-2-b. Liste ordonnée des éléments pouvant être contenus dans un fichier d'en-tête
II-B-2-c. Protection contre les inclusions multiples
II-C. Comment bien organiser son développement.
II-D. Comment construire sa bibliotheque.
II-D-1. Outil de développement gcc
II-D-1-a. Création d'une bibliothèque sous Code::Blocks
II-E. Comment bien configurer son compilateur.
II-E-1. gcc
II-E-2. Reglages dans Code::Blocks
II-E-3. Reglages dans wxDev-C++ (remplace Dev-C++ devenu obsolète)
II-E-4. Microsoft Visual C++ (6, 2005, 2008 etc.)
II-E-5. Borland C / Turbo C
II. Codage en langage C
II-A. Conventions de nommage
II-A-1. Constantes
II-A-1-a. Types
Le langage C permet de créer un alias sur un type existant plus ou moins complexe ou un autre alias existant
à l'aide du mot clé typedef.
II-A-1-b. Conventions typographiques
Il est d'usage d'utiliser les minuscules. Il est recommandé d'utiliser des suffixes tels que :
Suffixes de types (alias)
_e
|
typedef enum
|
_u
|
typedef union
|
_s
|
typedef struct
|
_a
|
tableau (array)
|
_f
|
fonction
|
NOTA : Il est recommandé de ne pas utiliser le suffixe '_t', car il est réservé par la norme POSIX pour
définir des alias de types.
II-A-1-c. Objets (Variables)
II-A-1-c-i. Conventions typographiques
Il est d'usage d'utiliser les minuscules.
II-A-1-c-ii. Identificateur
Il est un principe général de bonne conception qui veut que la longueur des identificateurs des objets
et des fonctions soit proportionnelle à leur portée.
Les variables indroduisent en plus la notion de durée de vie (duration). Il est bon qu'au premier coup
d'oeil, on sache exactement à quoi on a affaire. Il est bon de pouvoir aussi identifier rapidement
les variables en fonction de leur propriétés. Les plus remarquables sont :
-
simple (plain)
-
pointeur
-
tableau statiques
-
tableau dynamiques
-
chaine de caractères
Il est donc recommandé d'utiliser les préfixes suivants :
Préfixes d'identificateurs
|
simple
|
p_
|
pointeur
|
a_ ou sa_
|
tableau statiques(static array)
|
da_
|
tableau (dynamic array)
|
s_
|
chaine de caracteres (string)
|
et en ce qui concerne la durée de vie et la portée:
Préfixes indiquant la durée de vie et la portée
|
Portée
|
Durée de vie
|
|
bloc
|
bloc
|
g_
|
bloc
|
programme
|
S_
|
module
|
programme
|
G_
|
programme
|
programme
|
II-A-1-d. Fonctions
II-A-1-d-i. Conventions typographiques
Il est d'usage d'utiliser les minuscules. Il est des cas où l'on a besoin de 2 mots pour nommer une fonction.
Il n'est évidemment pas question d'accoler ces deux mots en minuscule, car le code serait vite
illisible. Il existe 2 pratiques répandues :
-
Coller les mots en mettant une majuscule au début de chaque mot :
ou (variante 'à-la-Java') à partir du 2ème mot :
-
Séparer les mots en mettant un souligné (underscore) entre chaque mot.
C'est une question de goût, le principal est d'être clair et cohérent. La seconde méthode a ma préférence.
II-A-1-d-ii. Identificateur
Pour une fonction, on choisit plutôt un identificateur qui exprime une action. (Verbe, verbe + substantif)
II-B. Organisation du code source
Il est pratique d'adopter une disposition logique et cohérente dans les fichiers sources et d'en-têtes.
Il est d'usage d'utiliser un principe simple, qui consiste à définir ce que l'on doit utiliser.
C'est pourquoi la disposition suivante est recommandée :
II-B-1. Fichiers sources (*.c)
II-B-1-a. Liste ordonnée des éléments pouvant être contenus dans un fichier source
-
Inclusion des en-têtes (.h) nécessaires
-
Définition des macros privées
-
Définition des constantes privées
-
Définition des types privés
-
Définition des structures privées
-
Définition des variables globales privées
-
Définitions des fonctions privées
-
Définition des fonctions publiques
-
Définition des variables globales publiques
Voici par exemple, une liste de séparateurs utilisés pour structurer des fichiers sources :
II-B-2. Fichiers d'en-tête (*.h)
II-B-2-a. Règles d'or régissant la définition des fichiers d'en-tête
-
Un fichier d'en-tête doit être protégé contre les inclusions multiples dans la même unité de compilation.
-
Un fichier d'en-tête doit être autonome[1].
-
Un fichier d'en-tête ne doit inclure que le strict nécessaire à son autonomie.
------------------
[1] Pour s'en assurer, lors des tests de l'implémentation correspondante, le placer en 1er include et
ajouter ce qui manque dedans.
II-B-2-b. Liste ordonnée des éléments pouvant être contenus dans un fichier d'en-tête
-
Inclusion des headers nécessaires et suffisants
-
Définition des macros publiques
-
Définition des constantes publiques
-
Définition des types publiques
-
Définition des structures publiques
-
Déclaration des fonctions publiques
-
Déclaration des variables globales publiques
-
[C99] Définition des fonctions publiques inline
Voici par exemple, une liste de séparateurs utilisés pour structurer des fichiers d'en-tête :
II-B-2-c. Protection contre les inclusions multiples
Les fichiers d'en-tête pouvant être inclus dans des fichiers sources, comme dans des fichiers d'en-tête,
et ce, dans un ordre non spécifié, il est indispensable de se prémunir contre les risques d'inclusions
multiples dans la même unité de compilation.
# ifndef IDENTIFICATEUR
# define IDENTIFICATEUR
# endif
|
Le principe de protection étant basé sur la définition d'une macro, cette macro doit être unique afin
d'éviter les protections abusives.
Personnellement, j'utilise le format suivant :
H_< initials> _< file> _< date>
initials ::= En majuscule : initiales du développeur, nom de la société etc.
file ::= En majuscule : nom du fichier (sans l' extension)
date ::= < year> < month> < day> < hour> < minute> < second>
year ::= année (0000 - 9999 )
month ::= mois (01 - 12 )
day ::= jour (01 - 31 )
hour ::= heure (00 - 23 )
minute ::= minute (00 - 59 )
second ::= seconde (00 - 59 )
|
Par exemple :
H_ED_EXEMPLE_20040529115235
|
Nota : Le choix de mettre le H_ en tête est justifié. En effet, il évite de briser une règle du langage
C qui dit qu'un identificateur commençant par E suivi d'une majuscule est réservé pour l'implémentation.
En fait ils sont utilisés pour implémenter les valeurs symboliques de errno.
II-C. Comment bien organiser son développement.
Le langage C autorise la compilation séparée. Cette technique permet de créer des unités de compilations
(compile units) séparées. Une bonne maîtrise de la programmation permet de réaliser du code
indépendant ou tout au moins suffisamment indépendant pour être testable individuellement.
Voici un exemple élémentaire de compilation séparée reprenant le célèbre "Hello world!". Le code est
divisé en 3 sources. Un fichier d'interface (hello.h), un fichier d'implémentation (hello.c) et
un fichier d'application (main.c) :
# include "hello.h"
int main (void )
{
hello ();
return 0 ;
}
|
# ifndef H_HELLO
# define H_HELLO
void hello (void );
# endif
|
# include "hello.h"
# include <stdio.h>
void hello (void )
{
puts (" Hello world! " );
}
|
Afin de bien organiser les fichiers, je conseille de créer des répertoires pour les sources et pour les
fichiers de sortie:
Organisation des répertoires
Type
|
Répertoire
|
Fichiers
|
nom.ext
|
Génération
|
./
|
make
|
Makefile
|
Source
|
inc/
|
inclus
|
*.h
|
Source
|
src/
|
source
|
*.c
|
Sortie
|
obj/
|
objets intermédiaires
|
*.obj
|
Sortie
|
exe/
|
exécutable
|
*.exe
|
et par conséquent, d'adopter l'organisation suivante pour les répertoires :
hello/ Makefile
hello/ inc/ hello.h
hello/ src/ hello.c
hello/ src/ main.c
hello/ obj/ hello.obj
hello/ obj/ main.obj
hello/ exe/ hello.exe
|
IMPORTANT : Afin de garantir un comportement correct du code et des outils de développement, je recommande
que les noms de répertoire et de fichiers soient tous écrits en minuscule. Je rappelle que si on
utilise un outil de gestion de version comme CVS, il est extrêmement difficile de modifier la casse
des répertoire et même des fichiers après coup. C'est donc une démarche à adopter dès la premiere
ligne de code.
Pour compiler et exécuter ce code sous gcc avec cette organisation, on peut utiliser ce Makefile (DJGPP).
(Remplacer <tab> par une véritable tabulation)
# Makefile HELLO
# Paths
DSRC = src
DINC = inc
DOBJ = obj
DEXE = exe
DLIB = d:/ djgpp/ lib
# Compiler flags
CFLAGS = - I$(DINC)
# Commands
CC = gcc - c $(CFLAGS)
LK = ld
RM = del
# Project list
OBJS = \
$(DOBJ)/ hello.obj \
$(DOBJ)/ main.obj \
# Rebuild all target
all : $(DEXE)/ hello.exe
# Clean all target
clean :
< tab> cd $(DOBJ)
< tab> $(RM) * .obj
< tab> cd ../ $(DEXE)
< tab> $(RM) * .exe
< tab> cd ..
# main target ( OBJS + init code + library )
$(DEXE)/ hello.exe : $(OBJS)
< tab> $(LK) - o $(DEXE)/ hello.exe $(OBJS) $(DLIB)/ crt0.o - lc
# objects
# The hello . c file
$(DOBJ)/ hello.obj : $(DSRC)/ hello.c \
$(DINC)/ hello.h \
Makefile
< tab> $(CC) $(DSRC)/ hello.c - o$(DOBJ)/ hello.obj
# The main . c file
$(DOBJ)/ main.obj : $(DSRC)/ main.c \
$(DINC)/ hello.h \
Makefile
< tab> $(CC) $(DSRC)/ main.c - o$(DOBJ)/ main.obj
|
Avec une utilisation comme suit (MS-DOS):
-
Génération ou mise à jour :
D :\HELLO> make
gcc - c - Iinc src/ hello.c - oobj/ hello.obj
gcc - c - Iinc src/ main.c - oobj/ main.obj
ld - o exe/ hello.exe obj/ hello.obj obj/ main.obj d:/ djgpp/ lib/ crt0.o - lc
|
D :\HELLO> make clean
cd obj
del * .obj
cd ../ exe
del * .exe
cd ..
|
II-D. Comment construire sa bibliotheque.
Le langage C utilisant lui même la notion de bibliothèque de fonctions, il est logique que cette possibilité
soit offerte aux développeurs. Les avantages sont bien connus :
-
Identification et bonne gestion du code réutilisable
-
Interface bien défini (headers)
-
Gain de productivité (réutilisation,moins de code à compiler)
Les détails de réalisation dépendent des outils de développement, mais le principe d'organisation du
code est le même. Il est évidemment une application directe du principe de la
compilation
séparée.
S'ajoutent quelques règles de bon sens telles que l'indépendance du code vis à vis de l'application.
Cela concerne particulièrement les sorties qui, si il le faut, seront implémentées par des 'callbacks'
(Détails
ici). Plus que jamais, les
globales seront évitées.
Pour réaliser la bibliothèque, il faut créer un projet rassemblant les fichiers d'interface (headers
: .h) et les fichiers d'implémentation (sources : .c). Evidemment, il ne doit pas y avoir de fonction
main().
Ensuite, après compilation classique, et sans faire d'édition de lien, bien évidemment, on utilise un
'librarian' qui est un outil particulier faisant partie des outils de développements courants,
et qui sert à créer la bibliothèque. Le résultat est un fichier dont l'extension dépend de l'outil.
(par exemple, .lib ou .a)
II-D-1. Outil de développement gcc
Tout d'abord, avec gcc, il y a des regles de nommages à respecter. En effet, le nom du fichier produit
doit impérativement commencer par lib et son extension doit être .a (comme archive).
Ensuite, l'outil s'appelle ar (ar.exe sous DOS/Windows) comme ... archiver. Les détails
d'appels en mode commande sont à lire dans la documentation de gcc. Comme toujours en mode ligne
de commande, un Makefile simplifie et automatise la tâche de production du code.
Les exemples suivants utilisent le fichier d'interface
hello.h et le fichier d'implémentation
hello.c décrits dans l'article sur la
compilation
séparée. La bibliothèque à créer s'appelle
libhello.a. Elle ne sert évidemment qu'à
illustrer le propos.
II-D-1-a. Création d'une bibliothèque sous Code::Blocks
Project > New project
Selectionner ' static library '
Cocher ' do not create any file '
Clicker sur [create]
Enregistrer dans le répertoire .../ hello avec le nom libhello / ! \ IMPORTANT / ! \
|
Dans le 'workspace', une ligne en gras a été ajoutée : 'Static library'. Il faut la renommer pour plus
de clarté. La dénomination est arbitraire, du moment qu'on s'y retrouve, par exemple : 'Biblio.
Hello' :
Sur la ligne 'Static library' : click droit
Properties
Dans le champ 'title', remplacer 'Static library' par 'Biblio. Hello'
Valider
|
Il faut maintenant ajouter les 2 fichiers source (.c et .h). Si il n'y sont pas déjà, les déplacer (ou
les créer) dans le répertoire du projet (.../hello), puis :
Sur la ligne 'Biblio. Hello' : click droit
Add files
Selectionner hello.h et hello.c
Valider
Compiler
|
La fenêtre de compilation doit donner quelque chose comme ceci :
Project : Biblio. Hello
Compiler : GNU GCC Compiler (called directly)
Directory : C:\dev\hello\
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Switching to target: default
Compiling : hello.c
Linking static library: C:\dev\hello\libhello.a
ar.exe: creating C:\dev\hello\libhello.a
Process terminated with status 0 (0 minutes, 4 seconds)
0 errors, 0 warnings
|
On voit que hello.c a été compilé, que ar.exe a été invoqué et que libhello.a a été crée. On peut maintenant
créer un petit programme de test avec le main.c et la bibliothèque. Le plus simple pour le moment
est de la créer dans le même répertoire.
Project : Test hello
Compiler : GNU GCC Compiler (called directly)
Directory : C:\dev\hello\
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Switching to target: default
Compiling : main.c
Linking console executable: C:\dev\hello\test.exe
.objs\main.o: In function `main' :
C :/ dev/ hello/ main.c:7 : undefined reference to `hello'
collect2 : ld returned 1 exit status
Process terminated with status 1 (0 minutes, 2 seconds)
1 errors, 0 warnings
|
Bien sûr, le main.c tout seul ne suffit pas, il faut ajouter la bibliothèque au projet :
Sur la ligne ' Test biblio. Hello ' : click droit
Build options > Linker > Add > [...] > libhello.a > Ouvrir > NON > OK > OK
Compiler
|
On obtient alors :
Project : Test hello
Compiler : GNU GCC Compiler (called directly)
Directory : C:\dev\hello\
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Switching to target: default
Linking console executable: C:\dev\hello\test.exe
Process terminated with status 0 (0 minutes, 0 seconds)
0 errors, 0 warnings
|
et le fameux
Hello world!
Press ENTER to continue.
|
tant espéré !
II-E. Comment bien configurer son compilateur.
Le compilateur dispose de moyens d'analyse du code qui peuvent être mis à profit pour détecter toutes
sortes d'erreurs. Il est donc important de savoir configurer son compilateur pour qu'il signale
au mieux les anomalies pouvant se produire dans le code (Warnings). C'est ensuite au programmeur
de réagir et, soit de justifier ou d'expliquer l'avertissement, soit de corriger le code.
La première des vérifications à faire est de s'assurer que l'on compile avec le bon compilateur. Certaines
extensions ou réglages par défaut font que c'est parfois le compilateur C++ qui est invoqué au lieu
du compilateur C. Ces quelques lignes placées au début de chaque fichiers sources (.c) permettent
de detecter cette erreur :
# ifdef __cplusplus
# error This source file is not C + + but rather C . Please use a C - compiler
# endif
|
Si l'erreur se produit, vérifier les réglages et l'extension. Celle-ci doit impérativement être .c (minuscule),
et non .cpp, ni .C (majuscule)
II-E-1. gcc
gcc est le 'gnu c compiler', version
GNU du cc Unix pour GNU/Linux.
Il a été porté sur de nombreuses plateformes donc Windows (Les plus connues sont MinGW et CygWin)
Par défaut, le niveau d'avertissement (Warnings) est très laxiste. Il est fortement recommandé
d'utiliser la configuration minimale suivante :
- Wall - Wextra - O - Wwrite- strings - Wstrict- prototypes - Wuninitialized
- Wunreachable- code
|
|
Sur les anciennes version de gcc (< 3.4.x), remplacer -Wextra par -W.
|
Si la version de gcc le permet, il est possible d'ajouter ces options :
- Wno- missing- braces - Wno- missing- field- initializers
|
|
Experts uniquement
|
Il est aussi possible de procéder à un reglage 'fin'. Voici comment je configure gcc en mode 'paranoïaque'
- O2 - Wchar- subscripts - Wcomment - Wformat= 2 - Wimplicit- int
- Werror- implicit- function- declaration - Wmain - Wparentheses
- Wsequence- point - Wreturn- type - Wswitch - Wtrigraphs - Wunused
- Wuninitialized - Wunknown- pragmas - Wfloat- equal - Wundef
- Wshadow - Wpointer- arith - Wbad- function- cast - Wwrite- strings
- Wconversion - Wsign- compare - Waggregate- return - Wstrict- prototypes
- Wmissing- prototypes - Wmissing- declarations - Wmissing- noreturn
- Wformat - Wmissing- format- attribute - Wno- deprecated- declarations
- Wpacked - Wredundant- decls - Wnested- externs - Winline - Wlong- long
- Wunreachable- code
|
|
Attention ce reglage révèle certains défauts dans les headers de MinGW. La correction est possible, mais
uniquement si on sait ce qu'on fait. Je n'en dirai donc pas plus ici.
|
Le détail des paramètres est expliqué dans la
doc de gcc
II-E-2. Reglages dans Code::Blocks
Settings > Compiler > Onglet ' Compiler ' > Onglet ' Other options ' .
Copié/ collé les options
OK
Régénérer (Ctrl- F11)
|
|
S'assurer qu'une coche ne traine pas dans l'onglet de configuration du compilateur
|
II-E-3. Reglages dans wxDev-C++ (remplace Dev-C++ devenu obsolète)
Outils > Options du compilateur
Dans la zone de saisie " Ajouter les commandes suivantes lors de l'appel du compilateur " ,
copier/ coller les options que je recommande.
Valider
Régénérer (Ctrl- F11)
|
II-E-4. Microsoft Visual C++ (6, 2005, 2008 etc.)
Propriétés du projet -> C/C++ -> général.
Mettre le niveau (level) de warning à 4.
II-E-5. Borland C / Turbo C
Option -> Compiler -> Messages -> Display
(*) ALL
Copyright © 2008 Emmanuel Delahaye.
Aucune reproduction, même partielle, ne peut être faite
de ce site ni de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à
trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.