Le langage C pour gcc (GNU/Linux 64 bits)

LE langage C a été mis au point vers 1970 par Ritchie et Thompson dans le cadre du développement de UNIX. GNU/Linux étant un des nombreux avatars de ce système d’exploitation, ce langage y est très important, surtout qu’il est beaucoup plus rapide que les langages interprétés comme PHP, Python et Javascript puisqu’il est compilé pour être directement exécuté par le processeur, sans passer par un interpréteur. Le compilateur de référence sous GNU/Linux est gcc.

Attention !

Documentation officielle : www.gnu.org/software/gnu-c-manual/gnu-c-manual.html

1. Organisation du script .c

1.1 Exemple minimal
1.2 Compilation
1.3 Directives

2. Variables et constantes

2.1 Généralités
2.2 Globales, locales, statiques
2.3 Entiers
2.4 Non entiers
2.5 Constantes
2.6 (short) Tableaux
2.7 (short) Chaînes
2.8 struct
2.9 union

3. Syntaxe

3.1 Séparateurs
3.2 Opérateurs
3.3 Adressage (à faire)

4. Structures

4.1 Logique et comparaisons
4.2 Conditions if - else
4.3 Choix switch - case
4.4 Boucle while
4.5 Boucle do
4.6 Boucle for
4.7 Fonctions

A. stdio : Console et fichiers

B. stdlib : exit(), malloc() et rand()

C. unistd : sleep(), droits...

D. math : Arrondis, log, exp, trigo

E. complex : Nombres complexes

F. ctype : Caractères

G. string : Chaînes

H. time : Temps

1. Organisation du fichier source .c

1.1 Exemple minimal

Considérons cette application minimale très souvent utilisée pour l’exemple :

# include <stdio.h> // exemple canonique

void main()
  {
  printf("Hello, World!\n") ; /* affiche une phrase */
  }

1.2 Compilation

Pour compiler une telle application, il faut sauvegarder le fichier (par exemple sous le nom essai.c et disposer d’un compilateur (le compilateur gcc existe pour tous les systèmes actuels, mais il faudra probablement l’installer). Pour compiler le script en un programme exécutable nommé a.out  :

prompt de la console$ cd chemin_du_fichier_source
prompt de la console$ gcc essai.c

Il s’exécute tout simplement avec `pwd`/a.out ou ./a.out si le chemin actuel du répertoire (pwd) contient le fichier exécutable, chemin_connu_de_vous/a.out sinon.

Notes :

Note : la compilation d’un programme en C avec interface graphique est plus délicate, voyez cette page pour GTK4.

1.3 Directives

Les directives #include et #define (macros) se placent avant la fonction principale main().

#include (bibliothèques)

La directive #include permet d’appeler les headers de bibliothèques nécessaires au fonctionnement du programme. Exemple :

#include <stdio.h>  // entrées et sortie (console et fichiers)
#include <math.h>   // puissances, algorithmes, trigonométrie…

Voir les appendices sur les bibliothèques stdio, stdlib, math, complex qui décrivent les fonctions qu’elle contiennent.

Cette directive permet également d’inclure des fichiers de bibliothèques écrites en C. Il faut alors entourer (le chemin et) le nom du fichier de guillemets "" :

#include "mylibs/semiCplx.c"  // ma bibliothèque sur les nombres semi-complexes

#define (macros)

Directive donnée au préprocesseur permettant un remplacement du mot défini par la valeur qui suit dans tout le script.

#define COPYLEFT "GPLv3"
#define PI 3.1415926

Il est de coutume d’utiliser des majuscules pour la chaîne à remplacer, mais ce n’est pas une obligation. Il ne s’agit pas d’une affectation (pas de =), et aucun point-virgule ne peut être écrit après. Des parenthèses doivent entourer les expressions composées :

#define PI (22./7)

Avec paramètre :

#define xchg(type, x, y) { type aux =x ; x =y ; y =aux ; }

interprétera toute expression de type xchg(int, a, b) en int dummy =a ; a =b ; b =dummy ;, échangeant les valeurs des deux variables d’un type défini, passé en premier paramètre.

Cela ressemble à une fonction, mais ce n’en est pas une : xchg(float, c, d) sera effectivement remplacé avant compilation par float dummy =c ; c =d ; d =dummy ; à l’intérieur du script. [à vérifier : semble ne pas être ennuyé par des homonymies de variables]

2. Variables et constantes

2.1 Généralités

Une variable est destinée à contenir une valeur, qui pourra être modifiée, d’où le nom.

Noms de variables

Un nom de variable peut contenir des lettres latines sans diacritique (accents, cédille), des chiffres et des soulignés _, sans commencer par un chiffre ni contenir d’espace ni de caractères spéciaux. Majuscules et minuscules définissent des variables différentes : var, Var, VAR, vaR peuvent coexister en représentant des valeurs différentes.

Certains mots sont réservés, car utilisés par la syntaxe C : auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while

Noms corrects de variable : s, blA, A4, Luna3, _hum, L_3c et même _ tout seul.

Noms incorrects de variable :

Déclaration et type

On attribue une valeur à une variable (ou on la modifie) à l’aide d’un (seul) signe égal. Cette valeur a un type (nombre, caractère…), qu’il faut déclarer avant toute utilisation, parce que le type a une influence sur la façon de coder la valeur et sur la quantité de mémoire utilisée.

int var ; var =3 ; déclare var comme variable entière, puis lui attribue la valeur 3
int var =3 ; déclare var comme variable entière en lui attribuant en même temps la valeur 3

Il est possible d’affecter la même valeur à plusieurs variables avec :

int x, y, z ; x =y =z =5 ;

…où x vaut ce que z puis y valent, à savoir 5

typedef

typedef int milli ; définit milli comme alias (synonyme) de int, ce qui veut dire que milli mes1 ; équivaut à int mes1 ;, ce qui assure une homogénéité de type et évite la question «quel est le type de ce que je déclare en général dans tel cas ?». Il s’agit d’une aide mnémotechnique.

2.2 Locales, globales, statiques

Variables locales ou globales

Une variable est définie pour le bloc de sa définition et ses sous-blocs : le code qui suit initialise trois variables différentes de même nom, valables à trois niveaux différents.

int x =7 ;
if(1)
  {
  int x =5 ;
  for(int x =0 ; x < 4 ; x++)
    {
    printf("%d\n", x) ;
    }
  printf("%d\n", x) ;
  }
printf("%d\n", x) ;

Attention : l’omission de int dans for(x =0 ; x < 4 ; x++) ramène la valeur de la variable x à 0 et sera à4 au sortir de la boucle (deuxième print()).

Pour résumer, une variable vaut pour tous les blocs inclus sauf si une autre variable de même nom y est initialisée (avec int, float, char, etc.). Une variable initialisée en dehors de toute fonction, y compris main(), est a priori globale.

Variables statiques

Si une variable initialisée en dehors de toute fonction est globale, une variable initialisée dans une fonction est réinitialisée à chaque appel. Pour conserver la valeur acquise par une variable au fil des appels, elle doit être initialisée avec static.

#include <stdio.h>

void myfunc()
  {
  int var1 =0 ; static int var2 =0 ;
  printf("var1 : %d -- var2 : %d\n", var1, var2) ;
  var1++ ; var2++ ;
  }

void main()
  {
  for (int i=0 ; i < 3 ; i++) { myfunc() ; }
  }

…retourne :

var1 : 0 -- var2 : 0
var1 : 0 -- var2 : 1
var1 : 0 -- var2 : 2

Pointeurs

Pour toute variable var, &var contient l’adresse en mémoire où se situe la variable : &var est le pointeur de la variable var. Ce pointeur peut être nécessaire à certaines fonctions pour agir sur plusieurs valeursifier la valeur. printf() utilise %p pour l’expression du pointeur, sous forme hexadécimale :

int var1 =1234567890 ;
printf("Adresse : %p\n", &var1) ;

Si l’on connaît l’adresse d’une variable, type *adr permet d’en récupérer la valeur. Attention : il est nécessaire d’en connaître le type, pour savoir le nombre d’octets à considérer.

Pour définir une variable contenant un pointeur, il faut utiliser *, le type devant être égal à celui de la variable pointée. Pour une indirection, c’est-à-dire chercher la valeur indiquée à l’adresse contenue dans le pointeur, on utiliser également * :

#include <stdio.h>
#include <string.h>

void main()
  {
  int var =1234567890 ;
  int *ptr =&var ;
  printf("Val. : %d\n", var) ;
  printf("Adr. : %p\n", &var) ;
  printf("Adr. : %p\n", ptr) ;
  printf("Val. : %d\n", *ptr) ;
  }

Pour expliquer la nécessité de préciser le type de la variable pointée, rappelons les façons dont une valeur (3 en l’occurrence) remplit la mémoire selon que la variable est short, int ou long :

        adr  adr+1 adr+2 adr+3 adr+4 adr+5 adr+6 adr+7
short    0     3
int      0     0     0     3
long     0     0     0     0     0     0     0     3

2.3 Types entiers

Rappel : ces types et leur codage (1, 2, 4 et 8 octets) sont valables pour le compilateur gcc sur système GNU/Linux 64 bits ; cela peut changer avec d’autres configurations ou d’autres compilateurs. Pour être sûr de la taille en octets d’un type d’entier :

printf("%d\n", sizeof(char)) ;
printf("%d\n", sizeof(short)) ;
printf("%d\n", sizeof(int)) ;
printf("%d\n", sizeof(long)) ;

char

char sert à déclarer un octet qui vaut pour un caractère (il ne s’agit nullement de chaîne).

char e, z ; indique que les variables e et z sont de type «caractère»
char e =127 ; déclare que la variable e (déclarée précédemment) a pour valeur 127

char a ='b' ; déclare le type 'caractère' pour la variable a (à ne faire qu’une fois) et y affecte la valeur 'b'

En WIN-1252 ou ISO-8859-1 (script et console) :

#include <stdio.h>

void main()
  {
  char car1 ='b', car2 =99 ;
  printf("1er car =%c, 2nd car= %c\n", car1, car2) ;
  }

…imprime 1er car =b, 2nd car= c

Certains caractères doivent être codés avec le caractère d’échappement \ :

signed char

Valeurs numériques entières codées sur huit bits, de -128 à +127

unsigned char

Valeurs numériques entières positives codées sur huit bits, de 0 à +255

En WIN-1252 ou ISO-8859-1 (source et console) :

#include <stdio.h>

void main()
  {
  unsigned char car =233 ;
  printf("%c\n", car) ;
  }

…imprime é

Attention ! la valeur -1 est codée 11111111 en mémoire, de la même façon que 255 «non signé» :

#include <stdio.h>

void main()
  {
  unsigned char car =-1 ;
  printf("%d\n", car) ;
  }

…retourne 255

(char)var ou (char)43.156 transforme une valeur en type char (sans changer le contenu de la variable). L’exemple suivant tronque la valeur à 43 et affiche donc le caractère + :

printf("%c\n", (char)43.989) ;

Les chaînes sont des tableaux de caractères

Type short

Le type unsigned short code les entiers sur 16 bits, -32 768 à 32 767

printf("%hd", varnum) pour la sortie d’un short signé (%h pour short, %s étant utilisé pour les chaînes ('string') de caractères.

Le type unsigned short code les entiers positifs sur 16 bits, de 0 à 65 535.

printf("%hu", varnum) pour la sortie d’un short non signé

Attention : les nombres de 32 768 à 65 535 d’un entier court non signé sont codés de la même façon que les nombres de -32 768 à -1.

#include <stdio.h>

void main()
  {
  short ent =-1 ;
  printf("%hu\n", ent) ;
  }

…retourne 65535. Ce comportement se retrouve chez les int et les long

(short)var ou (short)43.156 transforme une valeur en type short (sans changer le contenu de la variable). L’exemple suivant renvoie -43 en non signé, soit 65493 :

printf("%h\n", (unsigned short)-43) ;

Type int

Le type int permet le codage des valeurs entières sur 32 bits, de -2 147 483 648 à 2 147 483 647 ; par exemple :

int ent 2000000000 ;

printf("%d", varnum) pour la sortie d’un entier signé.

Le type unsigned int permet le codage des valeurs entières positives sur 32 bits, de 0 à 4 294 967 295 ;par exemple :

unsigned int entpos 4000000000 ;

printf("%u", varnum) pour la sortie d’un entier non signé.

La note concernant les entiers short est valable pour les int.

(int)var ou (int)23 transforme variable ou nombre en type int.

(int)var ou (int)43.156 transforme une valeur en type int (sans changer le contenu de la variable). L’exemple suivant affiche 5.4321e3 en entier (32bits), soit 5432 :

printf("%d\n", (int)5.4321e3) ;

Type long

Le type long permet le codage des valeurs entières sur 64 bits, de -9 223 372 036 854 775 807 à 9 223 372 036 854 775 807

Le type unsigned long permet le codage des valeurs entières positive sur 64 bits, de 0 à 18 446 744 073 709 551 615

La note concernant les entiers short est valable pour les long.

(long)var ou (long)23 transforme variable ou nombre en type long.

(long)var ou (long)43.156 transforme une valeur en type long (sans modifier le contenu de la variable). L’exemple suivant renvoie -5432 :

printf("%ud\n", (long)-5.4321e3) ;

2.4 Nombres à virgule flottante

Les nombres à virgule flottante permet d’approcher les nombres rationnels et réels. Pour rappel, une division entre deux entiers est entière. Il est possible d’indiquer qu’un nombre n’est pas entier en le terminant par un point : 5. /2.

Type float

Le type float permet de coder des valeurs non entières en 32 bits : un bit pour le signe, huit bits pour l’exposant et 23 pour la mantisse. Avec 24 bits de mantisse (le premier bit d’un binaire est toujours de 1, il est donc implicite), on ne peut être certain que des sept premiers chiffres décimaux.

printf("%f", 22/7.) permet une sortie limitée à six décimales : 3.142857
printf("%.3f\n", 22/7.) un point suivit d’un nombre avant le f précise le nombre de décimales : 3.143
printf("%7.3f\n", 22/7.) prévoit 7 caractères en tout avec le point et les trois décimales :

  3.143

printf("%e\n", 6789.1234) retourne un résultat en notation exponentielle : 4.568123e+03, qui peut combiner les variantes précédentes.

Attention à bien utiliser "%f" pour la sortie des nombres à virgule flottante :

#include ≶stdio.h>

void main()
  {
  float reel =1 ;
  printf("%f\n", reel) ;
  printf("%d\n", reel) ;
  }

Le résultat montre que le codage d’un float réinterprété en tant qu’entier n’a pas de sens :

1.000000
-1001762144

(float)var ou (float)123456789e23 transforme une valeur en type float (sans modifier le contenu de la variable). L’exemple suivant retourne 1.234568e+31  :

printf("%e\n", (float)123456789e23) ;

Type double

Valeurs codée en 64 bits : 1 bit pour le signe, 11 bits pour l’exposant, 52 pour la mantisse, pour une précision d’environ 16 chiffres décimaux.

double dbl =22 /7. ;
printf("%.20lf\n", dbl) ;

retourne 3.14285714285714279370, soit 17 chiffres significatifs. "%le" et leurs variantes existent.

À vérifier : Les bibliothèques mathématiques travaillent par défaut en double.

(double)var ou (double)123456789e23 transforme une valeur en type double (sans modifier le contenu de la variable). L’exemple suivant retourne 10000000000000000 :

printf("%.0f\n", (double)10e15) ;

typedef

typedef permet de définir des alias de types :

typedef float F définit F comme alias de float

typeof

typedef (var1) var2 permet d’initialiser var2 du type de var1, ce qui peut se justifier dans une macro qui doit déterminer les types des variables :

#define mx() ({ typeof(a) _a =(a); typeof(b) _b =(b) ; _a > _b ? _ : _b ; })

2.5 Constantes

Les constantes se comportent comme des variables, si ce n’est que leur valeur ne peuvent pas être modifiées.

const type variable valeur définit une constante :

const float PI=3.14159268 ;

Les constantes sont souvent définies avec des majuscules, mais ce n’est pas obligatoire.

enum

enum var{ NOM1, NOM2, NOM3, NOM4 } attribue les valeurs entières successives à des constantes à partir de 0.

enum var{ CINQ=5, SIX, SEPT, HUIT } attribue des valeurs entières successives à partir de 5.

2.6 Tableaux

(Service minimum)

Un tableau est une structure consistant en une suite de données de même type, qui se déclare sous la forme type nomdevariable[longueur] :

int nbr[5] ; // déclare un tableau de cinq entiers

La longueur est le nombre de cellules incluses dans le tableau, chacune marquées de 0 à longueur -1. Les cinq emplacements pour ces cinq entiers sont donc accessibles dans les cellules nbr[0] à nbr[4] :

nbr[0] =43 ;

Il est possible de déclarer des valeurs entre accolades lors de la déclaration du tableau, qui prennent place à partir du décalage 0. [4] = définit directement le cinquième décalage :

int nbr[5] ={ 14, 3, 5, [4] =43 } ;
for(int i =0 ; i < 5 ; i++)
  {
  printf("%d\n", nbr[i]) ;
  }

…donne :

14
3
5
0
43

…la quatrième valeur nbr[3], qui n’a pas été définie, vaut 0.

La longueur d’un tableau peut être implicite :

short courts[] ={ 1, 2, 3, [9] =99 } ;
for(int i =0 ; i < 10 ; i++)
  {
  printf("%d\n", courts[i]) ;
  }
printf("%zu\n", sizeof courts)

Attention :

Il est également possible d’accéder aux éléments d’un tableau par un pointeur

int tab[] ={ 5, 3, 4, 1, 2 } ;

for(int i =0 ; i < sizeof tab /sizeof *tab ; i++)
  {
  printf("tab[%d] : %d\n", i, *(tab +i)) ;
  }

2.7 Chaînes de caractères

Une chaîne de caractères est en C un tableau de caractères dont le dernier est l’octet 0, représenté par '\0'

Une façon simple de définir une chaîne est :

char ch[] ="L'hiver sera froid" ;

ou

char *ch ="L'hiver sera froid" ;

qui comptera 18 caractères plus \0 de fin de chaîne.

Pour réserver de l’espace pour une chaîne de n caractères :

char chaine[18] ;

…où chaine est une constante permettant l’inclusion de n caractères + le caractère de fin de chaîne.

Il semble impossible de réserver une longueur de chaîne avec une variable, il faut passer par une allocation de mémoire avec malloc() de la bibliothèque <stdlib.h>. C’est nécessaire lorsqu’on veut réserver exactement ce qu’il faut pour copier une chaîne dans une autre :

#include <stdio.h>
#include <stdlib.h> // pour malloc()
#include <string.h> // pour strcpy()

void main()
  {
  char *ch1 ="Avanti populo" ;
  int lon1 =strlen(ch1) ;
  char *ch2 =malloc(lon1 +1) ;
  strcpy(ch2, ch1) ;
  printf("%s\n", ch2) ;
  }

scanf() et fgets(), qui permettent la saisie de caractères, ajoute l’octet 0 de fin de chaîne.

2.8 struct

Envisagé.

2.9 union

Non encore envisagé.

3. Syntaxe

3.1 Séparateurs

"" entoure les chaînes de caractères
'' entoure les caractères pour un type char
( ) entoure les paramètres de réception des fonctions, l’expression logique d’une structure conditionnelle ou d’une boucle, le protocole du compteur de for() ; permet de modifier les priorités des opérandes
{ } entoure un bloc pour une fonction, boucle ou structure conditionnelle ; également utilisée pour les valeurs d’un tableau
[ ] entoure l'entier de l'adressage à l'intérieur d'un tableau
; doit suivre chaque instruction, mais pas les blocs { } ni les ( ) de réception de paramètres des fonctions ou des conditions d’entrée des structures

3.2 Opérateurs classiques

La fonction d’affichage printf() est traitée à la section stdio.

= est utilisé pour l’affectation variable =valeur

int var =43 ;

+ est l’addition. v =v +2 peut se résumer en v +=2 et v =v +1 en v++

- est la soustration. v =v -2 peut se résumer en v -=2 et v =v -1 en v--

Note : v++ s’appelle une incrémentation. Dans certains cas, il peut être intéressant de pré-incrémenter ou post-incrémenter. Dans le bloc qui suit :

int i =0 ;
printf("%d\n", ++i) ;
i =0 ;
printf("%d\n", i++) ;

La première sortie sera de 1 parce que l’incrémentation a lieu avant l’affichage, la seconde de 0 parce que l’incrémentation a lieu après l’affichage. Il en va de même avec la pré-décrémentation --i et la post-décrémentation i--.

* est la multiplication. v =v *2 peut se résumer en v *=2

Note : * est également utilisé comme préfixe pour l'adressage.

/ est la division. v =v /2 peut se résumer en v /=2.

Note : une division de deux entiers donne le résultat de la division entière. Il est possible de rendre la division «réelle» avec un point qui suit le diviseur entier :

int i =5 ;
printf("%d\n", i /2) ;
printf("%f\n", i /2) ;
printf("%f\n", i /2.) ;
printf("%d\n", i /2.) ;

donnera

2
0.000000
2.500000
-1938914656

Dans le premier et troisième cas, le paramètre d’affichage correspond à la nature de la valeur, respectivement un entier 32 bit et un «réel». Dans les autres cas, le langage interprète les données comme il peut, sans s’assurer de l’adéquation des données.

Note : les autres opérations, qui concernent la trigonométrie, les puissances et logarithmes ainsi que les arrondis nécessitent la bibliothèque math ; les nombres complexes sont gérés par la bibliothèque complex.

3.3 Adressage

* préfixe une variable dont le contenu est une adresse un point en mémoire

& adresse (préfixe de variable)

Note : & sert également pour la comparaison de nombres bit à bit (voir point suivant).

4. Structures

4.1 Logique et comparaisons

Les tests ci-dessous renvoient 0 si le test est faux, et 1 si le test est vrai. Il sont très utilisés en début de condition et de boucles.

== est utilisé pour le test d’égalité
!= est utilisé pour le test de différence

Rappel : = seul est utilisé pour l’affectation variable =valeur.

< teste si le premier terme est strictement plus petit
<= teste si le premier terme est plus petit ou égal

> teste si le premier terme est strictement plus grand
>= teste si le premier terme est plus grand ou égal

prop1 && prop2 «et» logique : vrai si les deux propositions sont vraies. Exemple :

prop1 || prop2 «ou» logique : vrai si au moins une des deux propositions est vraie. Exemple :

! est l’inversion logique : !(3<5) donne 0 (faux, l’inverse du vrai).

Les symboles & et | comparent les nombres bit à bit :

9 & 10 renvoie 8 : seuls les bits 1 communs subsistent
9 | 10 renvoie 11 : il suffit d’un bit 1 à chaque rang

&  9 : %1001    |  9 : %1001
  10 : %1010      10 : %1010
   8 : %1000      11 : %1011

4.2 Condition if - else

if(condition) { instruction(s) } permet l’exécution d’instructions que si la condition est vraie.

if(condition) { instruction(s) } else { instruction(s) } permet d’exécuter les instruction du bloc après else uniquement si la condition est fausse.

int a =5 ;
if(a < 7)
  {
  printf("a est plus petit que 7") ;
  }
else
  {
  printf("a n’est pas plus petit que 7") ;
  }

Les instructions des blocs doivent être suivies du point-virgule ; mais pas la ou les condition(s) entre parenthèses. Il est possible d’enchaîner plusieurs structures if - then :

a =5 ;
if (a < 5)
  {
  printf("%c est plus petit que 5\n", a) ;
  }
else
  if (a > 5)
    {
    printf("%c est plus petit que 5\n", a) ;
    }
  else // Sinon…
    {
    printf ("%c égale 5") ;
    }

Opérateur conditionnel ternaire

Il s’agit d’une structure if - else simplifiée permettant une affectation sous condition :

short i = 3 < 5 ? 43 : 87 ; résume :

short i ;
if(3 < 5) { i =43 ; } else { i =87 ; }

4.3 Structure switch - case

La structure switch / case est un test d’égalité d’une variable avec plusieurs caractères ou valeurs numériques qui suivent le mot-clé case et déclenchent les instructions correspondantes.

Les cas prévoient des nombres ou des caractères, mais pas des conditions (comme <, >, ==...). Il ne s’agit dont pas vraiment d’une suite de if.

La structure ci-dessous teste l’égalité de la variable var avec les valeurs numériques 1, 2 et 3.

switch(var)
  {
  case 1 : instruction(s) ; break ;
  case 2 : instruction(s) ; break ;
  case 3 : instruction(s) ; break ;
  default : instruction(s) ;
  }

Si un cas rencontré n’est pas terminé par break, les suivants sont également exécutés, jusqu’à un break. Essayez avec a, b, c et d :

#include <stdio.h>

void main ()
  {
  printf("Taper un caractère :\n") ;
  char var =getchar() ;

  switch(var)
    {
    case 'a' : printf("A minuscule\n") ; break ;
    case 'b' : printf("B minuscule\n") ;
    case 'c' : printf("C minuscule\n") ; break ;
    default : printf("ni A, ni B ni C\n") ;
    }
  }

4.4 Boucle while

Boucle qui se maintient tant que la condition est vraie.

int i =0 ;
while(i < 10)
  {
  printf("%d est plus petit que 10", i) ;
  i++ ;
  }

Pour que la boucle ne soit pas infinie, il est nécessaire de prévoir une condition de sortie ou de faire évoluer le compteur avec i++ ou tout autre pas.

Souvent en suite d’une condition rencontrée, deux mots réservés peuvent modifier le parcours de la boucle :

4.5 Boucle do - while

La condition est évaluée à la fin et doit être suivie d’un point-virgule ;

int i =0 ;
do
  {
  printf("%d est plus petit que 10") ;
  i++ ;
  } while (i < 10) ;

Souvent en suite d’une condition rencontrée, deux mots réservés peuvent modifier le parcours de la boucle :

4.6 Boucle for

La boucle for utilise une récursion à partir d’un compteur qui fixe un départ pour une variable, une condition à respecter et un pas de progression en fin d’itération. La forme la plus commune est :

for(int i =depart ; i < fin ; i++) { instructions ; }

La mise en œuvre sera la table de multiplication par 7 (la plus difficile) :

for(int i =0 ; i < 10 ; i++)
  {
  printf("%d x 7 =%d", i, i *7) ;
  }

Le plus souvent à la suite d’une structure conditionnelle, deux mots réservés peuvent modifier le parcours de la boucle :

On peut évidemment prendre une autre valeur de départ, une autre condition et un autre pas, éventuellement négatif.

for (int v =35 ; v > 7 ; v -=5)
  {
  printf("%d\n", v) ;
  }

Il est possible de faire intervenir plusieurs variables dans la définition de la boucle :

for (int i =5, j =1 ; i > j ; i++, j +=2)
  {
  printf("i: %d -- j: %d\n", i, j) ;
  }

4.7 Fonctions

Une fonction est une structure nommée définie une fois et qui peut-être appelée de tout endroit d’un programme. Une fonction simple serait :

long carre(int c)
  {
  return c *c ;
  }
printf("%ld\n", carre(88)) ;

La fonction doit être définie avant tout appel ; si ce n’est pas le cas, il faut déclarer un prototype qui s’écrivait initialement de la sorte :

long carre(int) ;

Cela ne semble pas fonctionner aussi simplement avec gcc. En attendant, placez vos fonctions avant l’appel, par exemple dans une bibliothèque à inclure.

Paramètres

C’est ainsi que l’on nomme l’ensemble des valeurs passées à une fonction. Chaque variable recevant une valeur (x, y et z) doivent être typées.

float fois_plus(int x, int y, float z)
  {
  return (x +y) *z ;
  }
printf("%.5f\n", fois_plus(3, 5, 1.1)) ;

Récursivité

Une fonction peut s’appeler elle-même, mais il faut prévoir une condition de sortie et veiller à ce qu’elle soit rencontrée. La plus célèbre est la factorielle, qui s’appelle elle-même en multipliant un nombre avec son précédent jusqu’à arriver à 1

#include <stdio.h>

int facto(int n)
  {
  if (n == 1) { return 1 ; }
  else { return n *facto(n -1) ; }
  }

void main()
  {
  printf("%d\n", facto(5)) ;
  }

main() fonction principale

main() est une fonction presque comme les autres, elle peut recevoir des paramètres de l’application appelante.

void main(int argc, char *argv[]) permet de récupérer les arguments fournis par la console. argc contient la longueur du tableau argv, ce dernier contenant au moins une chaîne de caractères. Voici une boucle pour afficher les paramètres passés lors de l’appel en console du programme et les transmettre au programme appelant :

#include <stdio.h>

int main(int argc, char *argv[])
  {
  for(int i =0 ; i < argc ; i++)
    {
    printf("%s\n", argv[i]) ;
    }
  return 0 ;
  }

Pour la commande ./monapp -v quiet saisie sur une console, la première chaîne (argv[0]) contient le nom de l’application ./monapp, les deux suivantes -v et quiet, ce qui fait que argc vaut 3 (il vaut toujours au moins 1, à savoir le nom de l’application). Note : argc et argv sont les noms de variable canoniques, cela pourrait être n_arg et tab_arg, voire narg et targ.

int main() indique que la fonction renvoie un entier : elle doit alors se terminer avec return 0 ; convention pour annoncer au logiciel appelant que le programme s’est bien déroulé. Il est possible de prévoir d’autres nombres à retourner selon les situations, à mentionner dans la documentations sur l’application. Il est sinon possible d’écrire void main() sans return 0 ;

A. stdio - Sorties et entrées

Les fonctions de cette section figurent dans la bibliothèque stdio, dont l’appel doit se faire en début de script avec :

# include <stdio.h>

Console

Sorties console

putchar()

putchar(car) envoie un caractère sur la sortie console uniquement

puts()

puts(car) envoie une chaîne de caractères sur la sortie console uniquement

putc() et fputc()

int c =putc(car, stdout) ; envoient un caractère à la console ; cela pourrait être dans un fichier si on remplace stdout par un identifiant de fichier

printf()

printf("Bonjour, monde!\n") ; affiche une chaîne sur la console. Le retour-chariot, symbolisé par \n, doit être précisé le cas échéant. printf() permet le formatage des nombres et des chaînes qu'il affiche :

printf("%s\n", "chaîne") ; affiche une chaîne, avec saut de ligne

printf("%hd\n", -1) ; affiche un entier 16 bits (h pour short) signé
printf("%hu\n", 65535) ; pour un 16 bits non signé (unsigned)

printf("%d\n", -1) ; affiche un entier 32 bits (int) signé
printf("%u\n", 65535) ; pour un 32 bits non signé (unsigned)

printf("%ld\n", -1) ; affiche un entier 64 bits (l pour long) signé
printf("%lu\n", 18446744073709551615) ; pour un 64 bits non signé (unsigned)

Notes :

Attention : printf("%lb", -1) et printf("%lx", -1) ne renvoient curieusement que des 32 bits (version 12.2.0, x86_64-linux-gnu, Debian)

float r =5 /3. ; printf("%f", r) ; retourne 1,666667
float r =5 /3. ; printf("%.3f", r) ; retourne 1,667 arrondissant à la troisième décimale
float r =5 /3. ; printf("%.20f", 16.8425) ; retourne 1.66666662693023681641 permettant de consater que la précision d’un float ne dépasse guère sept chiffres significatifs

printf("%7.3f\n", 22/7.) un point suivi d’un nombre avant le f précise le nombre de décimales : 3.143

printf("%e\n", 6789.1234) retourne un résultat en notation exponentielle : 4.568123e+03, qui peut combiner les variantes précédentes.

double r =5 /3. ; printf("%.20lf", r) ; retourne 1.66666666666666674068, soit une précision d’environs 16 chiffres significatifs

%f et %lf arrondissent, mais la précision n’est pas très grande :

printf("%.4f\n", 26.82605) ; // retourne 26.8260
printf("%.4f\n", 26.82615) ; // retourne 26.8261
printf("%.4f\n", 26.82625) ; // retourne 26.8263
printf("%.4f\n", 26.82635) ; // retourne 26.8264

sprintf()

chaine =sprintf(formatage, nombre) écrit dans une chaîne, aux mêmes formats que printf() ci-dessus, ce qui permet la transformation d’une expression numérique en chaîne. Pour la transformation de chaînes numériques en nombres, voir ato~la bibliothèque stdlib.

Entrées console

getchar()

int c =getchar() ; lit un caractère sur la console ; c doit être initialisé comme entier parce qu’il peut valoir -1 pour une erreur, qui ne peut être égal à 255 non signé qui est un caractère

getc() et fgetc()

int c =getc(stdin) ; et int c =fgetc(stdin) ; lisent un caractère à partir de la console, ou d’un fichier si stdinest remplacé par un identificateur de fichier

fgets()

fgets() permet de contrôler le nombre de caractères en entrée et remplace donc gets(), abandonné car vulnérable aux attaques par dépassement de capacité (overflow)

char chaine[20] ;
fgets(chaine, sizeof chaine, stdin) ;
printf("%s\n", chaine) ;

scanf() lit des données, en utilisant le formatage de et en réservant préalablement les variables réceptrices. Un retour-chariot termine la saisie, mais il est possible de saisir les deux nombres sur la même ligne en les séparant d’une tabulation ou espace. scanf() renvoie un entier représentant le nombre de variables saisies :

int age, n ; float taille ;
n =scanf("%d %f", &age, &taille) ;
printf("Age: %d ; Taille: %.3f (%d vars)\n", age, taille, n) ;

Les chaînes fonctionnent de la même manière, sauf que chaine étant déjà un pointeur, il ne faut pas le précéder de &) :

char chaine[20] ;
scanf("%s", &chaine) ;
printf("%s\n", chaine) ;

Deux inconvénients :

Il est possible d’y remédier en insérant un nombre dans l’expression %s (%10s) et de remplacer le s par l’expression [^\n] (%[^\n]). On peut combiner les deux :

char chaine[20] ;
scanf("%10[^\n]", chaine) ;
printf("%s\n", chaine) ;

Fichiers

Redirections

Note : l’appel à une application avec > sorties.txt ou 1 > sorties.txt redirige les sorties consoles (printf) de l’application vers ce fichier. La redirection 2 > erreurs.txt crée un fichier d’erreurs.

./monprog 1 > sorties.txt
./monprog 2 > erreurs.txt

La redirection >> accumul.txt ajoute les sorties de l’application au fichier > accumul.txt, le créant au besoin.

./monprog >> accumul.txt

La redirection < entree.txt capture le fichier > entree.txt.

./monprog < fichier.txt

Macros

FOPEN_MAX nombre maximal de fichiers ouverts simultanément, probablement 16.
EOF !vérifier

Modes d’ouverture de fichiers

fopen()

fopen("nom", mode) ouvre un fichier selon un nom et un mode. Il existe deux sortes de fichiers :

Les modes d’ouverture sont :

Pour les fichiers binaires, utiliser "rb", "rb+", "wb", "wb+", "ab" et "ab+"

Il est nécessaire de déclarer un identifiant de fichier, qui recevra un entier à l’ouverture. C’est lui qui permettra d’agir sur le contenu du fichier et d’ensuite le fermer. S’il est nul, c’est par exemple que :

FILE * IdFich ;
IdFich = fopen("fichier.txt", "a") ;

stdin est l’identificateur de l’entrée console, stdout celui de la sortie console. Il est donc possible de rediriger le flux venant d’un fichier avec stdin et vers un fichier pour une sortie console avec stdout.

Note : voir Entrées pour l’utilisation de putc() et putc(), et Sorties pour l’utilisation de getc(), fgetc() et fgets(). Rappel :

fprintf()

fprintf(IdFich, chaine) écrit dans un fichier ouvert en écriture ou ajout

fclose() et fflush()

fclose(IdFich) ferme proprement le fichier après avoir effectivement transcrit les données qui étaient dans la mémoire-tampon. Retourne une valeur non nulle si cela n’a pas été fait.

fflush(IdFich) permet d’écrire le contenu de la mémoire-tampon sans fermer le fichier tout de suite.

if (fflush(IdFich))
  {
  fprintf(stderr, "Ne peut transférer la mémoire-tampon "
                  "dans le fichier\n") ;
  }

Exemple d’utilisation d’une séquence fopen() / fprintf() / fclose() en mode «ajout»

#include <stdio.h>
#include <stdlib.h> // contient 'exit(0)'

void main()
  {
  FILE *IdFich ;
  IdFich = fopen("fichier.txt", "a") ;
  printf("%u\n", IdFich) ;
  if (IdFich ==NULL)
    {
    printf("Pas moyen d'ouvrir le fichier 'fichier.txt'\n") ;
    exit(0) ;
    }
  fprintf(IdFich, "Ajout dans le fichier\n") ;
  fclose(IdFich) ;
  }

Erreurs

ferror(FichId) contient la dernière erreur concernant un identifiant de fichier
perror() affiche sur stderr une texte associé à la dernière erreur selon errno
clearerr(FichId) remet à 0 l’indicateur d’erreur pour un identifiant de fichier

Exemple de traitement d’erreur :

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

void main()
  {
  FILE * FichId =fopen("fichier.txt", "r") ;
  int ret =fgetc(FichId) ;

  printf("%d %d %d\n", ret, ferror(FichId), errno) ;
  perror("") ;
  ret =fclose(FichId) ;
  }

Ici tout se passe bien, le fichier.txt existe et n’est pas vide

perror() renvoie donc Success.

Remplacer fgetc(FichId) par fputc('w', FichId) déclencherait un traitement d’erreur, puisque le fichier est ouvert en lecture, et non en écriture :

Préciser perror("Verdict") afficherait (:) :

Verdict : Bad file descriptor

Déplacement dans un fichier

ftell(FichId) ; retourne la position courante dans un fichier. En mode binaire, c’est en octets, en mode texte, cela dépend de l’encodage (moins de caractères que d’octets en UTF-8 ou écritures extrême-orientale).

fseek(FichId, deplac, lieu) ; déplace la position dans le fichier

feof(FichId) est vrai si le pointeur de fichier est positionné à EOF (fin de fichier).

Avec la variable pos initialisée avec fpos_t pos ;

fgetpos(FichId, &pos) ; retourne la position courante dans
fsetpos(FichId, &pos) ; fixe la position courante avec pos

B. stdlib - Mémoire et hasard

#include <stdlib> en début de fichier

abs() retourne la valeur absolue

atoi(), atol() et atoll() retourne le nombre entier écrit dans une chaîne ("43" vers 43). Attention : toute chaîne non conforme renvoie 0, qui peut également être le résultat de chaîne "0".

strtol, strtoll, strtold, strtoul, lstrtod, strtof fait la même chose, mais retournant une chaîne à partir du premier caractère erroné dans l’énoncé du nombre :

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void main ()
  {
  char str[15] ="203.3.5" ; // "203,4" ; "20.3f"
  char erreur[15] = {0}, *c = erreur ;
  float nf= strtof(str, &c) ;
  printf("%f\n", nf) ;
  if (strlen(c) > 0)
    {
    printf("`%s` pose problème\n", c) ;
    }
  }

Pour l’opération inverse, transformer un nombre en chaîne, voir sprintf() de la bibliothèque stdio.

B.1 exit()

exit() ; termine un programme
abort() ; termine un programme de façon plus brutale

B.2 Mémoire

À préciser.

malloc - allocation de mémoire

La fonction type adresse =(type *) malloc(type n) avec un type entier réserve n octets de mémoire et renvoie l’adresse du premier octet de la plage réservée (NULL en cas de mémoire indisponible). adresse est une variable

Pour 100 int, malloc(sizeof(int) *100)

calloc - allocation de mémoire

calloc(n, sizeof(type)) ; alloue n élément d’un certain type. Pour réserver un bloc mémoire pour 100 float :

int i ;
int * pointer = (int *) calloc(100, sizeof(float));

realloc() réallocation de mémoireb

void *realloc(void *ptr, type nv) change la taille de l’objet pointé par ptr, qui prend l’adresse du nouvel espace mémoire alloué ou NULL en cas d’échec.

free liberation de mémoire

free(p) libère une plage de mémoire réservée

B.3 Générateur aléatoire

rand nombre pseudo-aléatoire

int rand() retourne un entier pseudo aléatoire compris entre 0 et RAND_MAX.

void srand() intitialise ou réinitialise la graine aléatoire pour la fonction rand()

C. unistd : sleep

sleep(sec) attend le nombre de secondes indiqués.

#include <stdio.h>
#include <unistd.h>

void main()
  {
  printf("Début\n") ;
  sleep(3) ;
  printf("Fin\n") ;
  }

usleep(sec) attend le nombre de micro-secondes indiqués.

Gestion des répertoires : rmdir(), chdir(), fchdir(), getcwd()... fichiers : chown(), fchown(), lchown()... et processus, logins...

Sur GNU/Linux ou autres UNIX, voir le fichier de headers /usr/include/unistd.h où les fonctions sont brièvement décrites.

D. math : trigo, log, arrondis

Les fonctions de cette section figurent dans la bibliothèque math, dont l’appel doit se faire en début de script avec :

# include <math.h>

Les fonctions de cette bibliothèque traitent les nombres au format double. Il suffit de les suffixer de f ou l pour obtenir les fonctions qui traitent les nombres au format float ou long double.

Il est recommandé d’ajouter l’option -lm pour la compilation :

gcc essai.c -lm -o monprog

Macros

HUGE_VAL est la plus grande valeur pour un double, ainsi que HUGE_VALF et HUGE_VALL
INFINITY valeur infinie
NAN non valeur (Not A Number)
M_E constante e : 2.718282…
M_PI constante π : 3.141593…

Arrondis

round(x) ; retourne un float arrondi à l’unité
floor(x) ; retourne l’arrondi inférieur
ceil(x) ; retourne l’arrondi supérieur
trunc(x) ; retourne la partie entière
modf(x, y) ; retourne parties entière et fractionnaire :

double entier, fract ;
entier = modf(9876.5432, &fract) ;
printf("%d - %.6f\n", (int)fract, entier) ;

fmin(x, y) ; retourne le minimum de deux valeurs à virgule flottante
fmax(x, y) ; retourne le maximum de deux valeurs à virgule flottante
fdim(x, y) ; retourne la différence positive de deux valeurs à virgule flottante
fmod(x, y) ; retourne la différence positive entre les valeurs absolues

Exponentielles et logarithmes

exp(x) ; retourne la constante e à la puissance x
expm1(x) ; même chose, moins un
exp2(x) ; retourne une puissance de 2
exp2m1(x) ; même chose, moins 1
exp10(x) ; retourne une puissance de 10
exp10m1(x) ; même chose, moins 1
pow(x, y) ; retourne x à la puissance y. Si y n’est pas entier, x ne peut pas être négatif

sqrt(x) ; retourne la racine carrée de x. x ne peut pas être négatif
cbrt(x) retourne la racine cubique d’une valeur

log (x) ; retourne le logarithme naturel (en base e) de x strictement positif
log1p(x) ; équivaut à log(1+x)
log2(x) ; retourne le logarithme en base 2
log10(x) ; retourne le logarithme en base 10 de x strictement positif

frexp(x) ; retourne une fraction et une puissance de deux, facteurs de x :

double fract ; int exp ;
fract = frexp(9876.5432, &exp) ;
printf("%.6lf, %d\n", fract, exp) ;

ldexp(x, y) ; retourne x * exp2(y)
logb(x) ; retourne n tel que exp2(n) <= x
scalbln(x, n) idem précédent?

Trigonométrie

Les angles sont reçus ou retournés radians.

sin(x) ; retourne le sinus de x
cos(x) ; retourne le cosinus de x
tan(x) ; retourne la tangente x

Le domaine de définition est l’intervalle [-1, 1].

asin (x) ; retourne l’arcsinus de x
acos (x) ; retourne l’arccosinus de x

Sans restriction du domaine de définition :

atan(x) ; retourne l’arctangente de x
atan2(x, y) ; retourne l’angle de la pente y /x

toutes les fonctions trigonométriques sauf atan2 ont une version hyperboliques avec le suffixe h

La bibliothèque math dispose aussi de fonctions pour les complexes, à voir dans la librairie suivante.

E. Nombres complexes

Les fonctions de cette section figurent dans la bibliothèque complex, dont l’appel doit se faire en début de script avec :

# include <complex.h>  // lien vers la bibliothèque

La déclaration et l’affectation d’un complexe en double float :

double _Complex res, z =2.3 + 4.5 *I ;

La compilation du fichier doit comporter l’option -lm :

gcc essai.c -lm -o monprog

Les fonctions de cette bibliothèque traitent les nombres au format double. Il suffit de les suffixer de f ou l pour obtenir les fonctions qui traitent les nombres au format float ou long double.

Par défaut, les deux termes d’un complexe seront double, à condition de préciser un point décimal :

_complex z =2.3 + 4.5 *I ;

Macros

z =CMPLX(r, i) fabrique un complexe double à partir de deux réels
z =CMPLXF(r, i) fabrique un complexe simple float à partir de deux réels
z =CMPLXL(r, i) fabrique un complexe long double à partir de deux réels
type complex var =reel + imag*I ; déclare et mobilise deux réels pour en faire un complexe
type imaginary var =reel*I ; déclare un imaginaire pur et y affecte une valeur (ne semble pas fonctionner avec gcc 12.2.0)

Définitions

creal(z) retourne la partie réelle d’un complexe, ou le nombre réel lui-même passé en paramètre
cimag(z) retourne comme réel la partie imaginaire d’un complexe, 0. pour un nombre réel
conj(z) retourne le conjugué d’un complexe, soit creal() -cimag()

cabs(z) et carg() retournent la valeur absolue ou l’argument d’un complexe

Exposants et logarithmes

cexp(z) retourne l’exponentielle, basée sur e, d’un complexe
cpow(z, p) retourne la puissance complexe d’une valeur complexe
csqrt(z) retourne la racine carrée d’un complexe
clog(z) retourne le logarithme népérien (ou naturel) d’un complexe

Trigonométrie

csin(z) retourne le sinus d’un complexe
ccos(z) retourne le cosinus d’un complexe
ctan(z) retourne la tangente d’un complexe

casin(z) retourne l’arcsinus d’un complexe
cacos(z) retourne l’arccosinus d’un complexe
catan(z) retourne l’arctangente d’un complexe

csinh(z) retourne le sinus hyperbolique d’un complexe
ccosh(z) retourne le cosinus hyperbolique d’un complexe
ctanh(z) retourne la tangente hyperbolique d’un complexe

casinh(z) retourne l’arcsinus hyperbolique d’un complexe
cacosh(z) retourne l’arccosinus hyperbolique d’un complexe
catanh(z) retourne l’arctangente hyperbolique d’un complexe

cproj(z) projette un point d’un plan sur la surface d’une sphère de Riemann ; le résultat est le plus souvent z lui-même

F. ctype : caractères

Les fonctions de cette section figurent dans la bibliothèque ctype, dont l’appel doit se faire en début de script avec :

# include <ctype.h>  // lien vers la bibliothèque

Transformations

tolower('c') transforme une majuscule en minuscule
toupper('c') transforme une minuscule en majuscule

Tests

isalnum('c') est vrai en cas de caractère alpha-numérique
isalpha('c') est vrai en cas de caractère alphabétique
isdigit('c') est vrai pour les chiffre
isxdigit('c') est vrai en cas de chiffre hexadécimal (chiffres + a-f + A-F)

islower('c') est vrai en cas de lettre minuscule
isupper('c') est vrai en cas de lettre majuscule

isblank('c') est vrai si le caractère est blanc (\t et espace)
ispunct('c') est vrai en cas de ponctuation (affichables moins alphanumériques)
isspace('c') est vrai en cas de caractère d’espacement (\t, \r,\n, \v, \f et espace)
iscntrl('c') est vrai pour un caractère de contrôle (< ascii 32 + 127)
isprint('c') est vrai en cas de caractère est affichable (non contrôle, avec esppace)
isgraph('c') est vrai en cas de représentation graphique (non contrôle sauf espace)

G. string : Chaînes

Rappels :

size_t =strlen() retourne la longueur d’une chaîne, sans l’octet nul de fin de chaîne

Copies

strcpy() copie une chaîne dans une autre, qui est écrasée : attention si ch1 est plus courte que ch2

char ch1[] = "Chaîne", ch2[] ="0123" ;
strcpy(ch1, ch2) ;
printf("%s\n", ch1) ;

strncpy(ch1, ch2, n) limite la copie à n caractères. Attention : si la taille de ch2 est plus petit que n, l’octet nul de fin de chaîne n’est pas copié, et la nouvelle chaîne garde la longueur de la première.

char ch1[] = "Chaîne", ch2[] ="0123" ;
strcpy(ch1, ch2, 2) ;
printf("%s\n", ch1) ;

…donne 01aîne

strdup() réserve de la mémoire et duplique la chaîne de caractères passée en paramètre. Pour libérer la mémoire, utiliser free() de la bibliothèque stdlib

char *chaine ="ornithorynque" ;
char *copie =strdup(chaine) ;
printf("%s\n", copie) ;
free(copie) ;

strndup(chaine, n) duplique n caractères de la chaîne passée en paramètre.

Concaténations

strcat(chaine, ch1) ajoute une chaîne à la suite d’une autre. Pour assembler deux chaînes, opérer à partir d’une troisième d’une longueur équivalente à la somme des deux :

char ch1[] ="Chaîne", ch2[] ="0123", chaine[sizeof ch1 + sizeof ch2 -1] ={ 0 } ;
strcat(chaine, ch1) ;
strcat(chaine, ch2) ;
printf("%s\n", chaine) ;

…donnera

strncat() limite l’ajout à n caractères.

char ch1[] ="Chaîne", ch2[] ="0123", chaine[sizeof ch1 + sizeof ch2 -1] ={ 0 } ;
strcat(chaine, ch1) ;
strncat(chaine, ch2, 2) ;
printf("%s\n", chaine) ;

Comparaisons

strcmp(ch1, ch2) renvoie -1 si ch1 se classe avant ch2, 1 pour le cas inverse, et 0 si les deux chaînes sont identiques
strncmp(ch1, ch2, n) permet de comparer les n premiers caractères de deux chaînes

strcoll() permet de comparer deux chaînes en tenant compte de la localisation (librairie locale.h requise) :

#include <stdio.h>
#include <locale.h>
#include <string.h>

void main()
  {
  setlocale(LC_COLLATE, "fr_FR.utf8") ;
  char *s1 ="bète" ;
  char *s2 ="bétail" ;
  printf("%d\n", strcoll(s1, s2)) ;
  }

Attention :

Recherche

strchr() retourne l’offset de la première occurrence d’un caractère dans une chaîne de caractères
strrchr() recherche la dernière occurrence d’un caractère dans une chaîne de caractères.
strstr() recherche la première occurrence d’une sous-chaîne dans une chaîne de caractères principale.
strpbrk() renvoie la première position d’un caractère commun parmi les caractères d’une autre chaîne

strerror() renvoie la chaîne de caractères associée à un code d’erreur stocké dans la variable entière errno
strtok() permet d’extraire, un à un, tous les éléments syntaxiques (les tokens) d’une chaîne de caractères.
strxfrm() La fonction strxfrm transforme les n premiers caractères de la chaîne source en tenant compte de la localisation en cours et les place dans la chaîne de destination.

Mémoire

memccpy() transfère un bloc de mémoire orig de n octets dans un second cible :

memcpy(cible, orig, n) ;

memchr() Recherche la première occurrence d’une valeur dans un bloc de mémoire.
memcmp() permet de comparer le contenu de deux blocs de mémoire.
memcpy() permet de copier un bloc de mémoire dans un second bloc.
memmove() permet de copier un bloc de mémoire dans un second, mais fonctionne même si les deux blocs se chevauchent.
memset() permet de remplir une zone mémoire, identifiée par son adresse et sa taille, avec une valeur précise.

H. time : temps

Les fonctions de cette section figurent dans la bibliothèque ctype, dont l’appel doit se faire en début de script avec :

# include <time.h>  // lien vers la bibliothèque

Données

CLOCKS_PER_SEC retourne le nombre de «ticks» par seconde (par exemple 1 000 000)
clock_t moment = clock() ; la variable moment contient le nombre de ticks consommés par l’application depuis son lancement. Il ne s’agit pas du temps écoulé sur une montre, une application ne consommant pas toujours du temps CPU

L’empreinte temporelle «C» est définie comme suit :

time_t empreinte =time(NULL) ;
printf("%ld seconde(s) depuis 1970.01.01 à 0h00:00\n", empreinte) ;

Pour des raisons de portabilité, il faut utiliser difftime() pour être sûr de convertir la différence entre deux empreintes temporelles en secondes, pas toujours exprimées dans cette unité

#include <stdio.h>
#include <time.h>

void main()
  {
  time_t deb = time(NULL) ;
  for (long i =0 ; i < 1500000000 ; i++)
    { }
  time_t fin = time(NULL) ;
  int sec = (int)difftime(fin, deb) ;
  printf( "Finished in %ld sec\n", sec) ;
  }

struct tm est une structure contenant les informations de la date et l’heure d’une empreinte temporelle. L’exemple interroge le temps local connu de la machine il tourne ; le temps commence en 1900.

#include <stdio.h>
#include <time.h>

void main()
  {
  time_t empreinte =time(NULL) ;
  struct tm *Tps = localtime(&empreinte) ;

  printf("%04hd.%02hd.%02hd %02hdh%02hd:%02dh\n",
         Tps->tm_year +1900, Tps->tm_mon+1, Tps->tm_mday,
         Tps->tm_hour, Tps->tm_min, Tps->tm_sec) ;
  }

gmt_time() retourne le temps universel UT (le changement de dénomination de GMT à UT s’est fait en 1972, le nom de fonction est resté).

mktime() convertit une structure temporelle en empreinte temporelle.

Conversions en chaînes

asctime() convertit une structure en chaîne de caractères au format Www Mmm dd hh:mm:ss yyyy
ctime() convertit une empreinte en chaîne au format Www Mmm dd hh:mm:ss yyyy

strftime() convertit une structure en une chaîne de caractères, avec pour formatage :

%Y (%G) l’année complète, sur 4 chiffres jusqu’au 31 décembre 9999 à 23:59:59 ou 60
%y (%g) l’année sur deux chiffres, de 00 à 99 %C nombre de siècles entiers parcourus, ce qui précède l’année sur deux chiffres

%m mois en décimal, de 01 à 12
%b (%h) nom abrégé du mois Jan
%B nom entier du mois January

%d jour du mois, de 01 à 31
%e jour du mois, de 1 à 31
%u jour de la semaine (1 à 7) avec lundi =1
%w jour de la semaine (0 à 6) avec le dimanche =0
%j jour de l’année, de 001 à 366

%a nom abrégé du jour de la semaine, Mon
%A nom entier du jour de la semaine, Monday
%U numéro de la semaine, de 00 à 53, avec le premier dimanche commençant la semaine 01
%W (%V?) numéro de la semaine, de 00 à 53, avec le premier lundi commençant la semaine 01

%H heure sur 24h, de 00 à 23
%k heure sur 24h, de  0 à 23 (une espace remplace le 0)
%I heure sur 12 heures, de 01 à 12
%l heure sur 12 heures, de  1 à 12 (une espace remplace le 0)
%p AM ou PM
%P am ou pm

%M minutes, de 00 à 59
%S Second (00-61) 02

%c date et heure sous la forme Mon Jan 01 01:23:45 2024
%x (%D) date sous la forme %m/%d/%y
%F date sous la forme %Y/%m/%d (format ANSI)
%X (%T) heure sous la forme %H:%M:%S
%R heure sous la forme %H:%M

%s nombre de secondes depuis 1970-01-01 00:00:00.

%z décalage UT + heure d’été
%Z nom de zone CDT (par ex. CEST)
%n retour à la ligne
%t tabulation
%% le signe «pourcent»
%r semble annuler la séquence qui suit

Non-utilisés : %f, %i, %o, %q, %v, %E, %J, %K, %L, %N, %O, %Q

Rappel : expérimentation sur gcc (12.2) sur GNU/Linux Debian Bookworm 64 bits.

Procédé :

#include <stdio.h>
#include <time.h>

void main()
  {
  time_t empreinte =time(NULL) ;
  struct tm *bribes ;
  bribes =localtime(&empreinte) ;
  char chaine[30] ;
  strftime(chaine, 30, "%Y-%m-%d à %Hh%M:%S", bribes) ;
  printf("%s\n", chaine) ;
  }