Le langage C
C'est le langage d'origine associé à Unix.
Il permet de la programmation scientifique classique,
ou de la programmation système via les librairies
associées.
Outils associés
Le langage C est un langage compilé. Il y a:
- les compilateurs: cc ou gcc
- les debuggers: gdb, dbx, xdbx ou ups,
dbxtool en Open-Windows
- les outils de trace: trace en SunOS, truss en Solaris,
voire strace.
- des outils associés: lint, size, nm,
ar.
Compiler, c'est:
Depuis un source a.c:
- passer le pré-processeur cpp
- générer un fichier assembleur a.s
- faire l'assemblage de ce fichier pour obtenir a.o
- faire l'édition de liens avec les librairies utiles
Les options du compilateur sont:
- -O : optimisation demandée
- -o NAME : nom du fichier final
- -c : s'arrêter au fichier .o
- -g : générer les informations pour
les debuggers
- -lx : ajouter la librairie x lors de
l'édition de liens. Ceci fait référence
au fichier:
- /usr/lib/libx.a en cas de compilation statique
- /usr/lib/libx.so.X.Y et /usr/lib/libx.sa.X.Y
en cas de compilation dynamique. NB: ceci interfère avec
la variable d'environnement LD_LIBRARY_PATH
Les avantages de la compilation dynamique sont:
- gain de place disque : les librairies ne sont
pas dupliquées
- les erreurs se corrigent par remplacement simple
des librairies sans recompilation
- on peut modifier des fonctions par remplacement
de la librairie. Exemple: gethostbyname(3)
Les inconvénients sont:
- non-portabilité: un programme n'est
plus autonome, et il dépend des librairies
dynamiques (ldd) et de leurs
emplacements.
Exemple:
# en un coup
gcc -o myprog myprog.c
# en 2 coups
gcc -c myprog
gcc -o myprog myprog.o
# avec la librairie math en dynamique
gcc -o myprog myprog.c -lm
# avec la librairie math en statique
gcc -o myprog myprog.c -Bstatic -lm
La compilation séparée
Un gros programme peut être découpé en fichiers
indépendants.
Exemple:
Soit
un fichier principal,
une première partie,
une seconde partie.
La compilation sera:
gcc -c main.c
gcc -c part1.c
gcc -c part2.c
gcc -o main main.o part1.o part2.o
qui donne à l'exécution:
Ceci est un message 1
Ceci est le fichier part1, message de part2()
Ceci est un message 1
Pour information:
$ size main
text data bss dec hex
8192 8192 0 16384 4000
Le langage C
Syntaxe
Le format est libre. Néanmoins il y a un format standard,
par exemple la sortie de cb(1).
Les commentaires sont /* commentaire */.
Les identificateurs sont une lettre, suivi de lettres
ou chiffres ou de soulignés. Il y a des mots
réservés.
Constantes
- les entiers: 123, 123L, 0xABC12, 0123, %01001001
- les caractères : 'l', '\n', '\043', '\x21'.
- les chaînes "Hello World!"
- les flottants : 12.34, 1.6e-19
Types de données
- void
- char
- short
- int
- long
- float
- double, identique à long float
avec la variante:
NB: la notion de booléen n'existe pas, on peut remplacer par
un char.
En Ansi C, il existe aussi le type enum:
enum couleur { violet = 0, indigo, bleu, vert, jaune, orange,
rouge};
Constructeurs
Les pointeurs
Un pointeur est l'adresse mémoire d'un élément.
Exemple:
int *a, b;
a = &b; /* b contient l'adresse de a */
*a = 5; /* ce qui est pointé par a reçoit 5 */
Cas particulier: le pointeur NULL.
Les tableaux
Indicé toujours à partir de 0.
Exemples:
float f[20][10];
int c[] = {30, 20, 10, 5,};
unsigned char hello[] = "Hello, World!\n";
Dans le dernier exemple:
hello[0] = 'H';
hello[1] = 'e';
...
hello[13] = '\n';
hello[14] = '\0';
Il y a définition implicite de pointeurs.
Les structures
On met en mémoire à la suite les différents champs:
struct complex {
float re, im;
};
struct chaine {
char *c;
struct chaine *next;
};
struct string {
char s[80];
struct string *next;
};
Les unions
On met en parallèle en mémoire les différents champs.
union entier {
char c[4];
int n;
};
Les champs de bit
C'est une structure où l'on descend au niveau du bit.
struct word {
unsigned octet:8;
unsigned fill:7;
unsigned bit:1;
unsigned demi:16;
};
Les typedef
Permet un raccourci.
typedef struct { float re, im; } complex;
Allocation en mémoire, portée
- static
réserve de la place,
la durée de vie est le programme,
la portée est le bloc ou sinon le fichier
- extern
ne réserve pas de place mais référence
un élément extérieur
la portée est le bloc ou sinon le fichier
- auto
réserve de la place,
la durée de vie est celle du bloc (allocation dans
la pile) ou sinon le programme
la portée est celle du bloc
- register
identique à auto,
c'est un conseil au compilateur pour garder la variable dans un registre.
Ici un exemple compliqué.
NB: on obtient la table des symboles via nm(1).
Les affectations
var = expression
lue aussi lvalue = expression
Le tableau des opérateurs par priorité décroissante:
- a[b] a(b,c,d) a.b a->b
- a++ ++a &expr *expr +expr -expr ~expr !expr
- sizeof(type)
- (type)expr
- * % /
- + -
- << >>
- <= >= < >
- == !=
- &
- ^
- |
- &&
- ||
- a ? b : c
- a=b a+=b a-=b
- expr, expr
Quelques remarques:
- j = i++ * i; n'est pas défini
- &a[i] est égal à a + i
- (type)valeur veut dire convertir la valeur
dans le type indiqué. Donc sin(2) est
stupide, contrairement à sin((float)2).
- un entier se convertit en pointeur sans changement
de valeur.
- (x == 0 || (y / x > 2))... a un sens: C procède
par évaluation paresseuse.
Les instructions
Les fonctions
Tout en C est une fonction. Le type void permet
de faire des fonctions sans paramètre ou
sans résultat (donc identique à une procédure).
Une fonction ressemble en ANSI C à:
TYPE_RESULTAT fct(type1 var1, type2 var2) {
...
return RESULTAT;
}
ou en C classique:
TYPE_RESULTAT fct(var1, var2)
type1 var1;
type2 var2;
{
...
return RESULTAT;
}
Le type de résultat ne peut pas être un tableau;
ce doit être un type simple, (en ANSI C) une structure
ou une union.
Un cas particulier: tout programme exécutable est une fonction
main:
int main(int argc, char **argv, char **envp);
{
return...;
/* ou : exit ... */
}
Le résultat de main, i.e. l'argument
de exit ou du return de la procédure
main est le code de retour du programme.
Tous les arguments sont passés par valeurs.
Quelques déclarations possibles:
extern int max(int,int);
void swap(&int,&int);
char *lirechaine(void);
int count();
int *f(); /* f est une fonction rendant un pointeur vers
un entier */
int (*f)(); /* f est un pointeur vers une fonction a
resultat entier */
La fonction printf
Le premier argument est un format:
%d %08d %x %-3d
%c
%s %-20s %10s
%f %8.2f %e %g
%%
qui dit comment seront imprimés les arguments suivants.
Exemples:
printf("Ceci est un texte\n");
printf("i = %d, j = %d\n",i,j);
printf("%20s\n","Ceci est court!");
Variante: sprintf
Un premier argument s'ajoute: une chaîne où sera
écrit le résultat:
char line[80];
sprintf(line,"i = %d j = %f\n",i,j);
Ces fonctions rendent:
- en Berkeley: 0 ou EOF
- en System V : le nombre de caractères ou EOF.
La fonction scanf
Sachant que toute variable est passée par valeur,
il faut donner à scanf des pointeurs! Le principe
est identique à printf:
scanf("%d %d",&i,&j);
scanf("i = %d",&i);
Cette fonction rend le nombre d'arguments effectivement lus.
Variante sscanf()
Même principe que sprintf().
Application: reconnaissance de lignes:
gets(line);
if (sscanf("i = %d,&i) == 1) {
....;
}
if (sscanf("x = %f",&x) == 1) {
....;
}
Le préprocesseur
Il a plusieurs fonctions:
- inclusion de fichiers standard:
#include <stdio.h>
#include <sys/file.h>>
#include "header.h"
On peut ajouter des répertoires de recherche via les
options -I d'appel du préprocesseur,
i.e. du compilateur. Exemple: gcc -I/usr/include/X11 ...
- des compilations conditionnelles
#define OPTION1
#define OPTION2 1
#ifdef OPTION1
....
#endif COMMENTAIRE
#ifndef OPTION3
....
#endif
#ifdef defined(OPTION2) && OPTION2 == 1
....
#elif ...
#else
#endif
Applications classiques:
- compilation en fonction du système
#ifdef SIGLOST
/* on est en SunOS */
#endif
#ifdef SVR4
/* on est en SVR4 */
#endif
- compilation en fonction de choix
#define DEBUG
...
#ifdef DEBUG
fprintf(stderr,"Ici n vaut : %d\n");
#endif
- définition de constantes et de paramétrages
#define LIMIT 100000
#define TAILLE 100
char i[TAILLE];
...
for (i = 0; i < LIMITE; i++) { ... }
Ces 2 derniers cas peuvent être définis à l'appel
du compilateur: gcc -DOPTION1 -DOPTION2=1 -DSVR4
- et plus généralement de macros:
#define max(a,b) ((a)>(b))?(a):(b)
#define sinus(x) (nbappels++, sin(x))
/* en ANSI C */
#define check(n,m) printf("%s > %d\n",#n,m); /* ici le nom */
#define cmd(n) { cnt##n++; }
En librairie
- allocation mémoire: alloc, calloc, free, realloc
- messages d'erreur: perror, errno
- test de caractères: isalpha, isalnum, isdigit, etc.
- fonctions mathématiques: acos, cos, sin, etc.
- nombres au hasard : rand, srand
- traitement de chaînes de caractères : strcpy,
strcmp, strlen,... et bcopy ou memcpy, index ou strchr.
- des conversions de texte : atoi, atof, itoa, etc.
- des tris: qsort(), bsearch()
- l'heure: time(), asctime(), gettimeofday(), localtime()
- l'environnement : getenv()
Quelques programmes
- primes.c : calcul des nombres premiers.
De l'arithmétique simple...
- compte.c : compter les espaces,
minuscules et majuscules, lettres d'un texte. On utilise
des fonctions de librairie.
- tree.c : un tri à l'aide
d'un arbre. Utilisation de malloc(), de pointeurs.
- showargs.c : le programme minimal
qui montre ses arguments et son environnement
- showargs2.c : une variante
sans la variable i.