Planche 1

Inf 431 -- Cours 14
Programmation en C
http://jeanjacqueslevy.net
secrétariat de l'enseignement:
Catherine Bensoussan
cb@lix.polytechnique.fr
Aile 00, LIX,
01 69 33 34 67
http://www.enseignement.polytechnique.fr/informatique

Planche 2

Plan

  1. De Java à C
  2. Caractères et entrées-sorties
  3. Tableaux et pointeurs
  4. Enregistrements
  5. Allocation manuelle
Bibliographie

B.W. Kernighan, D.M. Rithchie, The C Programming Language, Prentice Hall, 1988.

B.W. Kernighan, R. Pike, The Practice of Programming, Addison Wesley, 1999.


Planche 3

Langages de programmation


Planche 4

Premier programme


#include <stdio.h>

int main ()
{
  printf ("Bonjour\n");
  printf ("les élèves du cours 431\n");
}

A mettre dans un fichier bonjour.c, ensuite compiler
cc bonjour.c

et exécuter le fichier binaire généré
a.out

Par défaut, le nom du fichier binaire généré est a.out. On peut le choisir en faisant
cc -o bonjour bonjour.c

L'exécution est alors faite par
bonjour

Planche 5

De Java


class Additionneur {
  public static void main (String[ ] args) {
    if (args.length != 2)
      System.out.println ("Mauvais nombre d'arguments.");
    else {
      int n1 = Integer.parseInt (args[0]);
      int n2 = Integer.parseInt (args[1]);
      System.out.println (n1 + n2);
} } }

à C

#include <stdio.h>

int main(int argc, char *argv[ ])
{
  if (argc != 3)
    printf ("Mauvais nombre d'arguments.\n");
  else {
    int n1 = atoi(argv[1]);
    int n2 = atoi(argv[2]);
    printf ("%d\n", n1 + n2);
} }

Planche 6

De Java


class Syracuse {
  public static void main (String[ ] args) {
    int a = Integer.parseInt (args[0]);
    System.out.println (a);
    while (a != 1) {
      if (a % 2 == 0)
        a = a / 2;
      else
        a = 3 * a + 1;
      System.out.println (a);
} } }

à C

int main(int argc, char *argv[ ])
{
    int a = atoi (argv[1]);
    printf ("%d\n", a);
    while (a != 1) {
      if (a % 2 == 0)
        a = a / 2;
      else
        a = 3 * a + 1;
      printf ("%d\n", a);
    }
}

Planche 7

Premières remarques

Planche 8

Caractères et entrées-sorties


#include <stdio.h>

int main ()
{
  int c = getchar();

  while (c != EOF) {
    putchar (c);
    c = getchar();
  }
}
#include <stdio.h>

int main ()
{
  int c;

  while ((c = getchar()) != EOF)
    putchar (c);
}


Une variable de type caractère char est un entier sur 8 bits.

getchar retourne un entier int sur 32 bits. La valeur EOF est en dehors de l'intervalle [0,255] de valeurs retournées pour un caractère normal. Elle signifie qu'on vient de lire la fin de fichier.

Comme la valeur d'une affectation
x =e est la valeur affectée à x, on a plutôt toujours tendance à écrire le programme de droite.

Planche 9

Caractères et entrées-sorties


#include <stdio.h>

int main ()
{
  int c, nLignes = 0;

  while ((c = getchar()) != EOF)
    if (c == '\n')
      ++nLignes;
  printf("%d\n", nLignes);
}

On compile ce programme rangé dans le fichier lignes.c
cc -o lignes lignes.c

On exécute le fichier binaire généré
lignes

On peut aussi rediriger l'entrée (grâce au shell Unix!)
lignes <lignes.c

Planche 10

Tableaux

En C, la taille des tableaux est fixée à la compilation. Il n'y a pas d'opérateur new pour initialiser leur valeur dynamiquement.
int main()
{
  int c, i;
  int nChiffre[10], nBlancs = 0, nAutres = 0;
  for (i = 0; i < 10; ++i)
    nChiffre[i] = 0;
  while ((c = getchar()) != EOF)
    if ('0' <= c && c <= '9')
      ++nChiffre[c - '0'];
    else if (c == ' ' || c == '\t' || c == '\n')
      ++nBlancs;
    else
      ++nAutres;
  printf ("chiffres =");
  for (i = 0; i < 10; ++i)
    printf (" %d", nChiffre[i]);
  printf (", blancs = %d, autres = %d\n", nBlancs, nAutres);
}

Planche 11

Pointeurs

La grande différence entre C et Java provient de l'existence de pointeurs, que l'on doit déréférencer manuellement.

Par exemple, si on considère trois variables entières
int x, y, z;

On peut considérer leurs adresses &x, &y, &z et les ranger dans trois pointeurs p, q, r sur des entiers
int *p, *q, *r;
p = &x; q = &y; r = &z;

On peut aussi prendre l'adresse d'un élément de tableau
int *p, a[10];
p = &a[3];

En C, on adopte la convention suivante a º &a[0]

Pour obtenir la valeur pointée par p, on écrit
int t;
t = *p;

Donc x º *&x

Planche 12

Pointeurs (2)


void echange (int *p, int *q)
{
  int x = *p;
  *p = *q;
  *q = x;
}

int main()
{
  int x = 2, y = 3;
  printf ("x = %d, y = %d\n", x, y);
  echange (&x, &y);
  printf ("x = %d, y = %d\n", x, y);
}

En C, les déclarations diffèrent légèrement de celles de Java.
Planche 13

Pointeurs et tableaux


void triSelection(int a[ ], int n)
{
  int   i, j, imin;

  for (i = 0; i < n - 1; ++i) {
    imin = i;
    for (j = i+1; j < n; ++j)
      if (a[j] < a[imin])
        imin = j;
    echange (&a[i], &a[imin]);
  }
}

int main()
{
  int a[10] = {2, 3, 4, 1, 8, 5, 6, 9, 7, 0};
  triSelection (a, 10);
}

On doit passer la taille du tableau en argument, puisque la valeur de a passée en argument n'est qu'un pointeur vers son premier élément.

Planche 14

Arithmétique sur les pointeurs

En C, on s'amuse à faire de l'arithmétique sur les pointeurs (!?) grâce à l'identité suivante
a + i º &a[i]

Donc si p = &a[i] et si on fait ++p, alors p = &a[i+1] quel que soit le type ou la taille du tableau a. Les programmes C sont souvent truffés d'instructions *p++ peu recommandables.

De l'identité précédente on déduit *(a + i) º a[i]

Soit en ne considérant que les types dans cette nouvelle identité
t    *a º t    a[ ]
pour tout type t.

On pouvait donc donner la signature suivante à triSelection
void triSelection(int *a, int n) { ... }

Planche 15

Chaînes de caractères

Les chaînes de caractères sont des tableaux de caractères terminés par le caractère nul ('\0').
int strlen (char s[ ])
{
  int i = 0;
  while (s[i] != '\0')
    ++i;
  return i;
}

int atoi (char s[ ])
{
  int r = 0; int i = 0;
  while (s[i] != '\0') {
    r = (s[i] - '0') * 10 + r;
    ++i;
  }
  return r;
}
int strlen (char *s)
{
  int s0 = s;
  while (*s++ != '\0')
    ;
  return s - s0 - 1;
}

int atoi (char *s)
{
  int r = 0; char c;
  while ((c = *s++) != '\0')
    r = (c - '0') * 10 + r;
  return r;
}


Les programmes de droite sont du C peu recommandable.

Planche 16

Tableaux à plusieurs dimensions

Il y a une subtilité sur les tableaux à plusieurs dimensions, où les identités précédentes ne peuvent être comprises qu'en considérant l'implantation mémoire.
int m[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
char *argv[3] = {"add""2002""-36"};



Planche 17

Enregistrements

En C, les données composites sont réalisées par la déclaration
struct point {
  int   x;
  int   y;
};

struct point p = {20, 100}, q = {40, 150};

struct point milieu (struct point p, struct point q)
{
   struct point m;
   m.x = (p.x+q.x)/2;
   m.y = (p.y+q.y)/2;
   return m;
}

Planche 18

Définition de type

Le mot-clé typedef permet de raccourcir l'écriture en donnant un nom aux types.
struct point {
  int   x;
  int   y;
};
typedef struct point Point;

Point p = {20, 100}, q = {40, 150};

Point milieu (Point p, Point q)
{
   Point m;
   m.x = (p.x+q.x)/2;
   m.y = (p.y+q.y)/2;
   return m;
}

Planche 19

Création de données dans le tas

Le new de Java n'existe pas en C. Mais on peut créer des données dynamiques en allouant manuellement un bout de mémoire en précisant sa taille en octets.
struct cellule {
  int   val;
  struct cellule *suivant;
};
typedef struct cellule *Liste;

Liste nouvelleListe (int x, Liste a)
{
  Liste r = (Liste) malloc (sizeof(struct cellule));
  if (r == NULL) exit(1);
  r->val = x;
  r->suivant = a;
  return r;
}

Liste ajouter (Liste a, Liste b) {
  if (a == NULL) return b;
  else return nouvelleListe(a->val, ajouter(a->suivant, b));

Planche 20

Quelques remarques

Planche 21

Pile et tas

Il y a 2 types de données en C (comme en Java): Les données dans la pile vivent le temps des appels des fonctions dont elles sont des variables locales, Les données dynamiques restent jusqu'à une libération manuelle par free.

Planche 22

Glaneurs de cellules -- GC

En Java, tous les pointeurs pointent sur le tas. Les variables sont typées. On distingue clairement une variable contenant une adresse par son type (objet, tableau).

En C, la situation est moins claire, puisqu'on peut prendre l'adresse de toute variable et ranger sa valeur dans une autre. Pire, on peut faire des conversions entre types sans aucun contrôle.

En Java, on peut retrouver facilement tous les pointeurs à partir des variables allouées dans la pile, parcourir le graphe des objets accessibles depuis la pile, et libérer les objets inaccessibles pour pouvoir les allouer par la suite. C'est ce que fait le glaneur de cellules (GC) (garbage collector).

De nombreux algorithmes existent pour le GC (compteurs de références, par traçage, par recopie, incrémental, conservatif, etc)

L'implémentation des langages de programmation est étudiée dans le cours Langages de Programmation, majeure 1, et Compilation, majeure 2.
Planche 23

Préprocesseur

Avant toute compilation, le préprocesseur change le source du programme. Pour les curieux cc -E programme.c donne le résultat. Il y a plein d'autres directives, toutes commençant par #, notamment pour tenir compte du type du processeur ou de la version du système d'exploitation.

Planche 24

Exercices

Exercice 1 En C, il n'y a pas de type booléen (grave erreur!). La constante FAUX est représentée par l'entier 0; et VRAI est représentée par tout entier non nul. Quel est le sens de if (x =0) S else S' ? Comparer au sens de if (x ==0) S else S'.

Exercice 2 Quel est le sens de
*p-- = *++p ?

Exercice 3 Comparer les valeurs de
strlen("Bonjour") et de sizeof("Bonjour") ?

Exercice 4 On suppose que
rang(m) calcule le rang de la matrice m. Expliquer qu'on ne peut écrire la déclaration sous la forme int rang(int m[ ][]), mais int rang(int m[n][])n est la première dimension.

Exercice 5 Dans les GCs par compteurs de références, on adjoint à chaque cellule allouée dans le tas un compteur du nombre d'objets ou autres données qui pointent sur cette cellule. Comment gérer cette information? Essayer de deviner le fonctionnement d'un tel GC.





This document was translated from LATEX by HEVEA.