Segmentation fault : que puis-je faire ?

Most good programmers do their work not because they expect to get paid or get recognition, but because they enjoy the programming.
--Linus Torvalds

Cette page s'adresse à l'apprenti programmeur en langage C. Elle donne quelques informations sur la pratique de la C-programmation dans le but de réaliser des GCC-expériences numériques sous un système GNU/linux. Il ne s'agit ni d'un manuel ni d'un cours de programmation. En effet, le fameux manuel The C programming language des fondateurs : Dennis Ritchie, Brian Kernighan, (Ken Thompson) est facile à trouver sur la toile. Pour ma part, j'ai découvert le C avec le manuel de Garetta, et je vous recommande vivement la visite de son site. Concernant gcc, il y a la précieuse introduction de Brian Gough préfacée par Stallman. Vous pouvez parcourir les cours de Bernard Cassagne ou encore celui d'Anne Canteaut. qui me paraissent complets et bien structurés, enfin je vous invite à participer au groupe de discussions fr.comp.langage.c , parcourir la faq du group comp.lang.c, ou encore suivre les conseils de Emmanuel Delahaye.

Introduction

First learn computer science and all the theory. Next develop a programming style. Then forget all that and just hack.
-- George Carrette
[skip ]

La dernière version de votre un programme, écrit en langage C, vient tout juste de passer le cap de la compilation. Notre collègue commun gcc, ne vous signale aucune erreur et pourtant, votre exécutable ne se termine pas comme il devrait. Si son dernier message segmentation fault ou encore bus error vous laisse perplexe, vous trouverez dans ces frames quelques moyens pour vous en sortir.

Bien entendu, ce petit texte s'adresse aussi à ceux et celles qui écrivent des programmes qui ne finissent jamais, ou font ce qu'ils ne devraient pas etc : les apprentis programmeurs, futurs mangeurs de pizzas et autres insecticides.

Adresse

C is not a high-level language.
--Brian Kernighan.
[skip ]

Le langage C est un langage de programmation de bas niveau, une sorte d'assembleur amélioré. L'apprentissage de ce type de langage ne peut se faire sans penser avec adresse et valeur.

// endianess test
int main( void )
{
char * x;
int z;
    z = 1 + (2 << 8) + (3<<16) + (4<<24);
    x = (char*) &z;
    printf("%p  %d\n", x, *x); x++;
    printf("%p  %d\n", x, *x); x++;
    printf("%p  %d\n", x, *x); x++;
    printf("%p  %d\n", x, *x); 
return 0;
}
Le programme ci-dessus fait un test de boutisme : ordre des octets en mémoire.
$ ./a.out 
0x7fff90b629f4  1
0x7fff90b629f5  2
0x7fff90b629f6  3
0x7fff90b629f7  4
Le débutant devrait sauter la suite.
int main( void )
{
char * x;
long long z;
    z = 683 * 8011;
    z *= 29*29;
    z *= 13;
    z <<= 3;
    x = (char*) &z;
    printf("my %s world\n", x);
return 0;
}
$ gcc z.c 
z.c: In function ‘main’:
z.c:12: attention : incompatible implicit declaration of built-in function ‘printf’
$ ./a.out 
my hello world
if ( ! capte() ) goto labas;
Exercice !
#include 

int main( int argc, char*argv[], char*env[] )
{
long long z;
int i=0, j=0;
    while (   *env[i] != 85) i++;
    while ( env[i][j] != 61) j++;
    j++;
    z = 2*j*2*j+2*j+1;
    z = (z << 32) + 1819043176;
    printf("%s %s!\n", &z, & (env[i][j]) );
return 0;
}

Quelques Conseils

Most of you are familiar with the virtues of a programmer. There are three, of course: laziness, impatience, and hubris.
-- Larry Wall
[skip ]
A toutes fins utiles...

[0] Programmez ce que vous comprenez.

[1] Ecrivez des programmes propres, clairs et concis.

[2] Les bons types font les bons programmes.

[3] Les identificateurs longs pour les variables globales

[4] Courts pour les locales, mais toujours un identificateur de type explicite.

[5] Evitez les variables globales

[6] Décomposez votre code en petites procédures et fonctions

[7] Commentez votre source sans être verbeux.

[8] Indentez votre texte.

[9] Certifiez vos procédures par des tests solides

[A] En matière de programmation : presque == pas du tout.

[B] Vérifiez régulièrement (man) votre compréhension des fonctions standards

[C] Méfiez vous des copier/coller

[D] Méfiez vous des copier/coller (bis)

[E] Ne programmez pas après deux heures du matin.

[F]   Programmer souvent.

[G] erreur de segmentation !

gcc de base

Nous évoquerons une assez petite partie de moins de 5% des possiblités du compilateur gcc
--PL
[skip ] Le débutant devrait compiler ses projets avec les options -Wall et -g, comme un expert :-) :
     gcc -Wall -g prog.c 
Les principales options de compilations utilisées dans ce document :
     -Wall  : obtenir les avertissements courants.
     -g     : obtenir du code debugable, 
              en liason avec gdb, valgrind
     -D     : passer une macro
     -U     : 
     -E     : passer la source au préprocesseur
     -S     : obtenir un programme en assembleur
     -c     : obtenir un code objet
     -o     : nom de l'output, par défaut : a.out
     -l     : librairie à utiliser pour une reliure
     -I     : chemin vers des fichiers d'entêtes 
     -L     : chemin vers des bibliothèques non-standard
    -shared : pour construire des bibliothèques dynamiques
    --help  : obtenir de l'aide : optimizers, warnings...
     -O2    : optimiser le code
     -pg             : profilage en liason avec gprof
     -fprofile-arcs 
     -ftest-coverage : test de couverture  avec gcov

commandes linux.

It is easier to port a shell than a shell script.
-- Larry Wall
[skip ]
Le programmeur en langage C sous linux utilise un terminal pour lancer des commandes par l'intermédiare d'un shell, préparation de l'environnement de travail, édition etc... Voilà, une liste de commandes usuelles dans un ordre d'apprentissage souhaitable ( plus ou moins ).
echo    : envoyer un message sur stdout
        : echo $PATH
        : echo -e '1\n2\n3\n' > /tmp/foo
date    : obtenir la date
at      : exécution différée
        : at now + 1O minutes echo "il est l'or"
mkdir   : créer un répertoire
        : mkdir -p /tmp/phil/src
cd      : changement de répertoire
pwd     : identifier le répertoire courant
rm      : effacer un fichier.
        : rm -f *.o
mv      : déplacer un fichier
rename  : renommer des fichiers
        : rename  .txt .old  *.txt   
cp      : copier un fichier, un répertoire
        : cp -ri /tmp/src /tmp/dst
man     : voir le manuel, the command !
        : man man       
        : man 3 printf
uname   : information sur le système
lscpu   : information sur l'architecture  
xterm   : un terminal X de plus
gedit   : éditer un fichier, idéal pour débuter
        : le plus souvent lancé en tache de fond 
        : gedit prog.c &
        : terminal + gedit + vi :  basta cosi ! 
bg      : lancer un processus en tache de fond
        : prog.exe ctrl-z.
        : bg 
emacs   : éditer un fichier, l'éditeur préferé des mangeurs 
        : de pizzas atomiques, à essayer donc.
        : strace -o /tmp/emacs.txt -e trace=open emacs 
        : grep -c open /tmp/emacs.txt
        : 447 ( houf ! )
vi      : The number of the beast : vi vi vi.
        : éditeur des mangeurs de pizzas napolitaines ! 
        : strace -o /tmp/vi.txt -e trace=open vi
        : grep -c open /tmp/vi.txt
        : 22  ( raisonable ? )
kyle    : beurck ?!
indent  : mettre en forme une source C
        : indent -kr prog.c
history : historique des commandes
        : !!
lpr     : imprime in fichier
        : lpr  source.c
a2ps    : idem
        : a2ps *.c
cat     : afficher un fichier
        : cat /proc/cpuinfo
xclip   : lire, écrire dans le clipboard X11
        : xclip -o | sed 's/<[^<>]*>//g'
more    : less : visiter un fichier.
        : ./prog.exe | more
        : / recherche un motif 
ls      : lister des fichiers, les repertoires...
        : ls -tild *
wc      : compter les caractères, les lignes...
        : wc -l cpu /proc/cpu
grep    : trouver un motif dans un fichier
        : grep -ir pi --include=*.h /usr
        : grep -c proc /proc/cpuinfo
sort    : trier les lignes d'un fichier
        : sort -h data.txt
uniq    : comparer des lignes
        : sort data.txt | uniq -c | sort -d
find    : trouver un fichier
        : find /usr -name "math.h"
        : find /usr -name "math.h" --exec grep -i pi {} \;
file    : obtenir des informations sur un fichier
        : file pl.png
          pl.png: PNG image data, 32 x 22, 8-bit/color RGBA, non-interlaced
convert : conversion d'images
          convert  src.png -resize 32x32 dest.png
gnuplot : faire des graphiques
xfig    : faire des figures
dot     : faire des graphes
script  : echo des in/output vers un fichier
        : script -a montravail.txt
wget    : telecharger des fichiers via http
tar     : manipuler une archive
	: tar cvf big.tar big
	: tar zxvf big.tar.gz
ssh     : se loguer sur une machine distante, tunnel etc...
        : ssh -f -N -L 2222:target.univ-tln.fr:22 michko@gate.univ-tln.fr
        : ssh -p 2222 michko@localhost
        : ssh-keygen, ssh-copy-id ...
scp     : copier des fichiers vers un hote distant.
        : scp -P 2222 foo.txt localhost:
gcc     : compiler une source
        : gcc -Wall -g prog.c -o prog.exe
        : gcc -O2 prog.c -o fast.texe
aspell  : véfier l'orthographe
        : aspell -d en --mode=tex -c  tips.tex
tex     : compiler des textes
	: tex      foo.tex      ( pure teX )
	: pdflatex bar.tex 
	: bibtex   bar
gdb     : debugguer un exécutable
        : gdb prog.exe
        : gdp [pid]    pour attraper un processus en cours
make    : compilation automatique.
ar      : manipule les archives.
touch   : dater (créer) un fichier.
cut     : extraire des champs.
        : cut -d ':' -f1,3 /etc/passwd
split   : éclater un fichier
xargs   : argumenter avec les lignes
        : cat fic.txt | xargs echo -e
time    : (/urs/bin/time ) obtenir un rapport d'exécution, 
        : temps de calculs et bien d'autres choses.
        : /usr/bin/time -a -o mesures.txt prog.exe
bc      : calculatrice
        : echo 'l(2^20) / l(10)' | bc  -l; 
        : -> 6.02059991327962390429
ps      : lister les processus en cours.
        : ps -e -opid,cmd
top     : processus en cours, idéal pour voir bouillir
        : la marmite, la consommation mémoire.
watch   : lancer la commande à intervalle régulier;
        : watch who
kill    : envoyer un signal
        : kill -9 31415
killall : envoyer un signal
        : killall sshd
hexdump : scruter un fichier, pour detecter les bad chars !
        : hexdump -c prog.c
strings : affichier les chaînes dans un binaire.
gprof   : profiler un exécutable.
valgrind: détecter les fuites mémoires

join    : joindre des fichiers.
paste   : un cat à la verticale !

nl      : numeroter les lignes.
diff    : comparer des fichiers
sed     : filtrer un fichier.
        : sed 's/[ ][ ]*/\t/g' fic.txt
        : sed 's/\([0-9]*\)[^0-9]*\([O-9]*\)/\2-\1/'
awk     : filtrer les colonnes des fichiers 
        : awk -F: '/mich/ {print $1,$6}'  /etc/passwd
rsync   : synchroniser des fichiers
        : rsync -az -e ssh --delete ~/cyberie/*.html  ou812:
git     : gestion de version
	: git clone git@mapix.hd.free.fr
unison  : synchronisation bi-directionnelle
          unison /tmp/local /tmp/distant
nohup   : quand il faut calculer jusquau bout de la nuit, voir plus ! 
        : nohup prog.exe &
        : ps -C prog.exe 
        : tail nohup.out
        : exit
disown  : quand on oublie d'utiliser nohup !!
        : prog.exe : ctrl-z 
        : bg ; disown ; exit
objdump : extraire des informations d'un fichier objet
        : objdump -S a.out
nm      : liste les symboles d'un fichier objet
ltrace  : trace les appels aux librairies dynamiques
        : ltrace factor 123456789
strace  : trace les appels systèmes.
        : strace wget langevin.univ-tln.fr

Les mots clefs du langage C.

C is a quirky, imperfect, but it is incredibly successful language.
-- Dennis Ritchie
[skip ]

typedef,
struct, union, enum,
auto , register, static, extern 
const, volatile 
signed, unsigned
char, int, short, long
double, float
for, while, do, continue, break, 
if, else, enum, float, for,
switch, case, default, 
goto, return,
Finalement, le langage C, c'est pas grand-chose !
Remarque : sizeof n'est pas une fonction, mais un opérateur

int x;
x = sizeof x;
Il ne faut pas confondre le langage C et la bibliothèque standard glibc qui contient des fonctions utiles pour le développeur. Par programmation en Langage C sous unix, j'entend : langage C, biblothèques (glibc,...) et commandes externes associées présentes sur un système linux (gcc,gdb,...)

Les Types scalaires

Why do programmers always mix up Halloween and Christmas?
A: Because Oct 31 == Dec 25!
[skip ]
Depuis sa création, le langage C propose 6 types entiers, trois types flottants, un type enuméré et cinq constructeurs : tableaux, fonctions, pointeurs, structures et union.

Le C-99 prévoit une plus large gamme de types : des entiers de tailles spécifiés, des caractères étendus, le type boolean, le type complexe... Tout n'est pas encore intégré dans gcc, voir le C99 status.

taille en bits min max
char 8 -128  127
unsigned char 8 0 255
short 16 -32768 32767
unsigned short 16 0 65535
long 32 -2 147 483 648 2 147 483 647
unsigned long 32 0 4 294 967 296
long long 64

float 32 (7 chiffres décimaux) 0.29 E-38 1.70E+38
double 64 (15 chiffres) 0.56 E-308 0.90 E+308
long double



Les tailles de ces types varient suivant les machines cibles. Vous pouvez obtenir  une certitude en utilisant sizeof :
printf( "%d %d %d", sizeof (char), sizeof (int), sizeof(long) );

sizeof s'applique aussi aux variables mais ce n'est pas une fonction.

    int  t[10];
    char* p;
    // sizeof t    --> 10 * sizeof(int)
    // sizeof p    --> 4 :-(      --> 8 :-)
    // sizeof *p   --> 1

Les  limitations sur la valeur d'une variable d'un type est source d'erreur, typiquement boucles infinies par effet de bord.

Je me rappelle de l'époque du passage à 32 bits, la plupart des gens étaient vraiment enthousiastes, le passage à 64 bits s'est presque fait en silence... Les temps changent !

Sur mon bureau, une petite machine à 300 euros :
$ uname -m
x86_64

$ free -m
             total       used       free     shared    buffers     cached
Mem:          1756       1731         24          0        177        453
-/+ buffers/cache:       1100        655
Swap:         3519        106       3413

$ grep -c process /proc/cpuinfo 
4
et dire que c'est du bas de gamme...

limits.h

Avec le C, vous pouvez dépasser les bornes des limites mais c'est rarement sans incident !
-- pl
[skip ]

Les limites sonts déclarées par des constantes dans le fichier entête limits.h.


$ grep MAX /usr/include/limits.h
#define MB_LEN_MAX	16
#  define SCHAR_MAX	127
#  define UCHAR_MAX	255
#   define CHAR_MAX	UCHAR_MAX
#   define CHAR_MAX	SCHAR_MAX
#  define SHRT_MAX	32767
#  define USHRT_MAX	65535
#  define INT_MIN	(-INT_MAX - 1)
#  define INT_MAX	2147483647
#  define UINT_MAX	4294967295U
#   define LONG_MAX	9223372036854775807L
#   define LONG_MAX	2147483647L
#  define LONG_MIN	(-LONG_MAX - 1L)
#   define ULONG_MAX	18446744073709551615UL
#   define ULONG_MAX	4294967295UL
#   define LLONG_MAX	9223372036854775807LL
#   define LLONG_MIN	(-LLONG_MAX - 1LL)
#   define ULLONG_MAX	18446744073709551615ULL
   LLONG_MAX, and ULLONG_MAX.  Instead only the values gcc defined for
#  define LLONG_MIN	(-LLONG_MAX-1)
# ifndef LLONG_MAX
#  define LLONG_MAX	__LONG_LONG_MAX__
# ifndef ULLONG_MAX
#  define ULLONG_MAX	(LLONG_MAX * 2ULL + 1)

Affectation

variable := type + adresse !
-- Pavle Michko
[skip ] L'affectation permet de modifier les variables, pour affecter le contenu de l'expression exp à la variable x, j'ecris tout simplement

x = exp
L'expression doit avoir le meme type que la variable x, ou de type compatible. Par exemple, les affectations suivantes sont valides :

char car;
int n;
float pi;
pi = 3.1415;
n  = pi;
// ici, n vaut 3

c = n * 20 + pi;
// ici, c vaut 63

c = 200 + c;
// ici, c vaut 7
Les flottants sont convertis en entiers (partie entière). L'affectation est un opérateur associatif à froite, et le langage C permet d'ecrire des trucs bizarres du genre :

    x = y = z;

Le débutant  sait  rapidement faire la différence entre égalité == ( 2 fois = ) et affectation =, mais copier/coller et autres erreurs de frappe conduisent à écrire des instructions alternatives douteuses :


     if ( x = y ) printf("egaux");
     else printf("differents");
L'echo de cette instruction  est "egaux" sauf si y est nul.

warning: suggest parentheses around assignment used as truth value.

typedef

Algorithms + Data Structures = Programs
--Niklaus Wirth
[skip ] En C, on déclare un type comme une variable : il ne faut pas se géner sur l'emploi des typedef pour introduire de la sémantique dans la source !! En effet, avec un petit effort, une code C peut passer pour un algorithme.
Par exemple,
#define dimen 3
typedef double vecteur[ dimen ];

double pscalaire( vecteur x, vecteur y );
Une liste chaînée de ce type ?
typedef struct {
  vecteur vec;
  struct _ll_ * next;
} enrvecteur, *lstvecteur;

Les flux std{in,out,err}


     do  c = getchar(); while ( isdigit(c) ); 
     // oops
     unget(stdin, c);  
The trouble with programmers is that you can never tell what a programmer is doing until it’s too late
-- Seymour Cray
[skip ] La compilation d'un programme C donne un exécutable, nommé par défaut a.out. L'option -o du compilateur gcc permet de donner un autre nom.
  gcc -o prog prog.c
Dans tous les cas sont associés un flux rentrant , un flux sortant et un flux de sortie des erreurs. Une source C référence ces flux respectivement par : stdin (standard input), stdout (standard output) et stderr (standard error). La plupart des opérations d'entrée/sortie opèrent  ( ou peuvent opérer) sur ces deux flux; c'est le cas des  opérations : getchar, putchar, puts, printf, gets, scanf, etc... Une bonne habitude à prendre : envoyer les messages d'erreurs sur le flux stderr:
     fprintf( stderr, "warning : undefined object");
Les flux standards correspondront aux fichiers standard du futur processus : 0, 1 et 2. Les puissants mécanismes de tube et redirection de fichiers du système vous permettent de combiner  efficacement les entrées sorties de vos programmes.
      $ prog  < source 
      # execution de prog avec le fichier source comme entrée

      $ prog  > destin 
      # execution de prog avec creation d'un fichier destin pour sortie

      $ prog >> destin 
      # la sortie de prog est accrochée à la fin du fichier destin

      $ cat prog.c | prog | sort > destin 
      # le fichier source est placé sur la sortie standard de cat
      # qui devient le flux rentrant de prog dont la sortie pipée
      # vers sort qui la trie et le resultat est envoye dans le fichier destin.
      
Par exemple, si le programme somme ajoute tous les nombres lus sur l'entrée standard et dépose le résultat sur la sortie standard alors :
     $ echo '1 2 3' | somme
     6

put & get

while ( putchar( getchar() ) ) ; //loops like a poor cat !
-- pl
[skip ]

     int getchar( void )        int fgetc( FILE *src)
     int putchar( int car )     int fputc(int c, FILE *dst);
Les fonctions pour lire et écrire un caractère sur les entrées/sorties standard, ou bien sur un flux. D'une manière générale, le "f" en premier signifie qu'une fonction requiert un flux : fputc, fgetc,fscanf,fgets...
Erreur courante, est de penser que les arguments et résultats de ces fonctions sont des char, non il s'agit bien d'entiers.
Lorsque le marqueur de fin de fichier ctrl-d est rencontré, getchar() renvoie un entier signé sur égal à la constante EOF, et donc pour pouvoir détecter la fin de fichier il est indispensable d'utiliser un type adéquate, le code qui suit est erroné :

      unsigned char car;
      do 
        car = getchar();
      while ( car != EOF );
Plus rarement, il est utile de remettre un caractère dans le flux :

      int ungetc(int c, FILE *src);

      int car;
      do 
        car = getchar();
      while ( ! isdigit(car) );
      //oups !!!
      ungetc( car, stdin );
      scanf("%d", &z);
Et des fonctions lire et écrire des chaines
       char * fgets(char *s, int size, FILE *stream);
       char *  gets( char *s ); 
       int fputs(const char *s, FILE *stream);
       int  puts(const char *s);
Erreur classique, la taille de la zone mémoire pour recevoir la chaine est insuffisante suite à une mauvaise allocation ou déclaration. En particulier, gets() ne peut pas être recommandée pour coder dans la vrai vie !

Les [fs][n]printf

Hey Roxan, you dont have to printf this string... just puts it !
-- police
[skip ] L'instruction printf : comprendre affichage formaté, vous permet d'imprimer vos données.  La section 3 des pages du manuel décrit avec précision l'ensemble des possibilités de cette puissante (si si)fonction, nous nous contenterons de quelques exemples.

    int   printf( const char * format, ...);
    int  fprintf( const char * format, ...); 
    int  sprintf(char *str, const char *format, ...);
    int snprintf(char *str, size_t size, const char *format, ...);
Notons que les fonctions retournent le nombre d'octets écrits. Notons aussi que le prototype permet un nombre variable d'arguments, ces fonctions sont dites variadiques, cf. les va_list.

    char fn[64];
    snprintf( fn, 64, "ouput-%d.txt", getpid() );
    src = fopen( fn, "r");
    if ( ! src )  {
         fprintf( stderr, "no file!");
         exit(1);
    }

Erreurs : oublis de quelques paramètres
warning: too many arguments for format
ou bien les types sont incompatibles
warning: unsigned int format, double arg (arg 2).

   #include 
   int main(void)
   {
   int z;
   float pi;
   char  chaine[12] = "Hello";
   z = 17;
   printf("\nEcrire un entier :");
   printf("\nen decimal %d, en octal %o, en hexa %x %X",z,z,z,z,z);
   
   //Ecrire un entier : decimal 17, en octal 21, en hexa 11 11
   
   printf("\nSur 4, 3, 2,1  colonnes |%4d|%3d|%2d|%1d|",z,z,z,z);
   
   //Sur 4, 3, 2,1  colonnes |  17| 17|17|17|
   
   z = -1;
   printf("\nnon signe %u", z);
   
   //non signe 4294967295
   
   z = 65;
   printf("\n%d est le code ASCII de %c", z, z);
   
   //65 est le code ASCII de A
   
   pi = 3.1415;
   printf("\nEcrire un rationnel :");
   printf("\nNotation scientifique %e du nombre  %f", pi, pi);
   printf("\navec deux chiffres apres la virgule %f.2", pi);
   
   //Ecrire un rationnel :
   //Notation scientifique 3.141500e+00 du nombre  3.141500
   //avec deux chiffres apres la virgule 3.141500.2
               
   printf("\nEcrire une chaine :");
   printf("%s world !\n", chaine);
   
   //Ecrire une chaine :Hello world !
   
   printf("\nEcrire une adresse :");
   printf("\nQuelques adresses  %p %p %p", chaine, &(chaine[0]), &(chaine[1]));
   
   //Ecrire une adresse :
   //Quelques adresses  0xeffff8a8 0xeffff8a8 0xeffff8a9
   
   printf("\n");
   }

Les [fs][n]scanf

Sometimes, it is better to stay home in bed on Monday, than to spend the whole week debugging the code that was written on Monday.
-- Christopher Thompson
[skip ] L'opération de lecture scanf est encore plus puissante que l'opération duale printf. Comme son nom l'indique scanf permet de scanner les données suivant un format :

  int scanf(const char * format,arg1, arg2, ?)
  int fcanf(FILE*src, const char *format,arg1, arg2, ?)
  int sscanf(char * str, const char *format, arg1, arg2, ?)
où les argi sont des adresses.

La procédure scanf tente de lire une donnée au format spécifié, renvoie 0 si c'est impossible, sinon un entier positif correspondant au nombre de valeurs lues. Dans ce cas les caractères sont absorbés. Par défaut les espaces (blancs, tabulation, et saut de ligne) sont des séparateurs. La plupart du temps scanf laisse ces séparateurs sur le flux stdin ce qui explique par exemple le mauvais fonctionnement de :


  while ( car != 'N') {
    puts("\nUne chaine :");
    scanf("%s", str);
    printf("\n%s contient %d caractères",str,strlen(str) );
    puts("\nEncore une fois ?");
    car = getchar();
  
   //car ne sera jamais égal au dernier caractère saisi ! 
   //mais toujours au dernier caractèrelaissé par scanf sur le flux
   //stdin. Vérifions
  
  printf("%d", car);
  
  //affiche 12
  
  car = getchar();
  
  //maintenant c'est ok.
  
  }
Terminons par un exemple assez complet. Supposons que le programme C ci-dessous s'appelle scan.c. Le résultat de la commande :
 gcc scan.c
 echo '123 azerty 1789 a1bc34&56a' | a.out
 
 Un nombre au format decimal
 Une chaine et un nombre :
 Ta chaine azerty, ton nombre 1789 
 et maintenant scannons...

 [lettres a]
 [chiffres 1]
 [lettres bc]
 [chiffres 34][...]
 [chiffres 56]
 [lettres a]

  #include 
  #include 
  int main( void) { 
    int z;
    char str[100];
    puts("\nUn nombre au format decimal :");
    if ( ! scanf("%d", &z) ) 
       printf("Pas de nombre a lire !");

    puts("Une chaine et un nombre :");
    scanf("%s %d", str, &z);
    printf("\nTa chaine %s, ton nombre %d",str,z);
    
    puts("\net maintenant scannons...");
    while ( 1 ) {
     if ( scanf("%[0-9]*",str)) 
          printf("\n[chiffres%12s]", str);
     else if ( scanf("%[a-z]*", str))printf("\n[lettres %12s]", str);
          else { scanf("%[^0-9a-z]*", str);puts("[...]");}
  }   
 

  flux binaire

There are only 10 types of people in the world: Those who understand binary, and those who don't !
-- Ben Harry
[skip ] Les fonctions

    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
permettent la lecture et l'écriture dans les fichiers binaires.

int main(int argc, char* argv[])
{
    FILE* src, *dst;
    int   index = 0;
    short buffer[1024];
    src = fopen ( argv[1], "r");
    dst = fopen ( argv[2], "w" );
   
    while ( EOF != fscanf( src ,"%hd", &buffer[index++]) );
    
    fwrite( buffer, sizeof (int ), index, dst );

    fclose( src );
    fclose( dst );
return 0;
}
$ for x in {1..64}; do  echo $x; done > fic.txt
$ hexdump -d -n12 fic.bin

$ hexdump -d -n8  fic.txt
0000000   02609   02610   02611   02612      
0000008

$ hexdump -c -n8  fic.txt
0000000   1  \n   2  \n   3  \n   4  \n      
0000008

./a.out fic.txt fic.bin

$ hexdump -c -n8  fic.bin
0000000 001  \0 002  \0 003  \0 004  \0      
0000008
$ hexdump -d -n8  fic.bin
0000000   00001   00002   00003   00004      
0000008

$ ls -alt fic.*
-rw-rw-r--. 1 drmichko drmichko 260  3 avril 11:23 fic.bin
-rw-rw-r--. 1 drmichko drmichko 183  3 avril 11:21 fic.txt


  perror

I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone.
-- Bjarne Stroustrup
[skip ] La fonction perror() affiche un message sur la sortie d'erreur standard, décrivant la dernière erreur rencontrée durant un appel système ou une fonction de bibliothèque.
 
#include 
#include 
/*
  (1:2)  (1:4)
*/
int main(int argc, char* argv[])
{
    FILE* src;
    int p = 0, s = 0, z;
    src = fopen ( argv[1], "r");
    if ( ! src ) {
        perror("main");
        exit( 1 );
    }
    perror("");
    while ( ! feof( src ) ) {
      switch ( fscanf(src, "(%d:%d)", &z, &z) ) {
         case 2 : p++; break;
         case 1 : s++; break;
         case 0 : fgetc( src );
      }
    }
    fclose( src );

    printf("\n%d paires,  %d entiers.\n", p, s );
return 0;
}
$ ./a.out xxx
main: No such file or directory
$ touch xxx
$ chmod u-r xxx
$ ./a.out xxx
main: Permission denied
./a.out p.c
Success

2 paires,  1 entiers.

Enumération.

Evolution of the C programmer:
0 months to 1 month: complete beginner
1 month to 1 year: incomplete beginner
1 year to 2 years: acolyte
2 years to 3 years: adept
3 years to 8 years: expert
at 8 years: discovers comp.lang.c
8 years+: buggrit, back to beginner again !"
— Richard Heathfield.
[skip ] L'instruction
 

enum chiffre {zorro, un, deux, trois, quatre, cinq, six, sept, huit, neuf};
à pour effet la définition du type enum chiffre et 10 constantes de ce type dont les valeurs seront respectivement 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. L'identificateur chiffre est optionnel.
Erreurs : ne pas oublier le préfixe enum lors des déclations.
Comme dans le cas des structures, chiffre c;n'est pas légale. Il faut écrire :
 enum chiffre c;
On peut définir un type sifr par :
 
typedef enum chiffre sifr;
et déclarer :

sifr c;

 Les caractères

If it wasn't for C, we'd be writing programs in BASI, PASAL, and OBOL.
[skip ] Les caractères sont cod\'es sur des octets ( char ). Le codage ascii établit depuis fort longtemps la correspondance entre les entiers de 7-bits et les caractères :
       $ man ascii

       Pour plus de commodité, nous vous fournissons des tables plus compactes
       en hexadécimal et en décimal.

          2 3 4 5 6 7       30 40 50 60 70 80 90 100 110 120
        -------------      ---------------------------------
       0:   0 @ P ` p     0:    (  2  <  F  P  Z  d   n   x
       1: ! 1 A Q a q     1:    )  3  =  G  Q  [  e   o   y
       2: " 2 B R b r     2:    *  4  >  H  R  \  f   p   z
       3: # 3 C S c s     3: !  +  5  ?  I  S  ]  g   q   {
       4: $ 4 D T d t     4: "  ,  6  @  J  T  ^  h   r   |
       5: % 5 E U e u     5: #  -  7  A  K  U  _  i   s   }
       6: & 6 F V f v     6: $  .  8  B  L  V  `  j   t   ~
       7: ´ 7 G W g w     7: %  /  9  C  M  W  a  k   u  DEL
       8: ( 8 H X h x     8: &  0  :  D  N  X  b  l   v
       9: ) 9 I Y i y     9: ´  1  ;  E  O  Y  c  m   w
       A: * : J Z j z
       B: + ; K [ k {
       C: , < L \ l |
       D: - = M ] m }
       E: . > N ^ n ~
       F: / ? O _ o DEL
Pour les travaux sur les caracères, pensez à utiliser les fonctions prédéfinies du ctype.h
  $ man isalpha

       #include 

       int isalnum(int c);
       int isalpha(int c);
       int isascii(int c);
       int isblank(int c);
       int iscntrl(int c);
       int isdigit(int c);
       int isgraph(int c);
       int islower(int c);
       int isprint(int c);
       int ispunct(int c);
       int isspace(int c);
       int isupper(int c);
       int isxdigit(int c);

  Les chaines

One of the best programmers I ever hired had only a High School degree; he's produced a lot of great software, has his own news group, and made enough in stock options to buy his own nightclub.
--Peter Norvig
[skip ] Les chaines de caractères permettent la manipulation des mots. En langage C une chaine est une zone mémoire contenant impérativevement le caractère de code ascii 0 qui indique alors la fin du mot. Une chaine constante s'écrit entre deux guillemets.

    char    *x = "hello";
    char  y[6] = {'w','o','r','l','d'};
Attention, lors de manipulation de chaine, il faut veiller à ce que la taille des tableaux soient suffisantes. D'une manière générale, il est préférable d'utiliser des pointeurs char * plutôt que des tableaux char t[]. Un fait qui illustré dans les fonctions suivantes :

    void  SansEspace( char dst [], char src [] ) 
    { 
     int i, j; 
     j = 0; 
     i = -1 ; 
     do { i = i + 1; 
       if ( ! isspace( src[i] )  ) dst[j++] = src[i]; 
     }  while ( src[i] ); 
    } 

    void  SansEspace( char *dst , char *src  ) 
    { 
     do 
       if ( ! isspace( *src )  ) dst++ = *src;  
     while ( *src++); 
    } 
Les fonctions usuelles du string.h :
    int strlen( char * src)     : retourne la longueur de la chaine src 
    int strcpy( char *dst, char* src)        : copie de src dans dst 
    int strncpy(char *dst, char* src, int n) : copie au plus n caractères de src vers dst 
    int strdup(char *dst, char* src)         : duplication : allocation + copie de src dans dst 
    int strndup(char *dst, char* src, int n) :
    int strcat( char *dst,  char* src)       : concaténation de src à dst 
    int strcmp(char *s, char* t)             : comparaison de s et t 
    int strncmp(char *s, char *t, int n)     : comparaison de s et t sur au plus n caractères 
    int atoi(char *s)           : conversion d'une chaine en entier 
    long int atol(char *s)      : conversion d'une chaine en un entier long

Tableaux

[ "hip", "hip" ]
(hip hip array!)
[skip ] Les variables de type tableaux représentent zones mémoires indexées. Etant donné un type objet les déclarations :

     objet t[16];
     objet m[4][4];
     objet x[2][2][2][2];
définissent trois tableaux de 16 objets. Quelle que soit la dimension, un tableau définit un espace mémoire compact :

   int t[3][3];
   int i, j, v=0;
   int *p;
   for( i = 0; i < 3; i++)
   for( j = 0; j < 3; j++)
     t[i][j] = v++;
   p = & t ;
   for( i = 0; i < 9; i++, p++)
      printf(" %d", *p); 
   $ ./a.out 
    0 1 2 3 4 5 6 7 8
Un tableau peut être initialisé par un tableau constant :

   int z[1024] = { 0 };
   int t[2][3] = {
       {1, 2, 3},
       {4, 5, 6}
   };

Il est possible de créer des tableaux dynamiques sur la pile :

   void proc( int k, int n)
   {
     int t[k][n];
     ...
   }
Mais pour des raisons assez évidentes :

   void proc( int n)
   {
   int t[n] = { 0 };
   }


   $ gcc -Wall x.c 
   x.c: In function ‘proc’:
   x.c:3: erreur: un objet de taille variable peut ne pas être initialisé
   x.c:3: attention : éléments en excès dans l'initialisation de tableau
   x.c:3: attention : (near initialization for ‘t’)
   x.c:3: attention : unused variable ‘t’
Il faudra faire une boucle d'initialisation ou encore utiliser une fonction memset. Enfin, le prototype

   void proc( int t[] )
   {

   }

définit une fonction qui acceptera tous les tableaux à une dimension, ainsi que les pointeurs vers les entiers.

Structures

Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves.
--Alan Kay
[skip ] La déclaration

     struct fiche {
       int   age;
       char* name;
     } x;
déclare une structure fiche et une variable à deux champs.

     struct fiche select( struct fiche x, struct fiche y )
     {
          if ( x.age < y.age ) 
             return x;
           return y;
     }
On peut introduire plus de sémantique dans le code en déclarant un nouveau type :

     typedef struct  {
       int   age;
       char* name;
     } fiche;
déclare un type fiche.

     fiche select( fiche x, fiche y )
     {
          if ( x.age < y.age ) 
             return x;
           return y;
     }

union

The evolution of languages: FORTRAN is a non-typed language. C is a weakly typed language. Ada is a strongly typed language. C++ is a strongly hyped language.
-- Ron Sercely
[skip ] Dans certains cas, nous pouvons avoir besoin de manipuler les champs d'une structure de différentes façons en fonction d'un contexte.

     union  {
       long long val;
       char byte[8];
     } x;
déclare une variable x avec deux champs superposés, la taille de x sera probablement de 8 octets, et non pas 16, car les champs val et byte sont superposés.

    x.val = 1 + (2<<8) + (3<<16) + (4<<24);
    if ( x.byte[0] == 1 )
       puts('little endian');
    else
       puts('big endian');

pointeur

& Joe...
-- Jimi
[skip ] Les variables de type pointeurs représentent des adresses. Etant donné un type objet, une variable x de type objet, l'adresse de x est de type objet*, ainis la déclaration :

     objet *p;
     objet t[16];
défini un pointeur vers un objet. L'arithmétique des pointeurs est faite pour rendre compatible les notions de tableau et de pointeur :

     p = & t[0];
     p++;
     if ( *p != t[1] ) {
        puts("Vous avez découvert un sérieux bug!");
        exit( 1 );
     }
L'opérateur -> permet d'accéder aux champs des structures pointées

    typedef struct ptr {
        int seg, ofs;
    }
    void inc( struct ptr * a )
    {
        a->ofs ++;
    }
Une allocation permet d'obtenir un tableau de taille dynamique dont la taille est déterminée au cours de l'exécution :
       objet * getobjet( int n ) {
         objet *r;
         r = calloc( n, sizeof(objet) );
         if ( r ) return r;
         perror("getobjet");
         exit(1);
       }
Depuis C90, le transtypage :
 r = ( objet*) calloc( n, sizeof(objet) );
est devenu optionnel.
Notons qu'il n'est vraiment pas recommandé de remplacer sizeof(objet) par une valeur inumérique : la taille de l'objet peut changer d'une artcitecture à l'autre, elle dépend aussi de certaines option de compilation.
Avec :

    typedef struct {
      int  x;
      char y;
    } objet;

mon gcc produit un objet de taille 8. L'attribut packed permet de contrôler la taille des types. Notons que les tableaux à deux dimensions gagnent à être vus comme des tableaux de lignes pour faciliter les permuations des lignes.

     int** mat;
     mat = ( int**) calloc( k, sizeof(int*) );
     for( i = 0; i < k; i++)
        mat[i] = (int*) calloc( n, sizeof(int) );
Enfin, une récursion permet de construire des listes chainées :

     typedef struct _list_ {
       int   val;
       struct _list_ * next, * pred;
     } enr, *list;

fonction

  int getrandprime( void ) {
  return 4;  // choosen by fair dice roll
             // an odd prime ( to be checked ). 
}

-- an inventive student.
[skip ] Le type fonction est un peu déroutant de prime abord, mais la description d'un pointeur de fonction retournant un entier et prenant deux arguments entiers :

     int( * )(int *, int *)
suit les règles habituelles.
    int  f;           -- adresse -->  int  *f;
    int  f (int x);   -- adresse -->  int  (*f) (int x );
    int* f( int x);   -- adresse -->  int* (*f) (int x);
En cas d'oubli, pensez à gdb :

Reading symbols from /home/pl/a.out...done.
(gdb) l
1	int id( int i )
2	{ return i;   }
3	int main()  { }
(gdb) whatis id
type = int (int)
(gdb) whatis &id
type = int (*)(int)
(gdb) call id(7)
You can't do that without a process to debug.
(gdb) b main
Breakpoint 1 at 0x804839f: file x.c, line 3.
(gdb) run
Starting program: /home/pl/a.out 

Breakpoint 1, main () at x.c:3
3	int main()  { }
Missing separate debuginfos, use: debuginfo-install glibc-2.11.2-3.i686
(gdb) call id(7)
$1 = 7
(gdb) call &id(7)
Attempt to take address of value not located in memory.
(gdb) call (&id)(7)
$2 = 7

int id( int x) { return x ;}

int sigma( int (*f) (int), int n)
{ int res = 0;
  while ( --n >= 0) res += f(n);
  return res;
}
Le lecteur vérifiera que sigma(id, 100) retourne la bien 4950.
$ echo 's=0;for(i=0; i < 100;i++) {s+=i}; s;' | bc
4950
Notons tout de même qu'une définition de type permet d'y voir plus clair :
    typedef double reel;
    typedef reel fonction( reel x );
    reel solve( fonction f, real a, reel b)
    {
       ... bla
       ... bla
       y = f( a + b );
       ... bla
    }
    fonction *test = sin;
    r = solve ( test, a, b );
    test( r );
L'usage n'est pas trés courant, citons l'exemple du tri rapide :
$ man qsort

NOM
       qsort - Trier une table

SYNOPSIS
       #include 

       void qsort(void *base, size_t nmemb, size_t size,
     int(*compar)(const void *, const void *));

Variadisme

A good system can't have a weak command language.
-- Alan Perlis
[skip ] Les fonctions variadiques s'appliquent à un nombre de paramètres indéfinis. Toutefois, ce nombre est connu à la compilation et doit être passé à la fonction comme dans l'exemple qui suit, ou bien de façon implicite par un format comme dans printf. Le traitement des arguments (va_list) se fait par l'usage de quatres macros, consulter le manuel man stdarg.h

#include 

int sum(int argc, ...){

    va_list ap;
    int  res = 0;
    va_start(ap, argc);
    while ( argc--)
          res += va_arg(ap, int );
    va_end(ap);
    return res;
}

int main( void ) {
 return sum( 3, 1, 2, 3 );
}
    $ ./a.out
    $ echo $?
      6

Variable constante

One man's constant is another man's variable.
-- Alan Perlis
[skip ] Il s'agit de déclarer des variables ouverte en lecture, fermées en écriture. Il s'agit plus d'un garde-fou (important) pour le programmeur, ou d'une information (précieuse) pour l'utilisateur de code. Exemple : la déclaration size_t strlen(const char *s) nous informe que la fonction strlen ne modifie pas les s[i], c'est utile pour l'utilisateur de cette fonction, et une protection si strlen était en développement.

    const int age = 50; // difficile a croire !
    #define age 50  // une constante 50 pas d'existence dans le code.

    age++;
Le code est incompilable :
In function ‘main’:
erreur: increment of read-only variable ‘age’
Garde-fou disais-je ? En effet,

    int *x = & age; 
    (*x)++;
    if ( *x != age ) printf("happy birthday !");
Une petite méditation sera nécessaire consternant les déclarations :

    const char  *s;    // les s[i] sont proteges
    char * const s;    // s est protege
    const char * const s; 

 Variable extern

Software is getting slower more rapidly than hardware becomes faster.
-- Nicolas Wirth
[skip ]

    extern int z;
nous signalons que la variable z de type int est dans un autre module. Dès lors, le compilateur peut vérifier la sémantique du code et produire du code objet.

register

[skip ]

    register int z;
nous suggérons au compilateur de placer la variable locale z dans un registre. Notons bien que ceci est rarement une bonne idée !
int sum( int l)
{
#ifdef REGISTER
register int r;
#else
  int r;
#endif
int s;
for( r = 0; r < l; r++)
  s += r;
return s;
}
$ gcc -Wall -c  z.c
$ objdump -S z.o
00000000 :
   0:	55      	push   %ebp
   1:	89 e5   	mov    %esp,%ebp
   3:	83 ec 10             	sub    $0x10,%esp
   6:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%ebp)
   d:	eb 0a   	jmp    19 
   f:	8b 45 fc             	mov    -0x4(%ebp),%eax
  12:	01 45 f8             	add    %eax,-0x8(%ebp)
  15:	83 45 fc 01          	addl   $0x1,-0x4(%ebp)
  19:	8b 45 fc             	mov    -0x4(%ebp),%eax
  1c:	3b 45 08             	cmp    0x8(%ebp),%eax
  1f:	7c ee   	jl     f 
  21:	8b 45 f8             	mov    -0x8(%ebp),%eax
  24:	c9      	leave  
  25:	c3      	ret    
Les variables r et s sont placées sur la pile.
$ gcc -Wall -DREGISTER -c  z.c
$ objdump -S z.o 

00000000 :
   0:	55      	push   %ebp
   1:	89 e5   	mov    %esp,%ebp
   3:	53      	push   %ebx
   4:	83 ec 10             	sub    $0x10,%esp
   7:	bb 00 00 00 00       	mov    $0x0,%ebx
   c:	eb 06   	jmp    14 
   e:	01 5d f8             	add    %ebx,-0x8(%ebp)
  11:	83 c3 01             	add    $0x1,%ebx
  14:	3b 5d 08             	cmp    0x8(%ebp),%ebx
  17:	7c f5   	jl     e 
  19:	8b 45 f8             	mov    -0x8(%ebp),%eax
  1c:	83 c4 10             	add    $0x10,%esp
  1f:	5b      	pop    %ebx
  20:	5d      	pop    %ebp
  21:	c3      	ret    
La variable r est placée dans le registre bx.

Variable statique

[skip ] Le qualifieur agit de façons différentes sur les variables locales et globales. Pour une variable locale, la déclaration
    static int z;
nous demandons au compilateur de placer la variable z dans le segment de données, pas sur la pile. La port\'e de l'objet demeure au niveau du bloc, contrairement aux apparences, elle ne sera initialisée qu'une fois.

    void proc( int k )
    {
     static int z=13;
     int x;
     if ( k ==  0 ) return;
     printf("\nk=%d x=%d z=%d", k, x, z);
     x++; z++; k--;
     proc( k );
     }

     main( )
     {
      proc(  3  );
     }
L'output est sans surprise :
$ ./a.out 
k=3 x=0  z=13
k=2 x=57 z=14
k=1 x=0  z=15
Un abc pour comprendre le cas des variables globales :
[pl@epsilon ~]$ cat a.c 
    #include 
    static  x;
    int y;
    int z;

    void A( ) {
    printf("\nA:x%ps y%p. z%p.", &x, &y, &z);
    }
[pl@epsilon ~]$ cat b.c
    #include 
    int x; 
    static int y; 
    int z;

    void B( ) {
    printf("\nB:x%p. y%ps z%p.", &x, &y, &z);
    }
[pl@epsilon ~]$ cat c.c
    #include 
   void A( void );
   void B( void );
   extern int x, y, z;
   int main( ) 
   {
    A();
    B();
    printf("\nC:x%p  y%p  z%p\n", &x, &y, &z);
    return 0;
   }

[pl@epsilon ~]$ gcc -c  a.c b.c
[pl@epsilon ~]$ gcc a.o b.o c.c
[pl@epsilon ~]$ ./a.out

A:x0x80497b0s y0x80497bc. z0x80497b8.
B:x0x80497c0. y0x80497b4s z0x80497b8.
C:x0x80497c0  y0x80497bc  z0x80497b8
alles klar ?

Variable volatile

[skip ] Par la déclaration
    volatile int cali;
nous signalons au compilateur que la variable peut changer de valeur spontanément, et donc, ce dernier ne doit pas envisager d'optimisation sur cette variable. La volatilité un phénomène surprenant lié au rayonnement cosmique ? Pourquoi pas... Mais plus raisonnablement lors d'une interruption !

   void proc( void )
   {
   #ifdef VOLATILE 
    volatile int cali = 0;
   #else
         int cali = 0;
   #endif
   while ( cali != 0 ) cali--;
   }
On commence par une compilation banale
   $gcc -g -c  a.c
   $objdump -S a.o

   00000000 :
   void proc( void )
   {
      0:	55      	push   %ebp
      1:	89 e5   	mov    %esp,%ebp
      3:	83 ec 10             	sub    $0x10,%esp
#ifdef VOLATILE 
   volatile int cali = 0;
#else
            int cali = 0;
      6:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%ebp)
#endif
   while ( cali != 0 ) cali--;
      d:	eb 04   	jmp    13 
      f:	83 6d fc 01          	subl   $0x1,-0x4(%ebp)
     13:	83 7d fc 00          	cmpl   $0x0,-0x4(%ebp)
     17:	75 f6   	jne    f 
   }
     19:	c9      	leave  
     1a:	c3      	ret    
gcc ne fait pas d'optimisation, il compile ce qui est écrit, en particulier, une boucle inutile ! Maintenant, on demande à gcc d'optimiser
$ gcc -g  -c -O2 a.c
$ objdump -S a.o

00000000 :
   volatile int cali = 0;
#else
            int cali = 0;
#endif
   while ( cali != 0 ) cali--;
}
      0:	f3 c3   	repz ret 
gcc analyse le code, il voit que cali vaut zéro, ne change pas, donc pas de boucle ! Le même chose mais avec un volatile sur cali :
$ gcc -g -c -O2 -DVOLATILE a.c
$ objdump -S a.o
00000000 :
void proc( void )
{
   0:	83 ec 10             	sub    $0x10,%esp
#ifdef VOLATILE 
volatile int cali = 0;
   3:	c7 44 24 0c 00 00 00 	movl   $0x0,0xc(%esp)
   a:	00 
#else
         int cali = 0;
#endif
while ( cali != 0 ) cali--;
   b:	8b 44 24 0c          	mov    0xc(%esp),%eax
   f:	85 c0   	test   %eax,%eax
  11:	74 18   	je     2b 
  13:	90      	nop
  14:	8d 74 26 00          	lea    0x0(%esi,%eiz,1),%esi
  18:	8b 44 24 0c          	mov    0xc(%esp),%eax
  1c:	83 e8 01             	sub    $0x1,%eax
  1f:	89 44 24 0c          	mov    %eax,0xc(%esp)
  23:	8b 44 24 0c          	mov    0xc(%esp),%eax
  27:	85 c0   	test   %eax,%eax
  29:	75 ed   	jne    18 
}
  2b:	83 c4 10             	add    $0x10,%esp
  2e:	c3      	ret    
Cette fois-ci, gcc ne fait aucune hypothèse sur cali, la boucle est codée.

Opérateurs

[skip ] En matière de programmation, il faut savoir ce que qu'on raconte. La priorité des opérateurs et leurs sens d'associations est le B.A.BA de la programmation. Ceux ou celles qui ne peuvent pas résister à la tentation de faire les malins méditeront sur l'instruction :   x = 0;
printf("%d  %d %d ", x++, x++, x++)

  Par ordre de priorités :

=> appel de fonction ( ) , indexation [ ] , selection de champ. et la fleche ->

<= négation !, complément à 1 ~, incrémentation ++, décrémentation --, opposition -, indirection *, adresse &, taille sizeof, et transtypage (type).

=> multiplication *, division / et réduction %

=> addition +, soustraction -

=> décalage à gauche << et décalage à droite >>

=> comparaisons <, <= , > et >=

=> égalité == différent !=

=> ET bit à bit &

=> OU exclusif bit à bit ^

=> OU bit à bit |

=> ET logique &&

=> OU logique | |

<= alternative ? :

<= affectations = *= /=  %=  +=  -=  <<=  >>=  &= ^=  |=

=> virgule ,
 

Alternatives

toc... toc... toc...
C'est ici cours de C ?
If if sir, please, between !
[skip ] En Langage C nous disposons de quatre structures alternatives.

L'instruction alternative incomplète :


    if ( condition ) instructions;

Erreurs : oubli des parenthèses, ajout d'un hypothétique then.
L'instruction alternative complète

    if ( condition ) instructions; 
    else instructions;

Erreurs : oubli des parenthèses, oubli du ; avant else ajout d'un hypothétique then.
Une affectation conditionnelle :

   x = test ? yes : no; 

Une écriture rapide et compacte de :

   if (test) x = yes; 
   else x = no; 

et une instruction de sélection.

switch

[skip ] L'instruction de choix multiple. Avec ces instructions, les compilateurs font des miracles, ne pas hésiter entre un switch et une cascade de if.

void letsgo(FILE *src )
{ int num = 0;
  char line[1024];
  while ( ! feof (src) )
    {
      line[0] = '\0';
      fgets (line, 1024, src);
      num++;
      switch ( *line )
	{
	case '#':
          // skip comments
	  break;
	case 'f' :
	  play( line );
	  break;
        case 'm':
          sscanf( line, "mode=%d", & MODE )
	  break;
	case 'p':
          if ( 2 != sscanf( line, "parm=%d %d", & X, &Y ) ) {
		fprintf( stderr, "error : %d : %s", num, line);
   exit(1);
          }
	  break;
	default:;
	}
    }
  fclose (src);  
}
Pour comprendre, l'approche calculatoire de gcc, je vous suggère de jeter un oeil aux codes assembleurs produit par le compilateur.

int test(int x)
{
    int y;
    switch (x) {
    case 0:
    case 4:
	y = 3;
	break;
    case 1:
    case 5:
	y = 1;
	break;
    case 3:
    case 7:
	y = 2;
	break;
    case 2:
    case 6:
	y = 3;
	break;
    default:
	y = 0;
    }
    return y + x;
}
Erreur : mauvaise gestions des breaks. Le break est optionnel. Une fois le branchement fait, les instructions sont exécutées jusqu'au premier break qui fait sortir du bloc. Le compilateur génère des sauts calculés, c'est simple et efficace :
[pl@localhost ~]$ gcc -S   x.c
[pl@localhost ~]$ cat x.s 
	.file	"x.c"
	.text
.globl test
	.type	test, @function
test:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$16, %esp
	cmpl	$7, 8(%ebp)
	ja	.L2
	movl	8(%ebp), %eax
	sall	$2, %eax
	movl	.L7(%eax), %eax
	jmp	*%eax
	.section	.rodata
	.align 4
	.align 4
.L7:
	.long	.L3
	.long	.L4
	.long	.L5
	.long	.L6
	.long	.L3
	.long	.L4
	.long	.L5
	.long	.L6
	.text
.L3:
	movl	$3, -4(%ebp)
	jmp	.L8
.L4:
	movl	$1, -4(%ebp)
	jmp	.L8
.L6:
	movl	$2, -4(%ebp)
	jmp	.L8
.L5:
	movl	$3, -4(%ebp)
	jmp	.L8
.L2:
	movl	$0, -4(%ebp)
.L8:
	movl	8(%ebp), %eax
	movl	-4(%ebp), %edx
	leal	(%edx,%eax), %eax
	leave
	ret
	.size	test, .-test
	.ident	"GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"
	.section	.note.GNU-stack,"",@progbits

Les Boucles

The C-loops are so cool !
-- Pavle Michko
[skip ] C propose trois structures itératives : une boucle tant que, une boucle répéter et une boucle for qui tient plus de la boucle tant que que de la boucle pour du langage pascal et assimilés.
Pour boucler en C, c'est :
   while ( condition ) instructions;
Par exemple, la valuation dyadique de x
  v = 0;
  while ( x % 2  ==  0  ) {
	 x = x / 2;
         v = v + 1;
  }
syntaxe : oubli des parenthèses
bug : insertion d'un point virgule aprés la parenthèse fermante.


Pour itérer en C, c'est :
for ( initialisation; condition; iteration ) instructions;
C'est l'abréviation de
   initialisation; 
   while (condition) itération;
Par exemple, le poids de x :
   for( w = 0; x > 0 ; w++ )  x &= x - 1;
syntaxe : confusions entre virgule et point virgule
bug : insertion d'un point virgule aprés la parenthèse fermante.


Pour répéter en C, c'est :

do 
    instructions
while ( condition );
Par exemple, l'ordre multiplicatif de p modulo n :
 
  f = 0;
  x = 1;
  do {
    x = ( x * p ) % n;
    f = f + 1;
  } while ( x != 1 );

Boucles infinies

Hardware : The parts of a computer system that can be kicked.
-- Jeff Pesis
[skip ] Rarement souhaitées, les boucles infinies ont des sources diverses. En voici quelques exemples :

 unsigned z; 
    z = 10000; 
    while ( z >=  0 ) z--; 
   

 int z; 
    z = 1; 
    while ( z < 0x7FFFFFFF ) z *= 2; 

 char c; 
 int z; 
     z = 357; 
     c = 0; 
     while ( c < z ) c++;
Quelle idée d'itérer avec un float ? Mais oui, quand on enseigne, on voit de tout : un étudiant m'a déjà fait ça !

 float x, y, p; 
    x = 0; 
    y = 1; 
    p = 0.1;
      
    //avec un pas p = 0.125, la boucle termine. 
    
    while ( y !=  x ) y = y - p;

int main(void)
{   
    char table[8]; 
    char i; 
    i = 0; 
    while ( i++ <=  10 ) 
        table[i] = 0;
    return 0;
}
Dans tous les cas, il est possible d'utiliser gdb pour intercepter un processus suspecté de boucler à l'infini :

$ ./a.out 
% ps -C a.out
  PID TTY          TIME CMD
 6823 pts/0    00:00:06 a.out
% gdb --pid 6823
Attaching to process 6823
Reading symbols from /home/pl/a.out...done.
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0x080483b4 in main () at x.c:12
12	    while ( i++ <=  10 ) 
Missing separate debuginfos, use: debuginfo-install glibc-2.11.2-3.i686
(gdb) display i
1: i = 3 '\003'
(gdb) n
13	        table[i] = 0;
1: i = 3 '\003'
(gdb) 
12	    while ( i++ <=  10 ) 
1: i = 3 '\003'
(gdb) 
13	        table[i] = 0;
1: i = 4 '\004'
(gdb) 
12	    while ( i++ <=  10 ) 
1: i = 4 '\004'
(gdb) 
13	        table[i] = 0;
1: i = 5 '\005'
(gdb) 
12	    while ( i++ <=  10 ) 
1: i = 5 '\005'
(gdb) 
13	        table[i] = 0;
1: i = 6 '\006'
(gdb) 
12	    while ( i++ <=  10 ) 
1: i = 6 '\006'
(gdb) 
13	        table[i] = 0;
1: i = 7 '\a'
(gdb) 
12	    while ( i++ <=  10 ) 
1: i = 7 '\a'
(gdb) 
13	        table[i] = 0;
1: i = 8 '\b'
(gdb) 
12	    while ( i++ <=  10 ) 
1: i = 0 '\000'
(gdb) 
13	        table[i] = 0;
1: i = 1 '\001'
Alors, c'est clair ?

On break ? continue !

The unavoidable price of reliability is simplicity.
--C.A.R HOARE
[skip ] En dehors de return, le langage C prévoit trois ruptures de séquences : break,continue et goto. On utilise break pour sortir d'un bloc itératif et l'instruction continue pour se brancher à la fin du bloc itératif.
while ( --x > 0 ) {
  y = f(x);
  if ( y == 0) break; 
  else s = s + 1/y;
}
while ( --x > 0 ) {
 y = f(x);
 if ( y == 0 ) continue;
 s = s + 1/y;
 }

go to

Please don't fall into the trap of believing that I am terribly dogmatical about [the goto statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!
-- Edsger Dijkstra
If you want to go somewhere, goto is the best way to get there.
-- Ken Thompson.
[skip ] Effectivement, le langage C conserve la possibilité de débrancher à base de goto. L'utilisation est assez rare, mais dans le cadre d'une programmation automatique pour un dévissage de récursion par exemple, le code n'a pas lieu de rester clair pour un humain.
while ( 1 ) {
  x = choice();
  if ( dejavu( x ) ) continue;
  marque( x );
  while ( test(x) ) {
     if ( good(x) ) goto yes;
     if ( bad(x)  ) break;
     x = next(x);
  }
}

yes: 

Bien Compiler.

We should forget about small efficiencies, say about 97% of the time:premature optimization is the root of all evil
-- Donald Knuth
[skip ] Bon voilà, j'ai écrit  un programme prog.c qui ne marche pas :-(... Je comprend pas ce qui se passe. Je l'ai compilé
 
gcc -o prog prog.c
Il n'y a aucune erreur et pourtant quand je lance l'exécutable, je récolte le message :
 Bus error (core dumped)
ou encore
 Segmentation fault (core dumped)
J'ai beau recompiler plusieurs fois rien ne change ! Que se passe-t-il ?

Je ne sais pas ... Mais commencez par compiler votre programme avec l'option -Wall grace à laquelle, le compilateur gcc d'ordinaire peu locace est rendu plus bavard. Essayez...

 gcc -Wall -o prog prog.c
Alors ? ce beau prog.c est-il vraiment socorrect ? Dans une phase de développement, l'option -Wall est indispensable mais encore insuffisante, je vous suggère :
gcc -Wall -g -Wuninitialized -O1 -Wunused-parameter 
L'option -g permettra le debugage éventuel avec gdb, les deux avertissements sont clairs, notez que (-Wuninitialized) requiert l'option -Ox (optimisation) pour être activée. Consultez les manuels directement sur le site http://gcc.gnu.org. Rappelez vous bien que la compilation d'un programme correct ne devrait signaler aucun avertissement !

Makefile

There are two major products that come out of Berkeley: LSD and UNIX. We don’t believe this to be a coincidence.
-- Jeremy S. Anderson
[skip ]

La mise au point d'un programme demande plusieurs tentatives de compilations au moins une (expert) mais parfois plusieurs dizaines. Alors plutôt que de taper cent fois la commande gcc -Wall -o prog prog.c, je vous suggère de créer un tout petit fichier makefile nommé qui contient les lignes

     prog : prog.c
          gcc -Wall -o prog prog.c
Il suit le schéma cible : dépendances, règles.
Erreur courante : oubli d'une tabulation sur une règle.

Pour lancer la compilation, il suffit d'exécuter l'utilitaire make qui recherche le fichier Makefile ou makefile dans le répertoire courant et exécute les instructions spécifiées.

    $ make
    gcc -Wall -o prog prog.c
    $ make
    make: « prog » est à jour.

Ici, on indique que le fichier prog dépend de la source prog.c et si ce dernier est plus récent que prog alors une compilation est lancée sinon rien n'est fait... Ecrire des fichiers makefile c'est un petit peu comme se brosser les dents, c'est une bonne habitude à prendre et gare aux caries ! L'approche dépasse largement le cadre de la compilation d'une source C. Dans l'exemple qui suit, make pi.png, fait une belle (bof) image !

SHELL=/bin/bash
MAX=1000
pi.exe : pi.c
	gcc -Wall pi.c -o pi.exe
pi.png : pi.exe
	./pi.exe $(MAX) > data.txt
	gnuplot <<< "set out 'pi.png';\
          set term png;\
          plot 'data.txt' w l, x/log(x)+sqrt(x);"
int main( int argc, char*argv[])
{
int i, j, s=0, n = atoi( argv[1]);
char*  t = malloc( n );
for( i = 2; i < n; i++ ){
   if ( 0 == t[i] ){
        j = i*i; s++;
        while ( j < n ) t[j+=i]=1;
   }
   printf("%d %d\n", i, s);
}
return 0;
}
Les variables automatiques internes:
$@ 	Le nom de la cible
$< 	Le nom de la première dépendance
$^ 	La liste des dépendances
$? 	La liste des dépendances plus récentes que la cible
$* 	Le nom du fichier sans suffixe
Règles implicites:
SRC=$(wildcard *.txt)
PNG=$(SRC:.txt=.png)

all :  $(PNG)

%.png: %.txt
	./mkstat.sh  $<

debogage

Beware of bugs in the above code; I have only proved it correct, not tried it !
-- Donald Knuth.
[skip ]

Si vous êtes arrivés jusque là c'est que votre programme est acceptable du point de vue de GCC aucun message de mise en garde, aucune alerte... Bravo ! Mais non!!! Le programme ne fonctionne toujours pas... Toujours ce satané bus error ou encore ce segmentation fault... Un message qui signifie que vous tentez un accès mémoire dans une zone interdite affectation invalide : débordement de tableau, pointeur mal alloué, débordement de pile ou encore un calcul erroné.

Un fichier core témoigne des dégâts, voir la section correspondante.

Pour trouvez la ligne qui contient l'instruction incriminée, vous devez recompiler votre programme avec l'option de debogage -g puis lancer l'utilitaire de debogueur.

    gcc -g -o prog prog.c
    gdb  prog

Le prompt gdb apparait introduit par un message plus ou moins loin. Pour une petite v isite de mise en train essayez la commande help, puis la commande help running. L'une des commandes listées permet l'exécution contrôlée du programme. Laquelle? run of course. Tapez donc run. Le programme s'arrête sur la ligne qui contient l'erreur à vous de diagnostiquer puis de corriger !

(gdb) run
Program received signal SIGSEGV,
Segmentation fault 0x106e4 in main () at mbp.c:8

8 table[j] = i
qui indique une erreur en ligne 8. La valeur de j doit-etre en dehors des limites du tableau. Pour le vérifier, nous pouvons imprimer j puis le type de la variable table.
(gdb) print j
1000
(gdb) whatis table
type = int [10]

ggggg ... db de base db de base db de base db de base

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.
--Kernighan
[skip ] Les commandes de bases de gdb dans l'ordre d'apprentissage probable.
run
lancer le programme
run  source.txt
list> lister des lignes
list 10
break ... if
delete    
placer un point d'arrêt
supprime
break 30,  break main,  break 12 if x == 3
delete 4
display
undisplay
tracer une variable
display x, display $pc, display  (int) *x
print
afficher une variable
print x,  print /t x
help

help print
next
exécuter l'instruction suivante

step
idem. y compris dans ss-programme

cont
continuer jusquau prochin arrêt

info
information
info break,  info registers
whatis
type d'une expression
whatis *x
watch
surveiller les  changements de valeurs
watch x
enable
desable
gestion des points
enable 6
desable 3






disassemble
code machine
disassemble main


Core file

/* when I wrote this, only God and I understood what I was doing. Now God only know ! #BeAGeek */
[skip ] En cas d'erreur, un fichier core peut-être créé dans le répertoire courant ou ailleurs. Vous pouvez l'utiliser pour faire un diagnostic sans relancer votre programme. Un certain programme prog.c compilé avec l'option -g provoque l'erreur d'exécution :
./a.out
Segmentation fault (core dumped)
Pour en savoir plus, il suffit d'invoquer gdb, sans oublier le fichier core en offrande.
 gdb a.out core

This GDB was configured as "sparc-redhat-linux"...
warning:
"/home/langevin/CC/CANL/core": no
core file handler recognizes format,
using default
Core was generated by `./a.out '.
Program terminated with signal 11, Erreur de segmentation.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x10ea4 in err (x=7) at dtest.c:13
13 *ptr = x;

L'erreur est en ligne 13,  adressage invalide !
Diagnostic ? le pointeur ptr est mal ou pas alloué...
Pour en savoir plus,  utilisez la commande bt qui donne la succession des appels jusqu'à l'erreur.

(gdb) bt
#0 0x10ea4 in err (x=7) at dtest.c:13
#1 0x120d8 in main (argc=3,argv=0xeffffd44) at dtest.c:2

Listez la source au voisinage de la ligne 13 !

(gdb) l 13
 8
 9 int DIM=7, GOOD=0;
10
11 void err( int x )
12 { int *ptr;
13 *ptr = x;
14 }
15

confirmez(vous  le diagnostic ?

Ligne de commande

If debugging is the process of removing bugs, then programming must be the process of putting them in.
--Edsger Dijkstra
[skip ] La fonction main  sert  d'interface entre le système et le programme. Lors du lancement du processus, les variables d'environnements sont empilées, tout comme les arguments placés sur la ligne de commande et le nombre d'arguments. La valeur retournée par la fonction main sera la valeur de retour du processus.

     int main( void )
     int main( int argc, char*argv[] )
     int main( int argc, char *argv[], char* env[] );

Exemple,

int main( int argc, char*argv[], char *env[] )
{
int i;
printf("\nnombre d'arguments : %d", argc );
for( i = 0; argv[i]; i++ )
   printf("\nargument %d : %s", i, argv[i] );
for( i = 0; env[i]; i++ )
   printf("\nvariable %d : %s", i, env[i] );
return 13;
}
$ ./a.out hello world

nombre d'arguments : 3
argument 0 : ./a.out
argument 1 : hello
argument 2 : world
variable 0 : XDG_VTNR=1
variable 1 : SSH_AGENT_PID=1794
variable 2 : XDG_SESSION_ID=2
variable 3 : SAL_USE_VCLPLUGIN=gtk
variable 4 : HOSTNAME=localhost.localdomain
variable 5 : IMSETTINGS_INTEGRATE_DESKTOP=yes
variable 6 : TERM=xterm
variable 7 : SHELL=/bin/bash
variable 8 : XDG_MENU_PREFIX=lxde-
variable 9 : BIGLROOT=/home/drmichko/CC/bigloop-0.2
variable 10 : HISTSIZE=1000
variable 11 : XDG_SESSION_COOKIE=66a63c4d633f569190aea4760000000d-1332749738.65811-1829739048
variable 12 : SYSFONT=latarcyrheb-sun16
variable 13 : IMSETTINGS_MODULE=none
variable 14 : USER=drmichko
variable 15 : LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:*.pdf=00;33:*.ps=00;33:*.ps.gz=00;33:*.txt=00;33:*.patch=00;33:*.diff=00;33:*.log=00;33:*.tex=00;33:*.xls=00;33:*.xlsx=00;33:*.ppt=00;33:*.pptx=00;33:*.rtf=00;33:*.doc=00;33:*.docx=00;33:*.odt=00;33:*.ods=00;33:*.odp=00;33:*.xml=00;33:*.epub=00;33:*.abw=00;33:*.html=00;33:*.wpd=00;33:
variable 16 : SSH_AUTH_SOCK=/tmp/ssh-NFlDcCHD1639/agent.1639
variable 17 : PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/drmichko/.local/bin:/home/drmichko/bin
variable 18 : MAIL=/var/spool/mail/drmichko
variable 19 : DESKTOP_SESSION=LXDE
variable 20 : BIGLDIST=/home/drmichko/CC/bigloop-0.2
variable 21 : QT_IM_MODULE=xim
variable 22 : PWD=/home/drmichko
variable 23 : XMODIFIERS=@im=none
variable 24 : LANG=fr_FR.UTF-8
variable 25 : SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
variable 26 : _LXSESSION_PID=1639
variable 27 : HISTCONTROL=ignoredups
variable 28 : KEYTABLE=fr-latin9
variable 29 : XDG_SEAT=seat0
variable 30 : HOME=/home/drmichko
variable 31 : SHLVL=2
variable 32 : XDG_CONFIG_HOME=/home/drmichko/.config
variable 33 : BOOT_IMAGE=/vmlinuz-3.2.7-1.fc16.i686.PAE
variable 34 : LOGNAME=drmichko
variable 35 : DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-uLZAboDtqZ,guid=106863000a3602b711dd653400000335
variable 36 : LESSOPEN=||/usr/bin/lesspipe.sh %s
variable 37 : XDG_RUNTIME_DIR=/run/user/drmichko
variable 38 : DISPLAY=:0
variable 39 : XDG_CURRENT_DESKTOP=LXDE
variable 40 : XAUTHORITY=/home/drmichko/.Xauthority
variable 41 : _=./a.out

$ echo $?
13

Traitement des options

$ whatis gcc
gcc (1) - GNU project C and C++ compiler
$ whatis C
C : rien d'adéquat
[skip ] Dans l'exemple qui suit, on utilise la fonction getopt pour traiter les options passées en arguments sur la ligne de commande :
  int main( int argc, char*argv[])
{
  int opt;
  char *optlist ="n:b:";

  int base = 2, n = 0;

  while ( ( opt = getopt( argc, argv , optlist) ) > 0  )
	  switch( opt ){
	  case 'n': n    = atoi( optarg ); break;
	  case 'b': base = atoi( optarg ); break;
	  default : printf("\nusage %s -> %s", argv[0], optlist); 
	            return 1;
	  }
  ...
  return 0;
}

Zone mémoire

main(){char*a="%cmain(){char*a=%c%s%c;printf(a,10,34,a,34);}";printf(a,10,34,a,34);}
-- un C-quine
[skip ] L'espace mémoire virtuel d'un processus est traditionnellement scindé en 4 zones : donn\'ees, code, pile et tas.

char *data = "hello";

void info(char *s, void* t)
{ printf("\n%10s : %10p ", s, t ); }

void proc( int x)
{
 char *y;
 y = (char*) malloc( 1024 );
 info("argument", &x);
 info("locale",   &y);
 info("sur tas",   y);
 if ( x ) proc(x-1);
}

int main( void )
{
 printf("\nptr size : %d", sizeof(int*));
 info( "data", &data );
 info( "main", &main );
 info( "proc", &proc);
 info( "lib" , &printf);
 proc( 1 );
 getchar();
 return 0;
}
On lance le programme
$ ./a.out
ptr size : 4
      data :  0x80498b0 
      main :  0x80484ac 
      proc :  0x8048447 
       lib :  0x8048320 
  argument : 0xbfb94b80 
    locale : 0xbfb94b6c 
   sur tas :  0x9b17008 
  argument : 0xbfb94b50 
    locale : 0xbfb94b3c 
   sur tas :  0x9b17410 
Le processus attend un caractère.
$ ps -C a.out
  PID TTY          TIME CMD
 2461 pts/0    00:00:00 a.out

$ cat /proc/2461/maps
00d0a000-00d0b000 r-xp 00000000 00:00 0          [vdso]
08048000-08049000 r-xp 00000000 fd:02 3539110    /home/drmichko/a.out
08049000-0804a000 rw-p 00000000 fd:02 3539110    /home/drmichko/a.out
09b17000-09b38000 rw-p 00000000 00:00 0          [heap]
42142000-42163000 r-xp 00000000 fd:01 262860     /lib/ld-2.14.90.so
42163000-42164000 r--p 00020000 fd:01 262860     /lib/ld-2.14.90.so
42164000-42165000 rw-p 00021000 fd:01 262860     /lib/ld-2.14.90.so
42167000-4230e000 r-xp 00000000 fd:01 262867     /lib/libc-2.14.90.so
4230e000-4230f000 ---p 001a7000 fd:01 262867     /lib/libc-2.14.90.so
4230f000-42311000 r--p 001a7000 fd:01 262867     /lib/libc-2.14.90.so
42311000-42312000 rw-p 001a9000 fd:01 262867     /lib/libc-2.14.90.so
42312000-42315000 rw-p 00000000 00:00 0 
b77cd000-b77ce000 rw-p 00000000 00:00 0 
b77de000-b77e1000 rw-p 00000000 00:00 0 
bfb76000-bfb97000 rw-p 00000000 00:00 0          [stack]
Avec man proc, vous comprendrez la nature des informations affichées, et l'absence de certaines information. Un point intéressant concerne l'adresse de la fonction printf qui ne semble pas indiquer un point dans une librairie...
 gdb --pid 2461
(gdb) x/i  0x8048320 
   0x8048320 :	jmp    *0x8049898
(gdb) x/a  0x8049898
   0x8049898 :	0x421b4f60 
Les appels aux librairies dynamiques sont indirects.

time.h

To understand what recursion is, you must first understand recursion.
-- and it may take a while.
[skip ]
La bibliothèque time.h fournit des types, fonctions et procédures relatifs au temps. Indispensable pour mesurer les temps de calculs avec précisions. Votre montre, ou encore la commande unix time peut suffire dans certains cas. Pour des situations plus complexes, le mieus est utiliser un profileur.

#include <time.h>
time_t deb, fin;

deb = time( NULL );
MaBelleProc();
fin = time( NULL );

printf("\nTemps %5.2f sec.", difftime(fin, deb) );
 

Profileur

Compilable, fooormidable
Tu étais profilable, j'étais debogable
-- maestro
[skip ]

Un profileur est un outil de développement pour analyser la performance d'un code. Le compilateur gcc fait une liason avec le profileur gprof par

gcc -pg  prog.c

ce qui a pour effet de surcharger automatiquement l'exécutable de compteurs d'appels vers les procédure et de chronomètres. Après exécution, les données sont  enregistrées dans le fichier gmon.out  qui  doit être ensuite filtré par gprof.

#include 
double s = 0;
void ping( int x    ) { s+=x; }
void pong( double x ) { s-=x; }

void loop( int x ) {
while ( x-- ) 
  if ( random()&1 ) ping(x);
  else pong(x);
s=0;
}

int main( int argc, char* argv[] )
{
loop( atoi( argv[1] ) );
return 0;
}
On se doute bien que pong() coute plus que ping(). Une preuve avec gprof :
gcc -Wall -g -pg g.c
./a.out 100000000
gprof -b
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
 43.33      0.97     0.97        1     0.97     2.25  loop
 33.56      1.73     0.76 49997387     0.00     0.00  pong
 23.11      2.25     0.52 50002613     0.00     0.00  ping


			Call graph

granularity: each sample hit covers 4 byte(s) for 0.44% of 2.25 seconds

index % time    self  children    called     name
   0.97    1.27       1/1           main [2]
[1]    100.0    0.97    1.27       1         loop [1]
   0.76    0.00 49997387/49997387     pong [3]
   0.52    0.00 50002613/50002613     ping [4]
-----------------------------------------------
          
[2]    100.0    0.00    2.25    main [2]
   0.97    1.27       1/1           loop [1]
-----------------------------------------------
   0.76    0.00 49997387/49997387     loop [1]
[3]     33.6    0.76    0.00 49997387         pong [3]

-----------------------------------------------
   0.52    0.00 50002613/50002613     loop [1]
[4]     23.1    0.52    0.00 50002613         ping [4]
-----------------------------------------------

Index by function name

  test de couverture

It is easier to write an incorrect program than understand a correct one.
-- Alan Perlis
[skip ]
Dans le programme qui suit, une ligne de code ne pas souvent. Le teste de couverture de gcov permet une détection rapide de ce type de bug.
#include 
double s = 0;
void ping( int x    ) { 
  if ( x >= 0) s+=x; 
  else s-=x;
}
void loop( int x ) {
while ( x-- ) 
  ping(x);
}

int main( int argc, char* argv[] )
{
loop( atoi( argv[1] ) );
return 0;
}
$ gcc -Wall -fprofile-arcs -ftest-coverage prog.c
$ ./a.out 13
$ gcov prog.c
$File 'prog.c'
$Lignes exécutées: 90.91% de 11
$prog.c:creating 'prog.c.gcov'
Haha, des lignes de codes n'ont pas été utilisées, le rapport complet est dans prog.c.gcov :
$ grep '####'  prog.c.gcov
$ #####:    5:  else s-=x;
la ligne 5 n'a jamais été exécutée.

  Fuite mémoire

Oh I've had the time of my life
And I've never felt this way before
And I swear this is true and I owe it all to you
You-you-you .....
-- Dirty bit, dirty bit
[skip ] Un programme qui sollicite le tas par des allocations dynamiques (malloc, calloc) doit impérativement restituer les blocs mémoires avant qu'ils ne deviennent inaccessibles ( free ). Dans le cas contraire, on parle de fuites mémoires. Imaginons un programme utilisant la fonction :
   int leak( int n )
   { int *x, *y;
    x = (int*) calloc( n, sizeof(int) );
    y = (int*) calloc( n, sizeof(int) );
    free( y );
   }
la fonction fuit par x et à chaque appel de leak( n ) le processus perd 4*n octets ! Pour exécuter le programme avec le detecteur de fuites valgrind, il suffit de compiler avec l'option de debugage avant de lancer :
valgrind --leak-check=full ./a.out
==2415== Memcheck, a memory error detector.
==2415== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==2415== Using LibVEX rev 1658, a library for dynamic binary translation.
==2415== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==2415== Using valgrind-3.2.1, a dynamic binary instrumentation framework.
==2415== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==2415== For more details, rerun with: -v
==2415== 
==2415== 
==2415== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 12 from 1)
==2415== malloc/free: in use at exit: 20 bytes in 1 blocks.
==2415== malloc/free: 2 allocs, 1 frees, 40 bytes allocated.
==2415== For counts of detected errors, rerun with: -v
==2415== searching for pointers to 1 not-freed blocks.
==2415== checked 47,228 bytes.
==2415== 
==2415== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2415==    at 0x400473F: calloc (vg_replace_malloc.c:279)
==2415==    by 0x804839C: leak (prog.c:5)
==2415==    by 0x80483DF: main (prog.c:12)
Et voila le travail, valgrind a détecté la perte de 20 octets ( en ligne 5 !) lors de l'appel de leak dans la fonction main.
==2415== 
==2415== LEAK SUMMARY:
==2415==    definitely lost: 20 bytes in 1 blocks.
==2415==      possibly lost: 0 bytes in 0 blocks.
==2415==    still reachable: 0 bytes in 0 blocks.
==2415==         suppressed: 0 bytes in 0 blocks.
==2415== Reachable blocks (those to which a pointer was found) are not shown.
==2415== To see them, rerun with: --show-reachable=yes

bibliothèque

The use of a program to prove the 4-color theorem will not change mathematics - it merely demonstrates that the theorem, a challenge for a century, is probably not important to mathematics.
-- Alan Persis
[skip ]

   #include "x.h"
   int main( void )
    {
     hello();
     return 0;
    }

Le programme utilise un fichier de définition x.h et une fonction hello. On compile cette source en indiquant : un répertoire pour les fichiers d'entêtes, le chemin des bibliothèques et la bibliothèque à utiliser
$ gcc -Wall -Imyinc -Lmylib y.c -ltest
$ ./a.out
hello at 0x80483d8
Nous sommes partis des x files :
$ cat ~/myinc/x.h 
#ifndef x_H
#define x_H
void hello( void );
#endif
$ cat x.c

   #include 
   void hello( void )
    {
     printf("hello at %p\n", hello);
    }

Pour créer la bibliothèque statique avec la commande ar à partir :
$ gcc -c x.c
$ ar cr libtest.a x.o
$ nm libtest.a 
x.o:
00000000 T hello
         U printf

$ mv libtest.a ~/mylib/.

-shared

[skip ] Les bibiothèques dynamiques offrent plus de souplesse que les bibliothèques statiques. Partons des mêmes x-files pour créer une librairie x.so :
$ gcc -o x.so -shared x.c
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH 
$ gcc -Wall -Iinclude  x.so y.c 
Notons que gcc s'est chargé de tout !
[pl@localhost ~]$ ./a.out 
hello at 0x487430
Nous discuterons plus tard de cette adresse ! Maintenant, nous changeons la source x.c et nous recompilons uniquement la librairie dynamique
$ vi x.c
$ grep @ x.c
printf("hello @ %p\n", hello);
$ gcc -o x.so -shared x.c
Et donc, sans recompiler a.out :
[pl@localhost ~]$ ./a.out 
hello at 0x487430
Ca c'est vraiment cool !

Assembleur

The kids seem to like them egg box... 360!
-- The One Ronnie Show
[skip ] Il est parfois utile de jeter un coup d'oeil au code assembleur produit par gcc. On peut le faire avec gdb , ou bien en compilant avec l'option -S. Par exemple, gcc -S prog.c produit un fichier prog.s. Donnons nous la source prog.c correspondant à la liste d'instructions :

   int sq( int n ) 
   {
     int r;
     while ( n-- ) s+ = n * n;
   }

gcc -S prog.c produit le fichier prog.s :
  .file  "prog.c"
  .text
  .globl sq
  .type  sq, @function
sq:
   pushl %ebp
        ; entree dans un sous-programme.
   movl %esp, %ebp
   subl $4, %esp
   movl $0, -4(%ebp)
.L2 :
   decl 8(%ebp) 
        ; adresse du parametre n
   cmpl $-1, 8(%ebp)
        ; n--
        ; hoho n est decremente avant le test !
   jne .L4
   jmp .L3
.L4:
   movl 8(%ebp), %eax
        ; on charge n dans l'accumulateur.
   movl %eax, %edx
        ; on passe dans edx
   imull 8(%ebp), %edx 
        ; calcul du carre dans le registre edx
   leal -4(%ebp), %eax
        ; chargement de l'adresse de la variable locale s
   addl %edx, (%eax) 
        ; on ajoute dx a la varaible locale s.
   jmp .L2
        ; on boucle.
.L3: 
   movl -4(%ebp), %eax
        ; le resultat est passe par le registre eax
   leave
   ret
.size  sq, .-sq
.section
.note.GNU-stack,"",@progbits
.ident "GCC: (GNU) 3.3.2 20031022 (Red Hat Linux 3.3.2-1)" 

Stack frame

To iterate is human, to recurse divine.
-- L. Peter Deutsch
[skip ] Lors d'un appel de fonction  les paramètres sont empilés de la droite vers la gauche. L'adresse de retour est empilée avant de faire un saut vers le sous-programme. A l'entrée du sous-programme le pointeur de base ebp est empilé, puis modifié de sorte à pointer sur un stack-frame dont la structure est :



exemple
adresse asm
contenu
 
variable c> adresse réelle>
%esp haut du stack frame


variables locales
z
0xbfee5f80
-T (%ebp)> premiere variable locale de taille T
i
0xbfee5f84
(%ebp)  %ebp de l'appelant 0xbfee5fa8 0xbfee5f88
4 (%ebp) adresse de retour 0x8048494
8 (%ebp) premier parametre
x
0xbfee5f90
... parametre
y
0xbfee5f94


z
0xbfee5f98

resultat 



A la sortie de la fonction le résultat peut être transmis sur la pile ou par des registres. Le rôle de l'instruction return est de restaurer la configuration de pile de l'appelant. L'adresse de retour est depilée dans le pointeur d'instructions. La partie droite du tableau ci-dessus indique la configuration de la pile lors de l'appel proc(1,2,3) , vous pouvez réaliser cette configuration par l'affichage des adresses des variables locales, des parametres et quelques effet de bords :

   void
   proc( int x, int y, int z)
   {
   int i;
   int t[1];
   t[0] = 13;
   printf("\n&i : %p", &i);
   printf("\n&t : %p", &t);
   printf("\n&p : %p", proc);
   printf("\n&x : %p", &x);
   printf("\n&y : %p", y);
   printf("\n&z : %p", z);
   for( i = 0 ;  7-i ; i++)
       printf("\nz[%d] = %10p", i, t[i] );
   printf("\n");
   }


Un exemple simple détaillé, consiérons la source langage C :
   int proc( int x, int y, int z)
   {
   int r;
   r = x+y+z;
   return r;
   }

   int main( void )
   {
   int x;
   x = proc(1,2,3);
   return 0;
   }
La source assembleur :
 
    .file "pile.c"
    .text
    .globl proc
    .type  proc, @function
proc:
     pushl %ebp
       ;sauvegarde de bp
     movl %esp, %ebp
       ;configuration du stack-frame
     subl $4, %esp
       ;allocation des variable locale
     movl 12(%ebp), %eax
       ;second parametre vers ax
    addl 8(%ebp), %eax
       ;ajout du premier parametre
    addl 16(%ebp), %eax
       ;ajout du troisieme
    movl %eax, -4(%ebp)
       ;affectation du resultat dans la variable locale r
    movl -4(%ebp), %eax
       ;transmission du resultat par un registre
    leave
    ret
       ;depilage de l'adresse de retour
.size  proc, .-proc
.globl main
.type  main, @function
 
main:
   pushl   %ebp
   movl %esp, %ebp
   subl $8, %esp
   andl $-16, %esp 
   movl $0, %eax
   subl %eax, %esp
   subl $4, %esp
     ; reservation de 4 octets sur la pile.
   pushl $3
     ; empilage du dernier parametre
   pushl $2  
     ; empilage du second
   pushl  $1 
     ; et du premier
   call proc 
     ; appel a proc
   addl $16, %esp
     ; suppression des parametres
   movl %eax, -4(%ebp)
     ; récupération du résultat
   movl $0, %eax
   leave
   ret
.size  main, .-main
.section
.note.GNU-stack,"",@progbits
.ident  < "GCC: (GNU) 3.3.2 20031022 (Red Hat Linux 3.3.2-1)"

Inclusion de code asm

There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies. And the other way is to make it so complicated that there are no obvious deficiencies.
-- C.A.R. Hoare
[
skip ] Il est parfois utile d'inclure du code machine directement dans la source C. On utilise la directive, __asm__ . Dans l'exemple qui suit, int fastham(int z) calcule poids de Hamming de l'entier z.
   int fastham(int z)
   { 
   __asm__("      xorl  %eax, %eax");
   __asm__("      movl  8(%ebp), %ecx");
   __asm__("boucle: ");
   __asm__("      jecxz fin ");
   __asm__("      incl %eax  ");
   __asm__("      movl %ecx, %ebx");
   __asm__("      decl %ebx  ");
   __asm__("      andl %ebx, %ecx");
   __asm__("      jmp  boucle ");
   __asm__("fin:"); 
   __asm__("     leave");;
   __asm__("     ret");
   }
Notons bien que cà c'était avant...
#define fastham( z) ( __builtin_popcountll( z )  )