Programmer en Safe-C

Table des matières


Introduction

Bien que le grand public soit friand de nouvelles technologies (tablettes, smartphones, PC, ..), la programmation a la réputation d'être difficile et n'est pas appréciée comme hobby.

Pourtant ce n'était pas le cas dans les années 1980, l'âge d'or de la micro-informatique, où beaucoup de gens bricolaient des petits programmes en "Basic".

Ce qui a changé depuis, c'est que les langages et les environnements de programmation sont devenus beaucoup plus complexes et inaccessibles au grand public. En effet, une nouvelle génération d'informaticiens a inventé des nouveaux concepts tels que : "la programmation orientée-objet", "les classes", "le polymorphisme", "les types dérivés", "les exceptions", "les expressions lambda", etc.

Ces concepts apportent-ils vraiment quelque chose ? Et bien au risque de vous surprendre, je vous dirais qu'ils n'apportent pas grand-chose, et qu'on s'en passe très bien. En fait, on n'en a pas besoin du tout, même pour des très grands programmes professionnels.

Dans ce tutorial vous allez apprendre à programmer de façon simple et amusante. Mais ne vous méprenez pas : le Safe-C n'est pas un jouet. Bien que nos premiers programmes soient simples, le langage est extrêmement puissant et permet d'écrire de manière très efficace tous les genres de programmes.


Chapitre 1 : Les boites de commandes

1.1 Ouvrir une boite de commande

Avant de commencer, il vous faut apprendre à domestiquer les boites de commandes. Vous les avez peut-être déjà vues, ce sont ces boites noires d'aspect rébarbatif que les "pros" utilisent pour envoyer des commandes à l'ordinateur.

Pour ouvrir une boite de commande, cliquer sur "Démarrer" puis sur "Exécuter".

Tapez ensuite la commande cmd et cliquez sur OK.

Vous devriez voir une boite de commande s'ouvrir comme celle-ci:

Comme vous passerez beaucoup de temps avec ces boites, cela vaut la peine de passer du temps à la configurer pour la rendre pratique. Vous pouvez changer de font et de taille (par exemple l'allonger verticalement pour avoir plus de place) en cliquant sur le coin supérieur gauche, puis sur Propriétés,

ce qui va faire apparaitre le menu suivant :

Observez sur l'image la hauteur de la mémoire tampon qui est mise à 300, ce qui va vous faire apparaitre une barre de défilement à droite et vous permettre d'avoir plus de place.


1.2 Les commandes de base

Voici quelques commandes que vous pouvez essayer : tapez DIR puis appuyez sur la touche Entrée(<-)

Une liste des dossiers de l'utilisateur Samuro va s'afficher.

La commande DIR est l'ancêtre de l'explorateur Windows, elle permet d'obtenir une liste de tous les fichiers qui se trouvent dans un dossier. Ici le dossier par défaut est C:\Users\Samuro.

Pour changer de dossier, on a la commande CD (change directory), par exemple tapez CD \WINDOWS (n'oubliez pas le \ !) pour vous positionner dans le dossier WINDOWS, ensuite tapez DIR. Vous allez voir les nombreux fichiers de Windows.

Pour défiler page par page, essayez la commande DIR /P     (attention le / est dans l'autre sens)

Pour effacer l'écran, essayez la commande CLS.

Pour vous positionner à la racine du disque C (donc dans aucun dossier), tapez CD \

Maintenant on va créer un nouveau dossier, par exemple on va l'appelez SAFE-C. Tapez simplement MD SAFE-C (MD signifie Make Directory).

On va se positionner dans le dossier SAFE-C en tapant CD \SAFE-C

Et finalement on va afficher la liste de tout ce qui se trouve dans ce dossier avec DIR :

Vous voyez que le dossier est vide.

Si vous démarrez l'explorateur windows, vous pouvez voir le dossier que vous avez créé :

Nous mentionnerons encore la commande HELP qui vous donne des infos sur toutes les commandes existantes. Par exemple HELP DIR vous donne des infos sur toutes les options de la commande DIR.


Chapitre 2 : Mes premiers programmes


2.1 Installer le compilateur Safe-C

Pour compiler vos programmes, vous avez besoin du compilateur Safe-C que vous pouvez télécharger ici.

Créez un dossier sur votre disque C (par exemple C:\Safe-C), ensuite dézippez et copiez tous les fichiers dedans.

Vous devriez voir les fichiers suivants :


2.2 Ecrire un programme simple

Ouvrez une boite de commande (voir chapitre précédent) et positionnez-vous dans le répertoire du compilateur.

tapez la commande :

    notepad hello.c

Notepad va vous poser la question : "voulez-vous créer un nouveau fichier ?" Répondez OUI.

Tapez ensuite votre premier programme :


// hello.c

from std use console;

void main()
{
  printf ("Hello, World !\n");
}

Sauvez et fermez Notepad.

Vérifiez que votre fichier hello.c se trouve bien dans le dossier :

Tapez ensuite dans la boite de commande :

    mk hello

ce qui va compiler votre texte source hello.c en programme hello.exe

Il ne vous reste plus qu'à lancer votre programme en tapant :

    hello

et vous devriez voir s'afficher "Hello, World!" à l'écran.

Bravo, vous venez de créer votre premier programme !

Si vous avez une erreur Windows au lancement du programme, il est probable que Windows Defender a effacé votre programme suite à une détection erronée de virus. Il faut alors ajouter le répertoire du compilateur dans les dossiers exclus de Windows Defender.

Plus tard si vous distribuez votre programme sur internet, et que vous voulez éviter des alertes chez vos utilisateurs, vous devez avertir l'équipe Windows Defender un jour à l'avance pour qu'ils mettent votre programme sur liste blanche. Cela se fait automatiquement en envoyant votre programme sur le site https://www.microsoft.com/en-us/wdsi/filesubmission



2.3 "Hello World" en détails


// hello.c

from std use console;

void main()
{
  printf ("Hello, World !\n");
}

Regardons maintenant ce premier programme en détails.

// hello.c

La première ligne commence par le signe // qui indique un commentaire. Elle est donc ignorée par le compilateur.

from std use console;

La deuxième ligne dit au compilateur d'ouvrir la librairie standard std (std.lib) et d'en charger le composant 'console'. Si vous allez voir le contenu du composant 'console' sur la page principale de ce site, vous y trouverez la déclaration de la fonction printf() que nous pourrons donc utiliser ci-après.

void main()

La troisième ligne indique au compilateur que c'est là que commence le programme. Tout programme Safe-C consiste en un ou plusieurs fonctions. main() est une telle fonction, et en fait tous les programmes ont une fonction main() parce que c'est là que démarre le programme. En général la fonction main() va appeler d'autres fonctions.

{
  printf ("Hello, World !\n");
}

Les accolades { } entourent les instructions que la fonction va exécuter. Ici on n'a qu'une seule instruction, printf() qui sert à afficher un texte sur la boite de commande. Chaque instruction se termine par un point-virgule (;)

Pour appeler une fonction, on donne simplement son nom (ici printf) et on donne d'éventuels arguments entre parenthèses (ici "Hello, World !\n"). S'il n'y a pas d'arguments, on ajoute simplement une paire de parenthèses vides ().

Notons au passage que le signe \n ne s'affiche pas, c'est un symbole qui indique un passage à la ligne suivante. Donc si vous placez un deuxième printf juste après, il affichera son texte sur la ligne en dessous et pas à droite.

En fait on aurait très bien pu écrire le printf en trois fois avec le même effet :


  printf ("Hello, ");
  printf ("World !");
  printf ("\n");


Essayez donc de modifier ce programme pour afficher autre chose ..

2.4 Mon 2ème programme

Le programme suivant va utiliser la formule C = 5/9*(F-32) pour afficher une table de conversion de températures en degrés Farenheit et Celsius :
    0   -17
   20    -6
   40     4
   60    15
   80    26
  100    37
  120    48
  140    60
  160    71
  180    82
  200    93
  220   104
  240   115
  260   126
  280   137
  300   148
Ce programme introduit plusieurs nouvelles idées : les variables et les boucles.

// table Fahrenheit-Celsius

from std use console;

void main()
{
  int fahr, celsius;
  int lower, upper, step;
  
  lower = 0;    // lower limit of temperature scale
  upper = 300;  // upper limit
  step = 20;    // step size
  
  fahr = lower;
  while (fahr <= upper)
  {
    celsius = 5 * (fahr-32) / 9;
    printf ("%5d %5d\n", fahr, celsius);
    fahr = fahr + step;
  }
}

En Safe-C, il faut déclarer la liste des variables avant de les utiliser :

  int fahr, celsius;
  int lower, upper, step;

"int" signifie que ce sont des variables entières qui contiennent un nombre sans virgule entre -2147483648 et +2147483647.

Le programme commence par 3 assignations qui donnent des valeurs initiales aux variables :

  lower = 0;    // lower limit of temperature scale
  upper = 300;  // upper limit
  step = 20;    // step size

L'instruction "while" sert à boucler : tant que la condition est vraie, on répète les instructions entre accolades. A noter que s'il n'y avait qu'une seule instruction, alors les accolades ne seraient pas nécessaires.

  fahr = lower;
  while (fahr <= upper)
  {
    celsius = 5 * (fahr-32) / 9;
    printf ("%5d %5d\n", fahr, celsius);
    fahr = fahr + step;
  }

Le gros du travail est fait dans l'instruction de calcul qui va évaluer "5 * (fahr-32) / 9" et placer le résultat dans la variable de gauche. Le signe * indique une multiplication, le signe / indique une division. Il est à noter que puisque ce sont des variables 'int', donc sans virgule, les résultats sont tronqués, la partie fractionnaire est supprimée.

    celsius = 5 * (fahr-32) / 9;

La fonction printf permet d'imprimer une ligne formatée. Ici le format %5d lui indique d'imprimer une variable décimale (donc int) en 5 positions.

    printf ("%5d %5d\n", fahr, celsius);

La dernière instruction va augmenter 'fahr' de la valeur 'step'. Plus précisémment, elle va calculer la valeur de droite 'fahr + step' et placer le résultat dans la variable de gauche "fahr". Cette dernière reçoit donc une nouvelle valeur avant que la boucle ne se répète.

  fahr = fahr + step;

Vous avez remarqué que les températures sont approximatives puisqu'on travaille avec des 'int'. Voici le même programme avec des variables "float" :


// table Fahrenheit-Celsius

from std use console;

void main()
{
  float fahr, celsius;
  float lower, upper, step;
  
  lower = 0.0;    // lower limit of temperature scale
  upper = 300.0;  // upper limit
  step = 20.0;    // step size
  
  fahr = lower;
  while (fahr <= upper)
  {
    celsius = 5.0 * (fahr-32.0) / 9.0;
    printf ("%8.3f %8.3f\n", fahr, celsius);
    fahr = fahr + step;
  }
}

ce qui donne le résultat suivant :

   0.000  -17.777
  20.000   -6.666
  40.000    4.444
  60.000   15.555
  80.000   26.666
 100.000   37.777
 120.000   48.888
 140.000   60.000
 160.000   71.111
 180.000   82.222
 200.000   93.333
 220.000  104.444
 240.000  115.555
 260.000  126.666
 280.000  137.777
 300.000  148.888

Vous remarquerez un formatage spécial pour les floats :

    
    printf ("%8.3f %8.3f\n", fahr, celsius);

qui signifie : imprimer avec 8 caractères au total, dont 3 chiffres après la virgule.

La commande printf est très riche et permet les formatages suivants :

    
   "% [Drapeau] [Largeur] [. Précision] Type"

   Type                          sortie
   ----                          ------
   %                             '%%' s'écrira '%'
   d  entier signé               nombre décimal (-61)
   u  entier non-signé ou enum   nombre non signé (12)
   x  entier ou enum             nombre hexadécimal (7fa)
   e  float, double              format scientifique (3.9265e+2)
   f  float, double              virgule flottante (392.65)
   c  char ou string             tous les char, ou max 'précision' char.
   C  wchar ou wstring           tous les wchar, ou max 'précision' wchar.
   s  string                     chaîne s'arrête à nul, ou "précision" char
   S  wstring                    chaîne s'arrête à Lnul, ou "précision" wchar

   Drapeaux
   --------
   -    Pour tous          : justifier à gauche endéans le champ (par défaut : justifier à droite).
   +    Pour d, e, f       : préfixe '+' pour les nombres positifs ou nuls.
   0    pour d, e, f, x, u : préfixe des chiffres '0' au lieu d'espaces;
                             (non valable avec le flag '-')

   Largeur
   -----
   (Nombre)   nombre minimum de caractères à imprimer (complété
              avec des espaces ou des zéros si nécessaire).
              La valeur n'est pas tronquée, même si le résultat est trop long.
   *          La largeur n'est pas spécifiée dans la chaîne de format, mais dans un
              paramètre supplémentaire de valeur int précédant le paramètre à
              formater.

   Précision
   ---------
   . Nombre  Un point sans nombre indique une précision de zéro.
             Pour f: c'est le nombre de chiffres à afficher après le
                     point décimal. La précision par défaut est de 6.
                     Aucun point décimal est n'imprimé pour une précision de zéro.
             Pour s/S: c'est le nombre maximum de caractères à
                       imprimer. Par défaut on imprime tous les caractères.
   .*        La précision n'est pas spécifiée dans la chaîne de format, mais comme un
             paramètre supplémentaire de valeur int précédant le paramètre à
             formater.



Voici maintenant une façon abbréviée d'écrire le même programme :


// table Fahrenheit-Celsius

from std use console;

void main()
{
  int fahr;
  for (fahr=0; fahr<=300; fahr+=20)
    printf ("%5d %5d\n", fahr, 5 * (fahr-32) / 9);
}

L'instruction "for" est composée de 3 parties : la première "fahr=0" n'est exécutée qu'une fois. La condition "fahr<=300" est testée avant chaque tour de boucle pour voir si on continue à boucler, et la 3ème partie "fahr+=20" est exécutée après les instructions à répéter, à la fin de chaque boucle.

La version suivante du programme utilise des "constantes" qui sont des variables qui ne changent jamais de valeur. On les écrit généralement en majuscules. Placer une constante en haut du programme est intéressant quand on risque de vouloir changer souvent cette valeur ou bien qu'elle est utilisée à plusieurs endroits dans le programme.


// table Fahrenheit-Celsius

from std use console;

void main()
{
  const int LOWER = 0;    // lower limit of temperature scale
  const int UPPER = 300;  // upper limit
  const int STEP  = 20;   // step size

  int fahr = LOWER;
  int celsius;
  
  while (fahr <= UPPER)
  {
    celsius = 5 * (fahr-32) / 9;
    printf ("%5d %5d\n", fahr, celsius);
    fahr = fahr + STEP;
  }
}


2.5 Les entrées-sorties


Ce programme vous permet d'entrer 2 nombres afin de calculer leur somme :
// add.c

from std use console;

void main()
{
  int a, b, c;
  
  printf ("enter first number : ");
  scanf (" %d", out a);
  
  printf ("enter second number : ");
  scanf (" %d", out b);
  
  c = a + b;
  
  printf ("the sum is %d\n", c);
}

enter first number : 123
enter second number : 79
the sum is 202

La fonction scanf vous permet d'introduire une valeur au clavier. Elle permet les formatages suivants :

  Un blanc correspond à ignorer zéro ou plusieurs caractères d'espace blanc.
  Un caractère non-blanc, à l'exception du caractère pourcentage (%),
    doit correspondre à la saisie, ou la fonction échoue.

  Format :  "%[*][Largeur] Type"

    *         Les données sont lues mais ne sont pas stockées (il n'y a aucun
              paramètre correspondant).
   Largeur    Nombre maximum de caractères à lire.

   Type                        Entrée
   ----                        -------
   '%'                         se lira '%%'

   d   tout entier signé       nombre décimal optionnellement précédé de + ou - (-61)

   u   tout entier non-signé   nombre non signé (12)
       ou enum

   x   idem à d ou u           nombre hexadécimal (7fa)

   f   float ou double         nombre à virgule flottante (0,5) (12.4e +3)
   e   idem que f

   c   char ou string          remplir arg, ou lire max 'width' chars.
   C   wchar ou wstring        remplir arg, ou lire max 'width' wchars.
   s   char ou string          idem que c mais s'arrête au premier blanc.
   S   wchar ou wstring        idem que C, mais s'arrête au premier blanc.


Le programme suivant vous permet d'introduire 3 valeurs entières et de calculer leur moyenne :


// averager.c

from std use console;

void main()
{
  int a, b, c;

  printf ("please input 3 numbers : ");
  scanf (" %d %d %d", out a, out b, out c);

  printf ("the average is %d\n", (a+b+c) / 3);
}




Le programme suivant vous permet d'introduire des valeurs et de calculer leur moyenne, mais vous remarquerez qu'on n'utilise pas scanf(). Les valeurs sont passées à la fonction main() lors du démarrage du programme.


// averager2.c

from std use console, strings;

void main (string[] arg)
{
  int i;
  float f, sum, count;
  
  sum = 0.0;
  count = 0.0;
  for (i=1; i<arg'length; i++)
  {
    sscanf (arg[i], " %f", out f);
    sum += f;
    count += 1.0;
  }
  
  printf ("the average is %f\n", sum/count);
}

Voici comment le lancer :
C:\SAFE-C> averager2 56 129 126
the average is 103.666664

Attention de fournir au moins une valeur, sinon count vaudra zéro et provoquera un crash du programme avec le message "division par zéro".


Le programme suivant vous permet d'introduire de créer un fichier sur le disque et d'y écrire des lignes de texte.


// writer.c

from std use console, files;

void main()
{
  FILE file;
  int  rc;
  
  const string FILENAME = "myfile.txt";
  
  rc = fcreate (out file, FILENAME, ANSI);
  if (rc < 0)
  {
    printf ("error: cannot create file %s\n", FILENAME);
    return;
  }
  
  fprintf (ref file, "Hi World !\n");
  fprintf (ref file, "This is a test writing into file %s\n", FILENAME);
  fprintf (ref file, "have a good day.\n");

  fclose (ref file);
}

Après exécution, le fichier "myfile.txt" sera créé sur le disque. Si vous l'ouvrez avec notepad, vous verrez qu'il contiendra :


Hi World !
This is a test writing into file myfile.txt
have a good day.




Le programme suivant vous permet de lire un fichier texte et d'afficher son contenu à l'écran.


// reader.c

from std use console, files;

void main()
{
  FILE file;
  int  rc;
  char line[100];
  
  const string FILENAME = "myfile.txt";
  
  rc = fopen (out file, FILENAME);
  if (rc < 0)
  {
    printf ("error: cannot open file %s\n", FILENAME);
    return;
  }
  
  while (fgets (ref file, out line) == 0)
    printf ("%s", line);
    
  fclose (ref file);
}

Si vous l'exécutez, vous verrez l'affichage suivant :


Hi World !
This is a test writing into file myfile.txt
have a good day.




Le programme suivant vous permet de lire un fichier texte et de compter le nombre de lettres "a" qu'il contient.


// counter.c

from std use console, files, strings;

void main()
{
  FILE file;
  int  rc;
  char line[100];
  int  count;
  
  const string FILENAME = "myfile.txt";
  
  rc = fopen (out file, FILENAME);
  if (rc < 0)
  {
    printf ("error: cannot open file %s\n", FILENAME);
    return;
  }
  
  count = 0;
  while (fgets (ref file, out line) == 0)
  {
    int len = strlen(line);
    int i;
    for (i=0; i<len; i++)
    {
      if (line[i] == 'a')
        count++;
    }
  }
    
  fclose (ref file);
  
  printf ("number of A's : %d\n", count);
}

Si vous l'exécutez, vous verrez l'affichage suivant :


number of A's : 4




Il est possible de séparer les partie lecture de fichier et comptage de lettre en utilisant une fonction.


// counter2.c

from std use console, files, strings;

int number_of_a (string line)
{
  int len = strlen(line);
  int count = 0;
  int i;
  for (i=0; i<len; i++)
  {
    if (line[i] == 'a')
      count++;
  }
  return count;
}

void main()
{
  FILE file;
  int  rc;
  char line[100];
  int  count;
  
  const string FILENAME = "myfile.txt";
  
  rc = fopen (out file, FILENAME);
  if (rc < 0)
  {
    printf ("error: cannot open file %s\n", FILENAME);
    return;
  }
  
  count = 0;
  while (fgets (ref file, out line) == 0)
    count += number_of_a (line);
    
  fclose (ref file);
  
  printf ("number of A's : %d\n", count);
}




Il est possible de placer la fonction dans un fichier séparé. Il faut pour cela créer 3 fichiers :


// acounter.h

int number_of_a (string line);

// acounter.c

from std use strings;

public int number_of_a (string line)
{
  int len = strlen(line);
  int count = 0;
  int i;
  for (i=0; i<len; i++)
  {
    if (line[i] == 'a')
      count++;
  }
  return count;
}

// mycounter.c

from std use console, files;
use acounter;

void main()
{
  FILE file;
  int  rc;
  char line[100];
  int  count;
  
  const string FILENAME = "myfile.txt";
  
  rc = fopen (out file, FILENAME);
  if (rc < 0)
  {
    printf ("error: cannot open file %s\n", FILENAME);
    return;
  }
  
  count = 0;
  while (fgets (ref file, out line) == 0)
    count += number_of_a (line);
    
  fclose (ref file);
  
  printf ("number of A's : %d\n", count);
}

Il faut compiler uniquement le ficher contenant la fonction main, le compilateur va suivre les clauses "use" et trouver les autres fichiers automatiquement.


Chapitre 3 : Le calendrier


3.1 La date du jour

Pour afficher la date et l'heure, essayez le programme suivant :


// date1.c

from std use calendar, console;

void main()
{
  DATE_TIME now;

  get_datetime (out now);

  printf ("Nous sommes le %02d/%02d/%04d ", now.day, now.month, now.year);
  printf ("et il est %02d:%02d:%02d.\n", now.hour, now.min, now.sec);
}

ce qui affiche :


Nous sommes le 03/01/2011 et il est 19:24:43.



DATE_TIME est une structure qui est déclarée dans le composant de librairie calendar comme suit :


struct DATE_TIME
{
  int2 year;     // 1901 to 9999
  int1 month;    //    1 to   12
  int1 day;      //    1 to   31
  int1 hour;     //    0 to   23
  int1 min;      //    0 to   59
  int1 sec;      //    0 to   59
  int2 msec;     //    0 to  999
}

Vous voyez qu'elle contient 7 champs (année, mois, jour, heure, min, sec et msec).

La fonction get_datetime() définie dans calendar va remplir la variable 'now', ensuite on va utiliser de nouveau printf() pour afficher les différents champs.

Remarquez le formatage de printf : %02d indique une zone numérique de 2 chiffres, %04d indique une zone numérique de 4 chiffres.

On aurait pu écrire aussi %2d et %4d mais alors le programme aurait affiché ceci :

Nous sommes le  3/ 1/2011 et il est 19:25:53.

Vous voyez la différence ? Le 0 indique à printf de remplir la zone avec des zéros au lieu de blancs.

A votre avis, si on écris %d au lieu de %2d, cela donne quoi ? A vous d'essayer ...




3.2 Les mois de l'année

On va compliquer un peu. Comment fait-on pour afficher les mois de l'année en lettres au lieu de chiffres ? Essayez donc ceci :


// date2.c

from std use calendar, console;

void main()
{
  const string mois[12] = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
                           "Juillet", "Aout", "Septembre", "Octobre", "Novembre",
                           "Decembre"};
  DATE_TIME now;

  get_datetime (out now);

  printf ("Nous sommes le %d %s %d\n", now.day, mois[now.month-1], now.year);
}

Voici le résultat :


Nous sommes le 3 Janvier 2011

A votre avis, comment fonctionne mois[now.month-1] ?

Le now.month indique le mois de 1 à 12. On en soustrait 1 parce que les indices du tableau mois[] commencent toujours à 0.

Remarquez aussi qu'on a utilisé %s pour indiquer un "string" au lieu d'une zone numérique comme précédemment.



3.3 Afficher un calendrier

Pour finir ce chapitre, nous allons afficher un calendrier avec tous les jours du mois.


// date3.c

from std use calendar, console;

void main()
{
  const string mois[12] = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
                           "Juillet", "Aout", "Septembre", "Octobre", "Novembre",
                           "Decembre"};
  const string jours_semaine[7] = {"Lundi", "Mardi", "Mercredi", "Jeudi",
                                   "Vendredi", "Samedi", "Dimanche"};
  DATE_TIME now;
  int       jour;

  get_datetime (out now);

  for (jour=1; jour<=max_days_in_month (now.month, now.year); jour++)
  {
    printf ("%9s : Le %2d %s %d\n",
            jours_semaine[day_of_week (jour,now.month, now.year)-1],
            jour,
            mois[now.month-1],
            now.year);
  }
}

La fonction max_days_in_month() calcule le nombre maximum de jours d'un mois, et la fonction day_of_week() calcule le jour de la semaine d'une date (1=lundi, 7=dimanche). Toutes ces fonctions sont définies dans le composant 'calendar'.

L'instruction 'for' permet de faire varier la variable 'jour' entre 1 et le nombre de jours maximum du mois en l'incrémentant de un (jour++) à chaque tour de boucle.


Essayez donc de modifier ce programme pour afficher autre chose ..



Chapitre 4 : les types de données


4.1 Entiers

Le Safe-c a un riche assortiment de types de données pour stocker les entiers.

Le tableau suivant en montre la liste :

nom du type synonyme utilisation mémoire intervalle
int1 tiny 1 byte de -128 à +127
int2 short 2 bytes de -32_768 à +32_767
int4 int 4 bytes de -2_147_483_648 à +2_147_483_647
int8 long 8 bytes de -9_223_372_036_854_775_808 à +9_223_372_036_854_775_807

Il existe aussi des types non-signés :

nom du type synonyme utilisation mémoire intervalle
uint1 byte 1 byte de 0 à 255
uint2 ushort 2 bytes de 0 à 65_535
uint4 uint 4 bytes de 0 à 4_294_967_295

Le type 'int' sera utilisé le plus souvent.

Les types plus petits peuvent être utiles :

. quand on doit déclarer beaucoup de variables de ce type, par exemple pour un grand tableau.
  Choisir un type plus petit permet alors d'économiser de la place mémoire.

. quand on doit fournir un type d'une certaine taille à une interface extérieure.

Voici un exemple qui utilise tous ces types:


// types.c

from std use console;

void main()
{
  int1 t;
  int2 s;
  int4 i;
  int8 l;

  byte  b;
  uint2 n;
  uint  u;

  t = 1;
  s = 32000;
  i = 1_000_000;                       // 1 million
  l = 1000L * 1000L * 1000L * 1000L;   // mille milliards !

  b = 0;
  n = 60000;
  u = 4_000_000_000;

  t = (tiny)(t * 100);    // conversion du résultat du calcul vers le type tiny 

  printf ("ceci sont des nombres signes : %d, %d, %d et %d\n", t, s, i, l);
  printf ("ceci sont des nombres non-signes : %u, %u et %u\n", b, n, u);

  printf ("le plus petit long est %d\n", long'min);
  printf ("le plus grand long est %d\n", l'max);
}

ce qui affiche :


ceci sont des nombres signes : 100, 32000, 1000000 et 1000000000000
ceci sont des nombres non-signes : 0, 60000 et 4000000000
le plus petit long est -9223372036854775808
le plus grand long est 9223372036854775807

En interne, tous les calculs entiers se font, soit en int, soit en uint, soit en long. Si le résultat est stocké dans un type plus petit, vous devez d'abord le convertir :


  int2 a = 1;
  int2 b = 2;
  int2 c = (int2)(a + b);

Les litéraux de type 'long' ont un L à la fin. Si l'une des opérandes a le type 'long', alors le résultat aura aussi le type 'long'.


  int  a = 1;
  long b = a + 1L;

Dans la fonction printf, les entiers signés sont représentés par %d et les non-signés par %u. On peut spécifier un nombre minimum de caractères (par exemple %10d), préfixer par des zéros au lieu de blancs (%010d), ou justifier à gauche (%-10d).

Les attributs 'min et 'max permettent de spécifier la plus petite ou plus grande valeur possible d'un type.



4.2 Nombres à virgule flottante

Pour les calculs mathématiques (par exemple la 3D), on utilise les types 'float' et 'double' :

nom du type synonyme utilisation mémoire nombre le plus minuscule nombre le plus gigantesque précision
float4 float 4 bytes 1.5E-45 3.4E+38 7 chiffres
float8 double 8 bytes 5.0E-324 1.7E+308 15 à 16 chiffres


Voici un exemple qui utilise ces types:


// types2.c

from std use console;

void main()
{
  float  f;
  double d;

  f = 16.0 * 1.0e+10;
  d = 1.0e150 / 7.25e34;

  printf ("ceci sont des nombres a virgule flottante : %f, %f ", f, d);
  printf ("qu'on peut representer aussi en notation scientifique : %e, %e\n", f, d);
}

ce qui affiche :


ceci sont des nombres a virgule flottante : 160000000000.000000, 137931034482758
60000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000.000000 qu'on peut representer aussi en notation scientifiq
ue : 1.600000e+11, 1.379310e+115


Dans la fonction printf, les types à virgule flottante sont représentés par %f en notation normale et par %e en notation scientifique (avec exposant).

On peut spécifier un nombre minimum de caractères (par exemple %10f), préfixer par des zéros au lieu de blancs (%010f), justifier à gauche (%-10f).

On peut aussi spécifier le nombre de décimales souhaitées (%.2f), ou combiner les deux, par exemple si on veut 12 caractères dont 6 décimales on écrira (%12.6f).

A remarquer que printf peut ne pas tenir compte de votre nombre de caractères (par exemple si vous spécifiez seulement %2f pour afficher un milliard).



4.3 Enumérations

Il y a essentiellement 2 types 'énumération' fortement utilisés :

. le type 'bool' qui peut avoir les valeurs 'false' ou 'true' et permet d'effectuer des opérations logiques.

. le type 'char' qui stocke les caractères ascii de tout ce qui s'affiche à l'écran.


Ces types consomment 1 byte. Voici un exemple de leur utilisation :


// types3.c

from std use console;

void main()
{
  bool  b;
  char  c;

  b = 1230 > 63+6;   // vrai
  b = true;

  c = '$';

  printf ("ceci est un bool : %u et ceci est un char : %c\n", b, c);
}

Ce qui affiche :


ceci est un bool : 1 et ceci est un char : $

Dans la fonction printf, le type 'bool' est spécifié comme un nombre non-signé (par %u) et s'affiche par un 0 pour faux et 1 pour vrai; le type char est spécifié par %c.

Signalons encore le type 'wchar' (en 2 bytes) qui permet de stocker tous les caractères internationaux, même chinois, et est spécifié par %C (C majuscule).




4.4 Enumérations créées par l'utilisateur

Il est possible de créer votre propre type énumeration. Imaginez que vous voulez écrire un jeu de cartes, vous pouvez définir un type énumération FAMILLE comme dans le programme suivant :



// types4.c

from std use console;

void main()
{
  enum FAMILLE {PIQUE, AS, CARREAU, TREFLE};

  FAMILLE f;

  f = CARREAU;

  f++;         // augmente f de 1, donc f a maintenant la valeur TREFLE

  printf ("f a la valeur           : %u\n", f);
  printf ("en clair cela veut dire : %s\n", f'string);

  printf ("le plus petit est %u\n", f'first);
  printf ("le plus grand est %s\n", f'last'string);
}

Ce qui affiche :


f a la valeur           : 3
en clair cela veut dire : TREFLE
le plus petit est 0
le plus grand est TREFLE

Dans la fonction printf, ce type énumération peut s'afficher de 2 manières différentes:

- soit avec %u, ce qui affichera son numéro de séquence commençant à 0, donc il affichera 0 pour PIQUE, 1 pour AS, etc ...

- soit la variable peut être convertie en string à l'aide de l'attribut 'string et s'afficher alors avec %s (donc ici TREFLE).

Les attributs 'first et 'last permettent de spécifier la première et la dernière valeur d'un type énumération.


Chapitre 5 : les tableaux


5.1 initialisation et affichage


Le programme suivant déclare un tableau de 6 entiers et le remplit de 3 manières différentes avant d'afficher ses éléments :


// tableau.c

from std use console;

void main()
{
  int tab[6];
  int i;

  tab = {12, 71, 33, 46, 84, 6};

  for (i=0; i<tab'length; i++)
    printf ("tab[%d] = %d\n", i, tab[i]);
  printf ("\n");
    
  tab[4:2] = {all => 1};

  for (i=0; i<tab'length; i++)
    printf ("tab[%d] = %d\n", i, tab[i]);
  printf ("\n");
    
  clear tab;

  for (i=0; i<tab'length; i++)
    printf ("tab[%d] = %d\n", i, tab[i]);
  printf ("\n");
}

Il initialise d'abord le tableau en y copiant un aggrégat :


  tab = {12, 71, 33, 46, 84, 6};

Ensuite, il remplace ses éléments 4 et 5 par la valeur 1 :


  tab[4:2] = {all => 1};

Et finalement, il remet tous ses éléments à zéro :


  clear tab;

Ce qui affiche :


tab[0] = 12
tab[1] = 71
tab[2] = 33
tab[3] = 46
tab[4] = 84
tab[5] = 6

tab[0] = 12
tab[1] = 71
tab[2] = 33
tab[3] = 46
tab[4] = 1
tab[5] = 1

tab[0] = 0
tab[1] = 0
tab[2] = 0
tab[3] = 0
tab[4] = 0
tab[5] = 0


Quand on réserve un tableau de 6 éléments, on dispose en fait des indices 0 à 5 car les tableaux commencent toujours à l'indice 0.

Remarquez la présence de l'attribut 'length. Il est équivalent à la valeur 6 mais a un gros avantage : si vous décidez d'agrandir votre tableau à 10 éléments il s'adaptera tout seul à la nouvelle taille.


5.2 Les tranches d'un tableau

Les tranches permettent de prendre, comme sur un gâteau, un ou plusieurs morceaux de tableau.

Une tranche est indiquée par la notation " tableau [ indice_début : nbre_d'elements ] " où indice_début indique l'indice du début de la tranche et nbre_d'elements indique le nombre d'éléments de la tranche.

L'exemple ci-dessus efface d'abord le tableau, ensuite il copie les valeurs {1, 2, 3} dans les 3 premiers éléments (tranche commençant à 0 de longueur 3).

Ensuite il recopie cette tranche dans les 3 éléments suivants du tableau.


// tableau2.c

from std use console;

void main()
{
  int tab[6];
  int i;

  clear tab;

  tab[0:3] = {1, 2, 3};

  tab[3:3] = tab[0:3];

  for (i=0; i<tab'length; i++)
    printf ("tab[%d] = %d\n", i, tab[i]);
}

Ce qui affiche :


tab[0] = 1
tab[1] = 2
tab[2] = 3
tab[3] = 1
tab[4] = 2
tab[5] = 3

Il est à noter que si vous enleviez l'instruction clear dans l'exemple ci-dessus, vous auriez l'erreur de compilation suivante :


line 14 col 14 : semantic : local variable 'tab' used without having been initialized 

Pourquoi ?

Parce qu'une variable n'est considérée initialisée que si elle a reçu une valeur complète de tous ses éléments en une fois. Or dans l'exemple on remplit seulement les 3 premiers éléments avant de la lire. Le Safe-c requiert que toutes les variables soient initialisées avant d'être lues, cela garantit des programmes déterministes qui se comportent de manière identique à chaque exécution.

5.3 Tri d'un tableau

Voici comment trier un tableau à l'aide du composant de librairie 'sorting':


// tableau3.c

from std use console, sorting;


int cmp (int a, int b)
{
  if (a < b)
    return -1;
  if (a > b)
    return +1;
  return 0;
}

package tri = new HeapSort (ELEMENT => int,
                            compare => cmp);

void main()
{
  int tab[6];
  int i;

  tab = {12, 71, 33, 46, 84, 6};

  tri.sort (ref tab);

  for (i=0; i<tab'length; i++)
    printf ("tab[%d] = %d\n", i, tab[i]);
}

Ce qui affiche :


tab[0] = 6
tab[1] = 12
tab[2] = 33
tab[3] = 46
tab[4] = 71
tab[5] = 84


Le composant 'sorting' contient plusieurs packages génériques pour trier des tableaux de n'importe quel type (en fait générique veut dire qu'ils sont indépendant de tout type).

Tout d'abord, il faut expliquer à la fonction de tri comment comparer deux éléments, ici deux 'int'. Pour cela, on déclare une fonction 'cmp' qui compare deux 'int' et qui renvoie -1 si le 1er est plus petit, 0 s'ils sont égaux et +1 si le 1er est plus grand.

Ensuite on "instancie" (ça veut dire on crée) un nouveau package de tri en donnant au package générique le type 'int' et notre fonction de comparaison. Il crée alors une fonction "sort" que nous pouvons utiliser.

Le résultat qui s'affiche est le tableau trié.



5.4 Vérification des longueurs à la compilation


// tableau4.c

from std use console;

void main()
{
  int tab[6];

  tab = {1, 2, 3};    // <-- ERREUR
}

L'exemple ci-dessus va vous donner une erreur à la compilation. Lors d'une assignation les longueurs de tableaux doivent concorder. Copier the aggregat de 3 valeurs dans un tableau de 6 éléments ne va forcément pas fonctionner.


5.5 Vérification des indices à l'exécution


// tableau5.c

from std use console;

void main()
{
  int tab[6];
  int i;

  for (i=0; i<100; i++)
  {
    printf ("remplissage de %d ..\n", i);
    tab[i] = i;
  }
}

L'exemple ci-dessus essaye de copier une valeur dans les indices 0 à 99 d'un tableau qui pourtant ne compte que 6 éléments. Si vous exécutez ce programme il va d'abord afficher :


remplissage de 0 ..
remplissage de 1 ..
remplissage de 2 ..
remplissage de 3 ..
remplissage de 4 ..
remplissage de 5 ..
remplissage de 6 ..

et ensuite il va "crasher" avec la fenêtre suivante :

Windows va envoyer un rapport sur ce crash à Microsoft et interroger la base de donnée pour voir si une solution existe pour ce problème. Evidemment comme Microsoft ne connait pas votre programme vous n'aurez pas de réponse ..

Pour plus d'infos sur les crash, rendez-vous au chapitre suivant ..



Chapitre 6 : les crashs et le tracing


6.1 Créer un traitement d'exception


// tableau5.c

from std use console, exception;

void main()
{
  int tab[6];
  int i;

  arm_exception_handler();
  
  for (i=0; i<100; i++)
  {
    printf ("remplissage de %d ..\n", i);
    tab[i] = i;
  }
}

Voici à nouveau l'exemple du chapitre précédent, mais avec deux modifications :

. on a ajouté le composant 'exception' dans la liste "use",

. on a ajouté un appel vers la fonction arm_exception_handler() tout au début du programme.

Cette fonction va "armer" un traitement d'exception spécial en cas de crash : au lieu d'envoyer un rapport de crash à Microsoft, elle va afficher le dialogue suivant :

On y voit que l'erreur s'est produite dans le fichier 'tableau5.c' à la ligne 15 !

Il ne vous reste donc plus qu'à ouvrir tableau5.c avec notepad, d'appuyer sur les touches Ctrl+G et de taper le numéro de ligne (ici 15), pour voir où le programme s'est crashé. Aha, c'est à la ligne "tab[i] = i;" cela n'a donc rien d'étonnant.



6.2 Les programmes distribués

Le traitement d'exception du chapitre précédent fonctionne que parce que le compilateur a placé dans votre répertoire un fichier "tableau5.dbg" avec la liste des lignes de code source de votre programme et des adresses processeur correspondantes.

Si vous distribuez votre programme sur internet vous n'allez probablement donner que "tableau5.exe". En cas de crash, la fenêtre d'erreur sera alors différente :

Comment retrouver la ligne d'erreur ici ? En utilisant le programme "bug.exe" qui est fourni avec le compilateur. A condition de disposer du fichier "tableau5.dbg" dans le répertoire courant, essayez de taper la commande suivante :


bug tableau5.dbg 401075

Le programme vous répondra alors :

ce qui vous permettra aussi de retrouver le fichier et le numéro de ligne de l'erreur.



6.3 Les rapports d'erreur

Si vous avez fermé le message d'erreur un peu trop vite, pas de panique, il n'est pas perdu. En effet, un rapport d'erreur CRASH-REPORT.TXT est placé dans le dossier du programme et il contient les mêmes informations :


crash date   : 03/11/2014 00:52:59
application  : C:\PROJECTS\SafeCSamples1\tableau5.exe
command line : tableau5
compile date : 03/11/2014 00:48:57

ACCESS_VIOLATION at rojects/safecsamples1/tableau5.c:15
401016
77120bb9
77120b8f
fffffffe

eax=00000006 ebx=0000000a ecx=7506d371 edx=0018faf4
esi=00000014 edi=00000013 ebp=0018ff88 esp=0018ff60



6.4 Le tracing

Jusque maintenant nous avons affiché le contenu des variables avec printf. Ce n'est cependant pas pratique si votre programme devient très grand et affiche beaucoup de lignes.

A la place des printf, on préfèrera donc utiliser un fichier de trace qu'on doit d'abord ouvrir au début du programme avec open_trace(), puis dans lequel on écrira des lignes de texte avec la fonction trace() qui s'utilise exactement comme printf() :


// test1.c

from std use exception, tracing;

void main()
{
  int tab[6];
  int i;

  open_trace ("test1.tra", 64*1024*1024);

  for (i=0; i<100; i++)
  {
    trace ("remplissage de l'élément %d\n", i);
    tab[i] = i;
  }
}


En cas de crash de votre programme, il vous suffit alors d'ouvrir le fichier de trace avec notepad (ici il s'appelle test1.tra) pour voir jusqu'à où votre programme était arrivé.

Dans cet exemple, le fichier contiendra :


11/01/10 22:03:55 : remplissage de l'élément 0
11/01/10 22:03:55 : remplissage de l'élément 1
11/01/10 22:03:55 : remplissage de l'élément 2
11/01/10 22:03:55 : remplissage de l'élément 3
11/01/10 22:03:55 : remplissage de l'élément 4
11/01/10 22:03:55 : remplissage de l'élément 5
11/01/10 22:03:55 : remplissage de l'élément 6

Remarquez que la date est inversée, ici 11/01/10 indique le 10 janvier 2011.

Quand le fichier de trace devient trop grand (>64 MB), il est automatiquement renommé en fichier .OLD et un nouveau fichier vide est créé. Vous ne risquez donc pas de remplir le disque dur. Vous pouvez spécifier une autre taille maximum dans open_trace().


Chapitre 7 : les strings


7.1 tableau de char et string


// str1.c

from std use console;

void main()
{
  string(4) nom1, nom2;

  nom1 = "Marc";
  nom2 = "Eric";

  printf ("Mon nom1 est : %s\n", nom1);
  printf ("Mon nom2 est : %s\n", nom2);
}

L'exemple ci-dessus déclare deux variables string de 4 caractères, ensuite il copie "Marc" et "Eric" dans ces variables et les affiche :


Mon nom1 est : Marc
Mon nom2 est : Eric

Un string c'est tout simplement un tableau de caractères. Tenez au lieu de :


  string(4) nom1, nom2;

on aurait pu écrire aussi l'une de ces 3 lignes strictement équivalentes :


  string  nom1(4), nom2(4);
  char[4] nom1, nom2;
  char    nom1[4], nom2[4];

C'est parce qu'en fait, le type "string" est prédéfini comme :


  typedef char[] string;

Remarquez qu'on aurait pu aussi, au lieu d'écrire ceci :


  nom1 = "Marc";

écrire cela :


  nom1 = {'M', 'a', 'r', 'c'};

Une chose est toutefois gênante, on ne sait recopier dans 'nom1' que des chaines de 4 caractères.

Si vous essayez ceci vous aurez une erreur :


  nom1 = "Luc";    // <-- ERREUR

Evidemment vous pouvez recopier Luc dans les 3 premiers caractères et nul dans le 4ème, ce qui va afficher Luc correctement parce que printf() s'arrête au caractère nul :


  nom1 = {'L', 'u', 'c', nul};    // OK

ou bien vous pouvez insérer le caractère nul dans la chaine, comme ceci :


  nom1 = "Luc\0";     // OK

Mais enfin tout ceci n'est pas pratique.

Comment faire pour manipuler des chaines de caractères plus facilement ?


7.2 Le composant 'strings'

En fait on utilisera le composant "strings" qui contient quelques fonctions très utiles :

La fonction strcpy() permet de recopier un string dans un autre string en remplissant la partie non-utilisée avec le caractère "nul" de valeur zéro.

La fonction sprintf() s'utilise comme printf(), seulement au lieu d'afficher le résultat sur la console elle le place dans le premier string. Remarquez qu'il faut toujours placer le mot-clé "out" devant la variable qui reçoit un résultat.


// str2.c

from std use console, strings;

void main()
{
  char nom[64], prenom[64], nom_complet[128];

  strcpy (out prenom, "Lucien");
  strcpy (out nom,    "Demoitier");

  sprintf (out nom_complet, "%s %s", prenom, nom);

  printf ("Mon nom est : %s\n", nom_complet);
}

ce qui affiche :


Mon nom est : Lucien Demoitier

La fonction strcat() permet d'ajouter un string à la fin d'un autre string. Remarquez qu'il faut spécifier le mot-clé "ref" devant le premier string parce que cette variable est "modifiée" :


// str3.c

from std use console, strings;

void main()
{
  char str[1000];

  strcpy (out str, "Bonjour ");
  strcat (ref str, " je ");
  strcat (ref str, " m'appelle ");
  strcat (ref str, " Lucien ");
  strcat (ref str, " Demoitier ");

  printf ("%s\n", str);
}

ce qui affiche :


Bonjour  je  m'appelle  Lucien  Demoitier


La fonction sscanf() effectue l'opération inverse de sprintf() : Elle examine un string et en extrait les différents champs qui le composent. Ici le format indique 4 champs (%*s %d %*s %d), on lui demande d'ignorer les champs avec une * et de recopier les deux champs numériques dans a et b.


// str4.c

from std use console, strings;

void main()
{
  char str[1000];
  int    a, b;

  strcpy (out str, "valeurs 12 et 18");

  sscanf (str, "%*s %d %*s %d", out a, out b);

  printf ("%d et %d\n", a, b);
}

ce qui affiche :


12 et 18


Dans l'exemple ci-dessous on a déclaré une constante (grâce au mot-clé const). Une constante ressemble à une variable, sauf qu'elle ne peut pas changer de valeur. Si vous utilisez souvent le même string, il est intéressant de ne le déclarer qu'une seule fois comme constante.


// str5.c

from std use console;

void main()
{
  const string TITLE = "Bonjour chers amis !";
  printf ("%s\n", TITLE);
}


L'exemple suivant montre qu'on peut imprimer une tranche d'un string. Ici on imprimera "njo" vu qu'on commence à l'indice 2 et qu'on imprime 3 caractères :


// str6.c

from std use console, strings;

void main()
{
  char str[1000];

  strcpy (out str, "Bonjour");

  printf ("%s\n", str[2:3]);
}


La fonction strlen() permet de demander la longueur d'un string jusqu'au caractère nul de remplissage ou jusqu'à la fin du tableau. Ici le programme va afficher 7 :


// str7.c

from std use console, strings;

void main()
{
  char str[1000];

  strcpy (out str, "Bonjour");

  printf ("le string a la longueur : %d\n", strlen(str));
}

Chapitre 8 : constantes, variables, typedef et ref


8.1 les constantes

Une constante est déclarée par le mot-clé const. Elle permet de donner un nom à une valeur qui ne change pas.

Il est de bon style d'écrire les constantes en majuscules.

Voici quelques exemples de constantes de différents types simples, tableau et structure :


  const int     MAX     = 45 * 100;
  const char    CARET   = '^';
  const string  WELCOME = "Hello " + " people" + "\n";
  const char[2] CRLF    = (char)13 + (char)10;
  const int^    NULL    = null;
  
  struct PRODUCT
  {
    char[4] code;
    float   value;
  }
  
  const PRODUCT prod1 = {"ABCD", 1.34};
  const PRODUCT prod2 = {code => "ABCD", value => 1.34};
  
  const PRODUCT[] prod_table = {{code => "AAAA", value => 1.31},
                                {code => "BBBB", value => 1.32},
                                {code => "CCCC", value => 1.33},
                                {code => "DDDD", value => 1.34}};



8.2 les types "jagged"

D'habitude on spécifie une taille quand on déclare un tableau dans une structure.

Il y a une exception à cette règle : quand on déclare une constante, on peut spécifier un tableau sans en préciser la taille.

Voici un exemple, la structure INFO ci-dessous est une "jagged structure" :


  from std use console;
  
  struct INFO
  {
    char[] code;   // un tableau de char sans longueur est autorisé ici
    float  value;
  }

  const INFO info1 = {"bananas", 12.5};
  const INFO info2[] = {{"melon",   8.6}, 
                        info1,
                        {"apple", 120.4}}; 
                        
  void main()
  {
    int i;
    for (i=0; i<info2'length; i++)
      printf ("%-10s %6.1f\n", info2[i].code, info2[i].value);
  }  

ce qui donne :


melon         8.6
bananas      12.5
apple       120.4

Il est à noter que la structure INFO de l'exemple ci-dessus ne peut pas être utilisée pour déclarer une variable. On peut l'utiliser uniquement pour déclarer des constantes, des paramètres de fonction de mode IN, ou des références vers celles-ci.

Dans une structure "jagged" on peut aussi déclarer des structures variantes sans donner de valeur pour le paramètre discriminant.


8.3 la syntaxe des littéraux constants

Nombres entiers
---------------
Les nombres entiers peuvent avoir une forme décimale, hexadécimale ou binaire.

Exemples:
  64           // compatible avec n'importe quel nombre entier signé ou non signé
  65_000_000   // avec souligné pour une meilleure lisibilité
  0xFF         // valeur hexadécimale
  0b1111_0000  // valeur binaire
  176L         // long

Le caractère souligné est autorisé dans un nombre entier pour une meilleure lisibilité.

La valeur entière la plus négative (-2^63) n'est pas représentable par un litéral entier,
l'expression long'min peut être utilisée à la place.

Si le littéral a un suffixe L, il est de type 'long'.


Nombres à virgule flottante
---------------------------
Un nombre à virgule flottante peut avoir le type float ou double.

Exemples:
  1.2f
  3.023_223E+2d
  0x80.0p-1

Un suffixe "f" indique un type 'float', un suffixe "d" indique un type 'double'.
Si aucun suffixe n'est présent, le type est déterminé d'après le contexte où apparait le nombre.

Un nombre hexadécimal (avec préfixe 0x) est utilisé pour une représentation machine précise :
la mantisse est alors spécifié dans la base 16 et l'exposant est spécifié dans la base 10.


Littéraux caractère
-------------------
Les littéraux caractère sont des valeurs constantes de type char (ou de type wchar s'ils
ont un préfixe 'L').

Exemples:
  'A'
  'É'
  '\N'
  '\x80'    // code 80 hexa
  L'\x00FF' // code FF hexa (le préfixe 'L' indique un type wchar)

Les séquences d'échappement suivantes sont supportées :

  \' 0x0027 Single quote
  \" 0x0022 Double quote
  \\ 0x005C Backslash
  \0 0x0000 Null
  \a 0x0007 Alert
  \b 0x0008 Backspace
  \f 0x000C Form feed
  \n 0x000A New line
  \r 0x000D Carriage return
  \t 0x0009 Horizontal tab
  \v 0x000B Vertical tab

  
Littéraux string
----------------
Les littéraux string sont des valeurs constantes de type string (ou de type wstring
en cas de préfixe 'L').

Exemples:
  "A"
  "Hello\n"
  "Hello" + " World"
  L"Hallöchen"
  L"\x1234"

Les littéraux string de plus de 128 caractères génèrent une erreur de compilation.
Des littéraux string plus longs peuvent être construits en utilisant l'opérateur
de concaténation '+'.

8.4 les variables

Une variable sert à nommer une case mémoire qui contiendra une valeur d'un certain type.

Voici quelques exemples:


  int        count, i=0;
  char[10]   c;
  string(80) buffer;
  PRODUCT[]^ ptr = new PRODUCT[nb];

Les variables peuvent être déclarées à différents endroits :

  1. variable locale

    On appelle variable locale une variable déclarée à l'intérieur d'une fonction ou d'un bloc d'instructions.

    Une telle variable est créée lors de l'appel de la fonction, et elle disparait lorsque la fonction se termine. En outre, si la fonction est appelée plusieurs fois récursivement, plusieurs occurences distinctes de la variable vont co-exister.

  2. variable globale

    Une variable globale se déclare, soit dans un fichier .h, soit au début d'un fichier .c (après les clauses use mais avant toute déclaration de corps de fonctions).

    Si elle est déclarée dans un ficher .h elle sera accessible à toutes les parties du programme qui importent le composant .h par une clause use.

    Une variable globale est créée au démarrage du programme et elle disparait lorsque le programme se termine. Son désavantage, par rapport à une variable locale, est qu'elle occupe de la mémoire pendant toute la durée du programme.

  3. variable dynamique

    Une variable dynamique est allouée par l'allocateur new et son adresse mémoire sera stockée dans une variable pointeur.

    Elle existe tant que sa mémoire n'a pas été libérée avec l'instruction free.


Une variable globale doit être déclarée avant toute déclaration des corps de fonctions, pour des raisons de lisibilité.

Ceci va donc donner une erreur :


  void treat()
  {
  }
  
  int i;    // <-- incorrect
  
  void main()
  {
  }


En effet, l'ordre correct de déclaration est :


  int i;    // <-- correct
  
  void treat()
  {
  }
  
  void main()
  {
  }

Maintenant, si vous souhaitez tout de même déclarer la variable entre deux corps de fonctions, il existe un truc, c'est de déclarer un package qui va englober la déclaration, comme ceci :


  void treat()
  {
  }
  
  package MES_VARIABLES
    int i;    // <-- correct
  end MES_VARIABLES;
  
  void main()
  {
  }



8.5 déclaration de type

Le mot-clé typedef sert à donner un nom à un type.

Exemples:


  typedef char[]   MYSTRING;      // type tableau sans taille
  typedef char[10] NAME;          // type tableau
  typedef byte[]^  POINTEUR;      // type pointeur vers tableau

  MYSTRING str(100);
  NAME     name;
  POINTEUR p;


typedef permet aussi de déclarer des types vers fonction, ce qui permet les appels de fonction indirects.

En voici un exemple:


  from std use console;
    
  typedef void TREAT (int nr);    // type pointeur vers fonction

  void A (int i)
  {
    printf ("A : %d\n", i);
  }
  
  void B (int i)
  {
    printf ("B : %d\n", i);
  }
  
  void main ()
  {
    TREAT treat;         // variable de type 'pointeur vers fonction'
    
    treat = A;           // la variable reçoit l'adresse de la fonction A
    treat (nr => 100);   // appel indirect de la fonction A()
    
    treat = B;           // la variable reçoit l'adresse de la fonction B
    treat (nr => 200);   // appel indirect de la fonction B()
  }

ce qui affiche :


A : 100
B : 200


8.6 déclaration de référence

Une référence permet de donner un synonyme à une variable ou à un paramètre.

Par exemple, imaginez que vous ayez le programme suivant :


  struct STUDENT
  {
    int  nr;
    int  birth;
    char grade;
    bool passed;
  }
  
  STUDENT student[100];
  
  void main()
  {
    int i = 5;
    
    student[i].nr    = 5;
    student[i].birth = 1967;
    student[i].grade = 'F';
    student[i].passed = (student[i].grade <= 'C');
  }

Ce programme peut être amélioré. En effet, il répète 4 fois student[i] ce qui nécessite 4 évaluations de l'indice [i].

Voici une version utilisant une référence qui n'évalue [i] qu'une seule fois:


  struct STUDENT
  {
    int  nr;
    int  birth;
    char grade;
    bool passed;
  }
  
  STUDENT student[100];
  
  void main()
  {
    int i = 5;

    {
      ref STUDENT s = student[i];
      s.nr     = 5;
      s.birth  = 1967;
      s.grade  = 'F';
      s.passed = (s.grade <= 'C');
    }
  }

Attention à ne pas oublier le mot-clé ref sinon vous déclareriez simplement une nouvelle variable. Ici s est bien un synonyme de student[i].

Voici d'autres exemples :


  ref char   ch    = buffer[i];     // reference to array element
  ref string str   = buffer[0:3];   // reference to array slice
  ref int    age   = student.age;   // reference to age field


Il y a un piège lorsqu'on utilise une référence d'un pointeur. Pendant la durée où la référence est active, celle-ci va bloquer la variable désignée par le pointeur de sorte que vous ne puissiez pas la libérer avec free.

Exemple:


  void main()
  {
    char[]^ buffer = new char[5];  // réservation de 5 caractères

    ref char[] table = buffer^;    // table est une référence vers buffer^

    table = "Hello";               // copie d'une valeur vers le tableau

    free buffer;                   // libération du buffer
  }

Le programme ci-dessus va crasher sur la commande free parce que la référence bloque buffer.

La manière correcte pour éviter ce problème est d'appeler free lorque la référence n'est plus active, en plaçant la référence dans un bloc d'instructions comme ceci :


  void main()
  {
    char[]^ buffer = new char[5];  // réservation de 5 caractères

    {
      ref char[] table = buffer^;  // table est une référence vers buffer^
      table = "Hello";             // copie d'une valeur vers le tableau
    }

    free buffer;                   // libération du buffer
  }

Chapitre 9 : calculs arithmétiques et conversions


9.1 Opérateurs numériques

 Opérateur      Symbole   nombre de cycles CPU
 ===========    =======   ====================
 Ajouter         +             1 µsec
 Soustraire      -             1 µsec
 Multiplier      *             4 µsec
 Diviser         /            40 µsec
 Reste Division  %            40 µsec
 Shift Gauche    <<            1 µsec
 Shift Droite    >>            1 µsec

En plus d'être utilisés sur des entiers et des float, les opérateurs "+" et "-" peuvent aussi être utilisés pour ajouter ou soustraire un entier d'un type énumération. Exemple:

  enum COLOR {BLUE, RED, GREEN};
  COLOR col = BLUE + 2;     // GREEN
  printf ("col = %s\n", col'string);

Les opérateurs / (division) et % (reste de la division) provoquent un crash du programme si l'argument de droite égale zéro. A noter que l'opérateur % n'est autorisé que sur des entiers.

Les opérateurs de shift permettent une multiplication ou division très rapide par une puissance de 2. Par exemple au lieu de diviser par 64 avec "a/64", vous pouvez shifter par 6 "a>>6", parce que 2 exposant 6 donne 64. Quand votre programme doit aller très très vite le shift est intéressant surtout pour remplacer la division qui est tout de même environ 40 fois plus lente qu'un shift sur les processeurs actuels.

  int a = 2;
  int b = (a * (a + 1)) >> 1;

9.2 Opérateurs d'incrémentation et de décrémentation

 Opérateur       Symbole
 ===========     =======
 Ajouter 1        ++
 Soustraire 1     --

L'opérateur d'incrémentation "++" permet d'ajouter un à une variable et en même temps de l'évaluer. L'opérateur peut être placé devant ou derrière la variable, ce qui a un effet différent sur l'évaluation : s'il est placé devant, l'évaluation renvoie la valeur après incrémentation, sinon elle renvoie la valeur avant incrémentation. Exemple:

 int i = 0;
 int j = ++i;  // j == 1, i == 1

tandis que :

 int i = 0;
 int j = i++;  // j == 0, i == 1

9.3 Opérateur de concaténation

 Opérateur      Symbole
 ===========    =======
 concaténer       +

L'opérateur "+" peut concaténer des chaines de caractères constantes de type string ou wstring. Exemple:

  const string WELCOME = "Welcome to " + "Safe-C programming\n";
  const string WELCOME2 = WELCOME + "We hope you have a nice day\n";

9.4 Opérateurs de masque de bit

 Opérateur                 Symbole
 ===========               =======
 ET logique                   &
 OU logique                   |
 OU exclusif                  ^
 Inversion                    ~

Ces opérateurs travaillent bit par bit sur la représentation binaire des valeurs.
Le ET logique renvoie un bit 1 seulement si les deux valeurs ont un bit de valeur 1.
Le OU logique renvoie un bit 1 seulement si au moins une valeur a un bit de valeur 1.
Le OU exclusif renvoie un bit 1 seulement si une valeur a un bit de valeur 1 mais pas l'autre.
L'inversion ne prend qu'un seul argument et change tous les bits 1 à 0, et 0 à 1.

  int i = 17 & 3;   // 1

  17 :   10001
   3 :   00011
  ------------
   1     00001

9.5 Opérateurs de comparaison

 Opérateur                 Symbole
 ===========               =======
 plus petit que               <
 plus petit ou égal           <=
 plus grand que               >
 plus grand ou égal           >=
 égal à                       ==
 pas égal à                   !=
 pas                          !

Les opérateurs de comparaison permettent de comparer deux valeurs numériques ou de type énumération. Le résultat est une valeur de type bool, true ou false.

  int  i = 2;
  int  j = 3;
  bool b = i <= j;   // true

L'opérateur "!" ne prend qu'un seul argument booléen à droite et inverse les valeurs false et true.

  bool b = !(i <= j);   // false

Les opérateurs == et != peuvent aussi comparer des pointeurs.

  if (p != null)

9.6 Opérateurs booléens

 Opérateur                 Symbole
 ===========               =======
 ET                           &&
 OU                           ||
 OU exclusif                  ^

Les opérateurs booléens permettent de tester plusieurs conditions en une fois.

  if (a == 2 && b == 3)   // les deux doivent être vrais
  if (a == 2 || b == 3)   // au moins l'un des deux doit être vrai
  if (a == 2 ^ b == 3)    // l'un d'eux doit être vrai, mais pas les deux

A noter qu'on peut combiner plusieurs opérateurs booléens identiques :

  if (a == 2 && b == 3 && c == 4 && d == 5)

par contre, si les opérateurs sont différents, vous devrez utiliser des parenthèses pour lever les ambiguités.

  if ((a == 2 && b == 3) || (c == 4 && d == 5))

Contrairement à tous les autres opérateurs, les opérateurs && et || n'évaluent leur argument de droite que si nécessaire. C'est utile pour éviter les divisions par zéro.

  if (den == 0 || num/den > limit)   // num/den n'est évalué que si den n'est pas zéro
    ...
  if (den != 0 && num/den > limit)   // num/den n'est évalué que si den n'est pas zéro
    ...

9.7 Valeur conditionnelle

L'opérateur "? :" permet de tester une condition et de donner une valeur si elle est vraie et une autre si elle est fausse. Exemple :

  average = (count != 0) ? sum/count : 0;

Cet exemple renvoie dans average la valeur 'sum/count' si count n'est pas zéro, sinon elle renvoie zéro.

9.8 Priorités

Les opérateurs ne sont pas toujours exécutés de gauche à droite. Dans l'exemple suivant, la multiplication est exécutée avant l'addition, ce qui fait que le résultat sera 14, et non pas 20.

  int i = 2 + 3 * 4;      // == 14

Si vous voulez que l'addition soit exécutée d'abord, vous devez utiliser des parenthèses, comme ceci :

  int i = (2 + 3) * 4;    // == 20

Les opérateurs ont les priorités d'évaluation suivantes :

  priorité des opérateurs
  -----------------------
  Unaire               +   -  !  ~  --  ++
  Terme                *   /  %
  Somme                +   -
  Expression simple    <<  >>
  Relation             ==  !=  <  >  <=  >=
  Condition            &&  ||  &  |  ^
  Test Conditionnel    ?  :

9.9 Conversions

Le Safe-C n'autorise pas le mélange de types signés (int) et non-signés (uint). Par conséquent, si vous voulez additionner un type signé et un type non-signé vous devrez d'abord convertir l'un des deux.

  int  i, j;
  uint u;

  i = 2;
  u = 34;

  j = i + (int)u;    // conversion de u vers le type int
  u = (uint)j;       // conversion de j vers le type uint

Si vous utilisez les petits types int1 ou int2 pour stocker le résultat d'un calcul, vous devez d'abord convertir le résultat.

  int  a, b;
  int1 i;

  a = 34;
  b = 12;

  i = (int1)(a + b);   // conversion vers le type int1

Si vous utilisez le petit type float pour stocker une valeur de type double vous devez d'abord convertir le résultat.

  float f;
  f = (float)sin(angle);

Si vous voulez convertir un type bool en type int, vous pouvez le convertir. Le résultat sera égal à 0 ou 1.

  int i;
  i = (int)(a < b);   // résultat 0 or 1

Si vous voulez convertir un type char en type int, vous pouvez le convertir.

  char c = 'A';
  int  i;
  i = (int)c;

Si vous voulez convertir un type float en type int, vous pouvez le convertir. Attention, le résultat sera tronqué. Vous pouvez utiliser les fonction floor(), round() ou ceil() du composant math pour différents arrondis.

  float f = 3.1415;
  int   i;

  i = (int)f;           // troncage (enlever la partie fractionnaire)
  i = (int)floor(f);    // arrondi vers le bas (le plus négatif)
  i = (int)round(f);    // arrondi vers l'entier le plus proche
  i = (int)ceil(f);     // arrondi vers le haut (le plus positif)

9.10 Assignations combinées

Les assignations peuvent être combinées avec un opérateur. Voici quelques exemples et leur équivalent :

  int i = 1;

  i += 2;   // i = i + 2;
  i -= 3;   // i = i - 3;
  i *= 4;   // i = i * 4;
  i &= 1;   // i = i & 1;
  i <<= 1;  // i = i << 1;


Chapitre 10 : instructions et blocs


10.1 clear

L'instruction clear permet d'effacer une ou plusieurs variables, donc d'y placer la valeur zéro.


  int   i, j;
  float f;
  
  clear i, j, f;   // i = 0; j = 0; f = 0.0;


10.2 L'assignation

L'assignation "=" permet de calculer une valeur (à droite) et de placer le résultat dans la variable (à gauche).


  int     i, j, k[2];
  float   f;
  char[3] tar;
  
  i = 2;
  j = i * 2 + 345;
  f = (float)j * 3.1415927;
  k = {i, j};
  tar = "ABC";

L'assignation combinée est une forme abbréviée valable pour les opérateurs suivants :

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

  int j = 2;
  j += 3;   // j = j + 3;


10.3 L'incrémentation ou décrémentation

Pour ajouter un ou soustraire un à une variable entière ou de type énumération, on utilise au choix l'une des formes abbréviées :
  i++;    // i = i + 1;
  ++i;    // i = i + 1;
  
  i--;    // i = i - 1;
  --i;    // i = i - 1;


10.4 l'appel de fonction

L'appel d'une fonction est indiquée par un nom suivi d'une liste de paramètres entre parenthèses. S'il n'y a pas de paramètres on indique une paire de parenthèses vides.
  t = ticks();         // pas de paramètres
  printf ("Hello");
  rc = fcreate (out file, "file.txt", ANSI);

Il existe 3 mode de passage des paramètres : IN, REF et OUT.

Le mode IN est celui par défaut quand on ne spécifie rien : il passe la valeur calculée à la fonction.

Le mode REF indique qu'on passe une variable à la fonction pour que celle-ci puisse éventuellement la modifier.

Le mode OUT signifie que la fonction va nous renvoyer un résultat dans cette variable. Contrairement aux deux autres modes, il n'est pas nécessaire d'initialiser la variable avec une valeur avant l'appel.


  int valeur1 = 1;
  char tableau2[10];
  int resultat3;
  
  clear tableau2;
  ma_fonction (valeur1, ref tableau2, out resultat3);

A noter que le mot-clé "in" n'existe pas. Quand on souhaite le mode IN on ne spécifie pas de mode.

Pour rendre un appel de fonction plus clair, on peut spécifier le nom des paramètres suivi d'une flèche "=>" :


  i = strstr (text => "WiZZ", fragment => "i");
  
  init_draw (out dc     => my_dc,
                 buffer => buf,
                 width  => 100,
                 height => 200);

Une fonction peut avoir une valeur par défaut pour les derniers paramètres. Ceux-ci sont alors optionnels.


    void put_int (int value, int base = 10);

    put_int (i, base => 16);
    put_int (i);                 // assumes base 10


10.5 return

L'instruction "return" permet de terminer une fonction et éventuellement de renvoyer un résultat. On ne peut renvoyer que des valeurs simples, pas des valeurs composées comme des tableaux ou des structures.


  int sum (int a, int b)
  {
    return a + b;
  }

  void proceed (int f)
  {
    if (f == 2)
      return;
    // ...
  }


10.6 bloc

Une instruction de bloc regroupe une série d'instructions. Les instructions if, while et for nécessitent soit une seule instruction, soit un bloc. Au début d'un bloc on peut déclarer des variables qui ne sont accessibles qu'à l'intérieur du bloc.


  {        // Instruction de bloc
    int i;
    H();
    I();
  }


10.7 if

Une instruction if permet l'exécution conditionnelle.


   if (b)
   {
     // ...
   }

   if (b)
     ;       // ; est une instruction vide
   else
     ;


10.8 switch

L'instruction switch permet une exécution conditionnelle à plusieurs branches.


  switch (i)
  {
    case -1:    // quand i vaut -1
      f();
      break;

    case 1:     // quand i vaut 1
      g();
      break;

    default:    // branche par défaut si aucune valeur ne correspond
      break;
  }


10.9 while

L'instruction 'while' permet la répétition d'instructions tant qu'une condition est vraie.


  compteur = 0;
  
  while (compteur < 10)
    compteur++;
    
  while (compteur < 20)
  {
    printf ("compteur = %d\n", compteur);
    compteur++;
  }


10.10 for

L'instruction 'for' est une forme plus compacte de l'instruction 'while'.

Elle est composée de 3 parties séparées par deux points-virgules. La première partie n'est exécutée qu'une fois, la seconde partie est une condition qui est testée avant chaque tour de boucle pour voir si on continue à boucler, et la 3ème partie est exécutée après les instructions à répéter, à la fin de chaque boucle.

Les parties 1 et 3 peuvent êtres vides ou contenir plusieurs instructions séparés par des virgules.

Si la partie 2 est vide les instructions vont boucler sans fin (sauf s'il y a un break ou un return à l'intérieur).


  for (compteur=0; compteur<10; compteur++)
    ;
    
  for (i=0; i<arg'length; i++)
    printf ("arg %d : %s\n", i, arg[i]);

  for ( ; p!=null; i++,j+=3,p=p^.next)
    ;

  for (;;)   // boucle infinie
    ;


10.11 break

L'instruction 'break' est utilisée pour quitter l'instruction 'while', 'for' ou 'switch' la plus interne.


   for (i=0; i<10; i++)
   {
     if (i == j)
       break;
   }


10.12 continue

L'instruction 'continue' est utilisée pour passer à la prochaine itération d'une instruction 'while' ou 'for'.


   for (i=0; i<10; i++)
   {
     if (i == j)   // skip j
       continue;
     printf ("i = %d\n", i);
   }


10.13 free

L'instruction 'free' libère l'objet heap désigné par un pointeur.


  free p;
  free null;
  free f();

Libérer un pointeur null n'a aucun effet.

Une erreur d'exécution se produit si l'objet heap est toujours référencé par une référence ou un paramètre, ou s'il a déjà été libéré.

10.14 assert

L'instruction 'assert' provoque un arrêt du programme si une condition est fausse.


  assert TABLE_SIZE%2 == 0;  // TABLE_SIZE doit être divisible par deux


10.15 abort

Une instruction 'abort' arrête le programme.


    switch (reponse)
    {
      case 'Y':
        printf ("oui");
        break;

      case 'N':
        printf ("non");
        break;

      default:
        abort;   // ne devrait jamais se produire
    }



10.16 sleep

Une instruction 'sleep' suspend le programme pour la durée spécifiée, en secondes.


  sleep 10;   // attendre 10 secondes
  sleep 1.5;  // attendre 1.5 secondes
  sleep 0;    // abandonner la tranche de temps en cours pour ce thread

L'expression doit avoir un type entier ou virgule flottante. Les durées de plus de 86400 secondes (un jour) ou plus petit que -86400 secondes ne sont pas autorisées. Une durée négative est équivalente à zéro.

10.17 code

Une instruction 'code' insère du code machine directement dans le programme.

Elle n'est autorisée que dans une section unsafe.


  #begin unsafe
  _asm {0xC2 0x08 0x00};   // ret 8
  #end unsafe


10.18 _unused

Une instruction '_unused' sert à supprimer l'avertissement du compilateur qu'une variable ou un paramètre est inutilisé.


   _unused par1;


10.19 Run

L'instruction "run" permet de lancer un thread qui va s'exécuter en parallèle avec le reste de votre programme. Les paramètres de la fonction thread sont limités à un seul paramètre de type int.


  from std use console;

  void my_thread (int nr)
  {
    printf ("this is my_thread %d\n", nr);
  }

  void main()
  {
    int rc;
    rc = run my_thread (1);    // rc == 0 si le thread a bien démarré.
    rc = run my_thread (2);    // rc == 0 si le thread a bien démarré.
    rc = run my_thread (3);    // rc == 0 si le thread a bien démarré.
    printf ("this is main\n");
    sleep 1.0;
  }

Ce qui affiche :


this is main
this is my_thread 2
this is my_thread 1
this is my_thread 3

Chapitre 11 : fonctions, paramètres, main et récursivité


11.1 Déclaration de fonctions

Une déclaration de fonction précise les paramètres, valeur de retour et options d'une fonction.


  int start_treatment ();
  int treat (string name, char option);
  void sum (int a, int b, out c);
  void operate (ref TABLE table);
  void print (int value, int width = 1);

Il y a 3 modes de passage de paramètres :

  mode  direction accès au paramètre
  ====  ========= ==================
  in      -->     lecture seule
  ref     <->     lecture et écriture
  out     <--     écriture d'abord, puis lecture et écriture

Le mode "in" permet de passer une valeur à la fonction. A l'intérieur de la fonction, un paramètre de mode "in" ne peut pas être modifié. A noter que le mot-clé "in" n'existe pas, quand on souhaite spécifier le mode "in" on n'indique aucun mode.

Le mode "ref" passe l'adresse d'une variable à la fonction afin que celle-ci puisse être modifiée.

Le mode "out" permet à la fonction de retourner une valeur. A l'intérieur de la fonction, un paramètre de mode "out" est considéré comme non-initialisé et doit recevoir une valeur avant que la fonction ne se termine.

  void my_func (int a, ref int b, out c)
  {
    a = 1;  // erreur : a ne peut être modifié
  }         // erreur : c doit recevoir une valeur

En interne, les paramètres de mode "in" sont passés par valeur pour des types simples et par adresse pour des types composés. Les paramètres de mode "ref" et "out" sont toujours passés par adresse. Pour des paramètres de type "tableau sans longueur" ou "struct variante sans discriminant", un paramètre additional caché est passé avec la longueur du tableau ou la valeur du discriminant. C'est ce qui permet d'utiliser l'attribut 'length pour connaitre la longueur du tableau à l'intérieur de la fonction.

  void my_func2 (string str)
  {
    int len = str'length;
  }

Si un paramètre de mode "in" a une valeur constante par défaut (exemple: 'width' ci-dessus), alors le paramètre correspondant est optionnel lors de l'appel de la fonction :

  print (value => 10, width => 2);
  print (value => 10);              // width == 1 par défaut

Le type de retour d'une fonction peut être soit un type simple (donc tableau, struct, union, opaque ou générique ne sont pas autorisés), ou "void" si la fonction n'a pas de paramètres.

  string concat (string a, string b); // erreur: valeur de retour doit être un type simple.

Les options suivantes existent :

1) inline
---------
  inline int treat (char count);

indique que la fonction devrait être insérée en ligne au lieu d'être appelée.

2) callback
-----------
  [callback]
  int MainWin (HWND hwnd, UINT message, WORD w, LONG l);

indique que la fonction sera appelée par le système d'exploitation.

3) extern
---------
  [extern "KERNEL32.DLL"]
  int GetStdHandle (int nStdHandle);

indique que le corps de la fonction se trouve dans une DLL externe.

Les options callback et extern ne sont utilisables quand dans une section unsafe 
(voir pointeurs unsafe).

11.2 Corps de la fonction

Le corps de la fonction définit son fonctionnement interne.

  void somme (int a, int b, out int c)
  {
    c = a + b;
  }

Le préfixe "public" est obligatoire pour un corps de fonction si cette fonction a été pré-déclarée dans un fichier .h de même nom que le .c :


  // p.h
  void somme (int a, int b, out int c);    // déclaration

  // p.c
  public void somme (int a, int b, out int c)   // corps avec le mot clé "public"
  {
    c = a + b;
  }

Pour utiliser la fonction somme vous devrez importer le fichier p.h à l'aide d'une clause "use p;"

Une fonction ayant un type de retour non-void doit se terminer par une instruction return ou abort.


11.3 fonction main

Le point d'entrée dans un programme est une fonction appelée 'main'. Il ne doit y avoir qu'une seule fonction de ce nom.

Elle doit avoir le type de retour void ou int. En cas de type int, la valeur est renvoyée au système d'exploitation.


  int main()
  {
    return -1;
  }

main() doit avoir, soit aucun paramètre, soit un seul paramètre de mode 'in' et de type string [ ] qui recevra un tableau de string avec les arguments spécifiés au lancement du programme. Le 1er argument (à l'indice 0) est le nom du programme lui-même.


  // prog.c
  
  from std use console;
  
  void main(string[] par)
  {
    int i;
    for (i=0; i<par'length; i++)
      printf ("parametre %d : %s\n", i, par[i]);
  }

ce qui affiche :


C:\SafeC> prog un deux trois
parametre 0 : prog
parametre 1 : un
parametre 2 : deux
parametre 3 : trois



11.4 récursivité

Une fonction peut s'appeler elle-même, ce qui permet les appels récursifs. Il faut cependant limiter la profondeur de la récursion parce qu'un nombre trop grand d'appels récursifs va provoquer un crash du programme. Ce nombre dépend du nombre de variables locales déclarées dans la fonction.


// recursif.c

from std use console;

int factorielle (int i)
{
  if (i == 1)
    return 1;
  return i * factorielle (i-1);  // la fonction s'appelle elle-même ici
}

void main()
{
  int n;
  for (n=1; n<=10; n++)
    printf ("factorielle(%d) = %d\n", n, factorielle(n));
}

avec le résultat:


factorielle(1) = 1
factorielle(2) = 2
factorielle(3) = 6
factorielle(4) = 24
factorielle(5) = 120
factorielle(6) = 720
factorielle(7) = 5040
factorielle(8) = 40320
factorielle(9) = 362880
factorielle(10) = 3628800

Chapitre 12 : unités de compilation et types opaques


12.1 unités de compilation

Un grand programme Safe-C est composé de multiples fichiers avec les extensions .h et .c

Les fichiers .h et .c vont toujours par paires, sauf le fichier .c principal pour lequel le .h est optionnel.

Un fichier .h et son .c correspondant forment une "unité de compilation" :
. le fichier .h contiendra les déclarations "publiques" utilisables à l'extérieur de l'unité de compilation
. tandis que le fichier .c qui est la partie "privée" contiendra tous les corps de fonction et autres déclarations non-visibles à l'extérieur.

Les fichiers .h définissent donc les interfaces entre toutes les parties d'un programme. Voici un exemple de fichier .h :


// plotter.h

struct POINT
{
  float x, y;
}

const POINT ORIGIN = {x => 0.0, y => 0.0};

void move (POINT from, POINT to);

L'interface plotter.h déclare une structure POINT, une constante ORIGIN et une fonction move qui pourront être utilisés par un programme p.c comme ceci :


// p.c

use plotter;

void main()
{
  move (ORIGIN,     {1.0, 2.0});
  move ({1.0, 2.0}, {2.0, 4.0});
  move ({2.0, 4.0}, ORIGIN);
}

La clause "use plotter;" au début du programme permet de charger le fichier plotter.h et de pouvoir utiliser ses déclarations. Remarquez que si plotter.h et .c se trouvent ailleurs sur le disque, il faudrait donner le chemin relatif pour l'atteindre. Par exemple s'ils se trouvent dans le sous-folder "hardware" :

use hardware/plotter;

A quoi ressemble plotter.c, la partie privée ? Et bien elle va contenir toute sortes de fonctions pour faire fonctionner un plotter, mais en tout cas elle devra contenir un corps pour la fonction move(). Vu que la fonction move() est déclarée dans le .h, le mot-clé public est obligatoire dans le .c :


// plotter.c

void plot (POINT p)
{
  ...
}

public void move (POINT from, POINT to)  // <-- mot-clé "public" obligatoire
{
  POINT p;
  p = from;
  while (p.x < to.x)
  {
    plot (p);
    p.x += 0.1;
  }
  // ...
}


12.2 les types opaques

Dans certains cas, on souhaite déclarer une structure dans le .h mais on ne souhaite pas que les champs de cette structure soient accessibles à l'extérieur. On utilisera alors un type opaque.

Par exemple, le composant de librarie file.h déclare une structure FILE comme ceci :


// files.h

struct FILE;   // ceci est un type OPAQUE car on ne spécifie pas les champs de la structure

int fopen (out FILE file, string filename);

Les utilisateurs de file.h ne pourront pas faire grand-chose avec le type FILE, à part déclarer des variables de ce type, les effacer par un clear et passer ces variables dans les fonctions déclarées dans file.h telles que fopen(). En particulier, ils ne pourront pas prendre une copie de la variable.

Evidemment la structure FILE sera re-déclarée une 2ème fois dans le fichier file.c, mais cette fois-ci avec tous les champs accessibles uniquement dans le .c.


// file.c

struct FILE
{
  int      h;             // windows handle (<=0 means closed file)
  int      error;         // error code from last fill_buffer or flush_buffer
  ENCODING coding;
  bool     is_write;      // false=read, true=write
  bool     last_chunk_read;
  bool     look_ahead_encoding_full;
  wchar    look_ahead_encoding;
  bool     look_ahead_crlf_full;
  wchar    look_ahead_crlf;
  bool     unget_full;
  wchar    unget_char;
  bool     put_char_full;
  wchar    put_char;
  int      len;           // index of buffer for reading from o.s. (not used for write)
  int      ofs;           // read or write index into buffer
  byte     buffer[16*1024];
}


12.3 comment faire un bon découpage

Il y a toujours plusieurs façons de découper un programme en morceaux.

Un bon découpage (un bon design comme disent les professionnels) se caractérise par le fait que les fichiers .h sont assez petits et clairs et que les fichiers .c sont assez gros, car plus les interconnections entre les différentes parties d'un programme sont petites et bien définies, et plus la complexité est enfermée dans les .c et pas distribuée dans tout le programme, plus le programme dans son ensemble sera facile à comprendre.

Vous pouvez facilement diviser le temps de programmation par dix si, avant de commencer, vous rédigez une analyse détaillée de votre programme sur papier, puis rédigez des fichiers .h qui ne changeront plus (cela s'appelle "programming in the large").

Si l'analyse papier et les fichiers .h sont clairs, cela ne posera aucun problème de les donner à une équipe de programmeurs où chacun écrira l'un des .c.

Si lors de cette phase on s'aperçoit qu'il faut changer un .h parce que ça "coince", qu'il manque quelque chose, ou qu'il y a un cas que vous n'aviez pas prévu, alors votre analyse était de mauvaise qualité et vous allez perdre du temps. Votre méthode d'analyse est alors à revoir ... pour votre prochain programme.


Chapitre 13 : Les structures


13.1 Utiliser des structures

L'exemple suivant déclare une structure PRODUCT. Une structure, c'est en fait un type composé de plusieurs champs de types plus simples. L'avantage c'est qu'en déclarant une seule variable de type structure, on déclare tous les champs qui la composent en une fois.


// s1.c

from std use console;

struct PRODUCT
{
  int     nr;
  char[4] code;
  float   price;
}

void main()
{
  PRODUCT rice;

  rice = { nr => 1, code => "rice", price => 12.5};

  printf ("My product is : %d, %s, %f\n", rice.nr, rice.code, rice.price);
}

ce qui donne :


My product is : 1, rice, 12.500000

L'exemple suivant déclare un tableau de structure 'PRODUCT'. Vous remarquez que la syntaxe pour accéder à un élément du tableau est "[indice]", tandis que la syntaxe pour accéder à un champ de la structure est ".champ"

"product[1].price" combine les deux.


// s2.c

from std use console;

struct PRODUCT
{
  int     nr;
  char[4] code;
  float   price;
}

const int MAX_PRODUCTS = 3;
PRODUCT product[MAX_PRODUCTS];

void main()
{
  int i;
  
  clear product;

  product[0] = {1, "rice", 12.5};
  product[1] = {2, "appl", 8.0};
  product[2] = {3, "citr", 4.05};

  for (i=0; i<product'length; i++)
    printf ("product[%d] is : %d, %s, %f\n", i, product[i].nr, product[i].code, product[i].price);
    
  printf ("we change the price of appl to 100 !\n");
  product[1].price = 100.0;
  
  for (i=0; i<product'length; i++)
    printf ("product[%d] is : %d, %s, %f\n", i, product[i].nr, product[i].code, product[i].price);
}

ce qui donne :


product[0] is : 1, rice, 12.500000
product[1] is : 2, appl, 8.000000
product[2] is : 3, citr, 4.050000
we change the price of appl to 100 !
product[0] is : 1, rice, 12.500000
product[1] is : 2, appl, 100.000000
product[2] is : 3, citr, 4.050000



13.2 les structures paramétrisables

L'exemple ci-dessous déclare une structure GEOMETRY paramétrisable. En effet, vous remarquerez dans sa déclaration un paramètre (KIND kind) ainsi que le mot-clé "switch". Les champs effectifs de cette structure dépendent de la valeur du paramètre KIND qui est donné lors de la création de la variable. Quand une variable de type GEOMETRY est créée, son discriminant (kind) est figé, ses champs sont définis, et sa taille est calculée, et tous les trois ne changeront plus.

On aurait pu écrire ce programme en créant 4 structures différentes (pour circle, triangle, rectangle, point). L'avantage de rassembler ces 4 formes géométriques dans la même structure est qu'on peut passer le type GEOMETRY par exemple à une fonction d'affichage commune (telle que display) ou déclarer des pointeurs vers cette structure commune.

Quand vous utilisez un champ déclaré dans la partie switch, le langage vérifie toujours strictement que le discriminant a une valeur qui fait que le champ existe.


// sp1.c

from std use console;

enum KIND {CIRCLE, TRIANGLE, RECTANGLE, POINT};

struct GEOMETRY (KIND kind)
{
  float x, y;
  switch (kind)
  {
    case CIRCLE:
      float radius;
      
    case TRIANGLE:
      float base;
      float height;
      
    case RECTANGLE:
      float width;
      float height2;
      
    case POINT:
      null;          // utilisez le mot-clé null quand il n'y a aucun champ
  }
}

void display (GEOMETRY g)
{
  printf ("type : %s\n", g.kind'string);
  printf ("position : (%f,%f)\n", g.x, g.y);
  switch (g.kind)
  {
    case CIRCLE:
      printf ("radius = %f\n", g.radius);
      break;
      
    case TRIANGLE:
      printf ("base = %f, height = %f\n", g.base, g.height);
      break;
      
    case RECTANGLE:
      printf ("dimensions = %f x %f\n", g.width, g.height2);
      break;
      
    default:
      break;
  }
  printf ("\n");
}

void main()
{
  const GEOMETRY my_circle(CIRCLE) = {x => 1.0, y => 2.0, radius => 0.5};
  const GEOMETRY my_point(POINT)   = {x => 1.0, y => 2.0};
  
  GEOMETRY^ my_triangle
     = new GEOMETRY(TRIANGLE) ' {x => 1.0, y => 2.0, base => 1.0, height => 3.0};
  
  display (my_circle);
  display (my_point);
  display (my_triangle^);
  
  free my_triangle;
}

ce qui donne :


type : CIRCLE
position : (1.000000,2.000000)
radius = 0.500000

type : POINT
position : (1.000000,2.000000)

type : TRIANGLE
position : (1.000000,2.000000)
base = 1.000000, height = 3.000000

Chapitre 14 : la librairie de base

Sur la page principale du site Safe-C, vous pouvez trouver une liste complète des composants de la libraire standard std.lib avec leur contenu.

Pour ne pas vous noyer, nous allons simplement survoler rapidement les composants les plus courants, afin que vous sachiez au moins qu'ils existent. Si vous ne trouvez ce que vous cherchez, n'hésitez pas à consulter la liste complète sur la page principale de ce site.

Pour utiliser un composant, il suffit de placer une clause 'from std use nom_du_composant;' au début de votre programme. Il faut que le fichier de la librairie standard (std.lib) soit dans le dossier avec le compilateur.

Exemple:

  // p.c
  
  from std use win;
  
  void main()
  {
    message_box (title => "Ma message box", message => "Veuillez cliquez sur Ok", button => " Ok ");
  }

14.1 console : affichage sur la boite de commande

  void printf (string format, object[] arg);
  int scanf (string format, out object[] arg);

14.2 strings : manipulation de chaines de caractères

  int strcpy (out string dest, string src);
  int strncpy (out string dest, string src, int len);
  int strcat (ref string dest, string src);
  int strlen (string s);
  int strcmp (string a, string b);
  int stricmp (string a, string  b);
  int strncmp (string a, string b, int maxlen);
  int strnicmp (string a, string  b, int maxlen);
  int memcmp (byte[] a, byte[] b);
  int strchr (string s, char c);
  int strrchr (string s, char c);
  int strstr (string text, string fragment);
  int stristr (string text, string fragment);
  int sprintf (out string buffer, string format, object[] arg);
  int sscanf (string buffer, string format, out object[] arg);
  int strcatf (ref string buffer, string format, object[] arg);
  char tolower (char c);
  char toupper (char c);
  bool isalpha (char c);   // A-Z a-z
  bool isdigit (char c);   // 0-9
  bool isxdigit (char c);  // 0-9 A-F a-f

  int wstrcpy (out wstring dest, wstring src);
  int wstrncpy (out wstring dest, wstring src, int len);
  int wstrcat (ref wstring dest, wstring src);
  int wstrlen (wstring s);
  int wstrcmp (wstring a, wstring b);
  int wstricmp (wstring a, wstring  b);
  int wstrncmp (wstring a, wstring b, int maxlen);
  int wstrnicmp (wstring a, wstring  b, int maxlen);
  int wstrchr (wstring s, wchar c);
  int wstrrchr (wstring s, wchar c);
  int wstrstr (wstring text, wstring fragment);
  int wstristr (wstring text, wstring fragment);
  int wsprintf (out wstring buffer, wstring format, object[] arg);
  int wsscanf (wstring buffer, wstring format, out object[] arg);
  int wstrcatf (ref wstring buffer, wstring format, object[] arg);
  wchar wtolower (wchar c);
  wchar wtoupper (wchar c);
  bool wisalpha (wchar c);   // A-Z a-z
  bool wisdigit (wchar c);   // 0-9
  bool wisxdigit (wchar c);  // 0-9 A-F a-f

14.3 calendar : calendrier

  struct DATE_TIME;
  void get_datetime (out DATE_TIME datetime);
  int max_days_in_month (int month, int year);
  bool is_valid_date (int day, int month, int year);
  int day_of_week (int day, int month, int year);
  int nb_days_since_1901 (int day, int month, int year);
  void add_days (ref DATE_TIME date, int nb_days);
  void add_seconds (ref DATE_TIME date, long nb_secs);
  int get_time_zone ();

14.4 math : fonctions mathématiques

  // basic functions
  double fabs  (double x);   // absolute value
  double sqrt  (double x);   // square root, with x >= 0.
  double round (double x);   // rounds to nearest integer
  double ceil  (double x);   // rounds up
  double floor (double x);   // rounds down
  double fmin (double a, double b);             // minimum
  double fmax (double a, double b);             // maximum
  double fmin3 (double a, double b, double c);  // minimum of 3 values
  double fmax3 (double a, double b, double c);  // maximum of 3 values

  // trigonometric functions
  const double PI = 3.141592653589793238;
  double pi();        // pi in internal 66-bit precision
  double sin (double x);
  double cos (double x);
  double tan (double x);     // same as sin(x)/cos(x), with cos(x) != 0.
  double asin (double x);    // with x in [-1..+1]
  double acos (double x);    // with x in [-1..+1]
  double atan (double x);
  double atan2 (double y, double x);   // valid for all values of x and y.

  // exponential, logarithm and power functions
  double exp   (double x);   // e exponent x
  double ln    (double x);   // log e, with x > 0
  double log2  (double x);   // log 2, with x > 0
  double log10 (double x);   // log 10, with x > 0
  double pow (double base, double exponent);      // base to the power of exponent

  // hyperbolic functions
  double sinh (double x);
  double cosh (double x);
  double tanh (double x);
  double asinh (double x);
  double acosh (double x);
  double atanh (double x);
  
  // infinite test
  bool isINF (double x);

14.5 arithm : arithmétique simple

  int abs (int a);          // valeur absolue
  int min (int a, int b);   // minimum
  int max (int a, int b);   // maximum
  int min3 (int a, int b, int c);
  int max3 (int a, int b, int c);
  uint umin (uint a, uint b);
  uint umax (uint a, uint b);
  uint umin3 (uint a, uint b, uint c);
  uint umax3 (uint a, uint b, uint c);
  long labs (long a);
  long lmin (long a, long b);
  long lmax (long a, long b);
  long lmin3 (long a, long b, long c);
  long lmax3 (long a, long b, long c);
  int  ilog2 (int a);    // logarithme base 2, a > 0
  uint ulog2 (uint a);   // logarithme base 2, a > 0

14.6 random : génération de nombres aléatoires

  int rnd (int first, int last);  // nombre au hasard entre first et last
  void get_random_number (out byte[] value);

14.7 exception : traitement d'exception

  // voir chapitre 6 : Les crashs et le tracing
  void arm_exception_handler (bool create_crash_report_file        = true,
                              bool display_fatal_error_message_box = true);

14.8 tracing : fichiers de trace

  // voir chapitre 6 : Les crashs et le tracing
  void open_trace (string filename,    // for example "trace.tra"
                   long   max_file_size = 64*1024*1024,  // default is 64 MB
                   bool   date = true,
                   bool   time = true,
                   bool   msec = false);
  void trace (string format, object[] arg);
  void trace_block (byte[] block);
  void close_trace ();

14.9 sorting : tri de tableaux

  // voir chapitre 5 : les tableaux
  package HeapSort
  package QuickSort
  package BubbleSort

14.10 files : fichiers texte et binaire, répertoires, disque.

  //----------------------------------------------------------------------------
  // Text Files : functions for reading or writing buffered text files.
  //----------------------------------------------------------------------------
  struct FILE;  // the same FILE must not be used by several threads at the same time.
  enum ENCODING {ANSI, UTF8, UTF16, UTF16BE};
  int fopen (out FILE file, string filename);
  int fcreate (out FILE file, string filename, ENCODING encoding);
  int fappend (out FILE file, string filename, ENCODING encoding);
  const int FEOF = +1;  // return value that indicates end of file
  int fgetc (ref FILE file, out char c);
  int wfgetc (ref FILE file, out wchar c);
  int fputc (ref FILE file, char c);
  int wfputc (ref FILE file, wchar c);
  int fgets (ref FILE file, out string buffer);
  int wfgets (ref FILE file, out wstring buffer);
  int fputs (ref FILE file, string buffer);
  int wfputs (ref FILE file, wstring buffer);
  int fscanf (ref FILE file, string format, out object[] arg);
  int wfscanf (ref FILE file, wstring format, out object[] arg);
  int fprintf (ref FILE file, string format, object[] arg);
  int wfprintf (ref FILE file, wstring format, object[] arg);
  bool feof (ref FILE file);
  int fflush (ref FILE file);
  int fclose (ref FILE file);
  int fhandle (ref FILE file);

  //----------------------------------------------------------------------------
  // Binary Files : functions that operate on low-level binary files.
  //----------------------------------------------------------------------------
  int create (string filename, uint access = WRITE, uint share = READ, ORGANIZATION organization = UNDEFINED);
  int open (string filename, uint access = READ, uint share = READ, ORGANIZATION organization = UNDEFINED);
  int read (int handle, out byte[] buffer);
  int write (int handle, byte[] buffer);
  long lseek (int handle, long offset, SEEK_MODE mode = SEEK_SET);
  long filesize (int handle);
  void get_ftime (int handle, out calendar.DATE_TIME time);   // get last write time.
  void set_ftime (int handle, calendar.DATE_TIME time);       // set create, last access, last write time.
  int lock_file   (int handle, LOCK lock, long offset, long nb_bytes);
  int unlock_file (int handle, LOCK lock, long offset, long nb_bytes);
  int flush (int handle);
  int close (int handle);

  int move_file (string source, string target);
  int copy_file (string source, string target, bool overwrite = true);
  int delete_file (string filename);

  void split_pathname (    string pathname,
                       out string device,
                       out string directory,  // starts with / or \ if absolute path
                       out string filename);   // does not contain any / or \
                       
  int expand_pathname (    string current_directory,
                           string source_filename,
                       out string result_filename);
                       
  void normalize_pathname (ref string pathname);

  bool name_matches_pattern (string name,
                             string pattern,
                             char   replace_one  = '?',
                             char   replace_many = '*');

  int select_filename (    string  dialog_title,
                           string  mask,
                           bool    saving,     // false=load, true=save
                       out char    filename[MAX_FILENAME_LENGTH],
                           string  default_filename = "",
                           uint    hwnd = 0);  // parent window handle

  //----------------------------------------------------------------------------
  // Directories
  //----------------------------------------------------------------------------
  struct FILE_INFO;
  int open_directory (out FILE_INFO info, string directory_name);
  int read_directory (ref FILE_INFO info);
  int close_directory (ref FILE_INFO info);
  int create_directory (string directory_name);
  int remove_directory (string directory_name);
  void get_current_directory (out char directory_name[MAX_FILENAME_LENGTH]);
  int set_current_directory (string directory_name);

  //----------------------------------------------------------------------------
  // Disk Volumes
  //----------------------------------------------------------------------------
  long free_disk_space (string directory_name);   // any directory on the disk

14.11 image : compression et décompression (jpg, gif, png, ..), traitement d'image

  // Part 1 : image compresssion/decompression
  enum IMAGE_FILE_FORMAT {FORMAT_BMP, FORMAT_JPEG, FORMAT_GIF, FORMAT_PNG, FORMAT_TIFF};
  struct IMAGE;
  int create_image (out IMAGE image, string filename, uint width, uint height, IMAGE_CREATION_PARAMETERS parameters);
  int open_image (out IMAGE image, string filename);
  void get_image_size (    IMAGE image, out uint  width, out uint  height);
  void get_image_attributes (IMAGE image, out IMAGE_ATTRIBUTES attr);
  void set_image_options (IMAGE image, IMAGE_OPTIONS opt);
  int read_image (IMAGE image, out byte[] buffer);
  int next_image (IMAGE image);
  int write_image (IMAGE image, byte[] buffer);
  void multiply_rgb_by_alpha (ref byte[] buffer);
  int close_image (ref IMAGE image);

  // Part 2 : RGBA bitmap manipulations
  int load_image (out IMAGE_INFO info, string filename);

  int save_image (IMAGE_INFO                info,
                  string                    filename, /* output file */
                  IMAGE_CREATION_PARAMETERS param);   /* output parameters */

  void free_image (ref IMAGE_INFO info);

  int copy_image (IMAGE_INFO source, out IMAGE_INFO target);

  int bestfit_size (ref IMAGE_INFO info,
                    ref uint       width,      /* in out ! */
                    ref uint       height);    /* in out ! */

  int resize_image (ref IMAGE_INFO info,
                        uint       width,
                        uint       height,
                        uint       border_color = 0xFF000000);  // opaque black

  int resize_image2 (    IMAGE_INFO info,
                         uint       width,
                         uint       height,
                         uint       border_color = 0xFF000000,  // opaque black
                     out IMAGE_INFO result);

  int zoom_image
       (    IMAGE_INFO source,       /* source image */

            uint       click_x,      /* zoom x-center on screen */
            uint       click_y,      /* zoom y-center on screen */
            int        zoom,         /* in percent (100 .. 6_500_000) */
                                     /* use 100 for first call of new image ! */
            uint       width,        /* target screen width  */
            uint       height,       /* target screen height */
        ref CLIP_INFO  source_clip,  /* clear for first call, value must be kept between calls */
        ref CLIP_INFO  target_clip,  /* clear for first call, value must be kept between calls */
            uint       border_color, /* =0 */
        out IMAGE_INFO target);      /* result image for screen */

  int rotate_image (ref IMAGE_INFO info,
                        int        angle,
                        uint       border_color = 0xFF000000);  // opaque black

  int rotate_image2 (    IMAGE_INFO info,
                         int        angle,
                         uint       border_color = 0xFF000000,  // opaque black
                     out IMAGE_INFO result);

  int copy_image_rectangle (    IMAGE_INFO source_image,
                                CLIP_INFO  source_clip,
                            out IMAGE_INFO target_image);

  int compute_bestfit_target_clip (    CLIP_INFO source_clip,
                                       CLIP_INFO target_clip,
                                   out CLIP_INFO bestfit_target_clip);

  void reverse_clip_coordinates (    uint      x,
                                     uint      y,
                                     CLIP_INFO source_clip,
                                     CLIP_INFO target_clip,
                                 out uint      out_x,
                                 out uint      out_y);

  int compute_zoom_clip_info
          (    uint      center_x,      /* within source                 */
               uint      center_y,      /* within source                 */
               int       zoom_factor,   /* in percent (100 .. 6_500_000) */
           ref CLIP_INFO source_clip,   /* in out                        */
           ref CLIP_INFO target_clip);  /* in out                        */

  int stretch_image (IMAGE_INFO source,
                     CLIP_INFO  source_clip,
                     IMAGE_INFO target,
                     CLIP_INFO  target_clip);

14.12 draw : dessiner dans un buffer mémoire (ligne, cercle, texte, image)

  struct DRAW_CONTEXT;
  void init_draw (out DRAW_CONTEXT dc,
                      byte[]^      buffer,     /* -> (4*width*height) bytes */
                      uint         width,
                      uint         height);
  void draw_line (ref DRAW_CONTEXT dc,
                      int          x1,
                      int          y1,
                      int          x2,
                      int          y2,
                      uint         color,    /* = rgb(red,green,blue) */
                      int          thick);   /* >= 1                  */
  void draw_circle (ref DRAW_CONTEXT dc,
                        int          x,
                        int          y,
                        int          radius,
                        uint         color,
                        int          thick);
  void draw_solid_circle (ref DRAW_CONTEXT dc,
                              int          x,
                              int          y,
                              int          radius,
                              uint         color);
  int draw_text (ref DRAW_CONTEXT  dc,
                     string        text,           /* string to display */
                     string        font_name,      /* ex: "Times New Roman" */
                     int           font_height,    /* ex: 12 */
                     int           x,              /* lower left corner */
                     int           y,
                     uint          color,
                     uint          style);         /* = 0 */

  int draw_wtext (ref DRAW_CONTEXT  dc,
                     wstring       text,           /* string to display */
                     string        font_name,      /* ex: "Times New Roman" */
                     int           font_height,    /* ex: 12 */
                     int           x,              /* lower left corner */
                     int           y,
                     uint          color,
                     uint          style);         /* = 0 */
  void draw_image (ref DRAW_CONTEXT  dc,
                       int           x,            /* target (upper-left corner) */
                       int           y,
                       byte[]        image,
                       uint          width,
                       uint          height,
                       uint          clip_x,       /* =0 for all image */
                       uint          clip_y,       /* =0 for all image */
                       uint          clip_size_x,  /* =width for all image */
                       uint          clip_size_y,  /* =height for all image */
                       uint          flags = 0);
  void set_draw_origin (ref DRAW_CONTEXT dc,
                            int          ox,
                            int          oy,
                            int          dx,   /* direction of x-axis */
                            int          dy);  /* direction of y-axis */
  void set_draw_clip (ref DRAW_CONTEXT dc,
                          int          min_x,
                          int          min_y,
                          int          max_x,
                          int          max_y);

14.13 win : affichage de fenêtres, boutons, listbox, ..

  void message_box (string title, string message, string button);
  void create_screen (int x, int y, int x_size, int y_size, string title);
  void set_screen_title (string title);
  void close_screen ();
  void create_text (int x, int y, int x_size, int y_size, OBJECT_ID id);
  void text_put (string text, OBJECT_ID id);
  void create_edit (int x, int y, int x_size, int y_size, int length, OBJECT_ID id);
  void edit_get (out string buffer, OBJECT_ID id);
  void create_checkbox (int x, int y, int x_size, int y_size,
                        int box_width, string text, OBJECT_ID id);
  void checkbox_get (out bool setting, OBJECT_ID id);
  void create_listbox (int x, int y,        int x_size, int y_size,
                       int arrow_box_width, int arrow_box_height,
                       int length,          OBJECT_ID id);
  void listbox_insert (int       index,    // 0 = append to end
                       string    text,
                       OBJECT_ID id);
  void create_button (int x, int y, int x_size, int y_size, string text, OBJECT_ID id);
  void get_event (out EVENT event);
  // ....

Exemple:

  // p.c
  
  from std use win;
  
  void main()
  {
    int left, top, right, bottom;
    bool quit = false;
    
    get_screen_borders (out left, out top, out right, out bottom);
    
    create_screen (x => 100, y => 100, x_size => left+right+300, y_size => top+bottom+200, title => "Test");
    
    create_button (x => 100, y => 100, x_size => 100, y_size => 30, text => "Click me", id => 10);
    
    while (!quit)
    {
      EVENT event;
      get_event (out event);
      switch (event.typ)
      {
        case EVENT_BUTTON_PRESSED:
          switch (event.id)
          {
            case 10:
              message_box (title => "Ma message box", message => "Merci pour votre click !", button => " Ok ");
              quit = true;
              break;
              
            default:
              break;
          }
          break;
          
        default:
          break;
      }
    }
    
    close_screen ();
  }

L'utilitaire makelib.exe vous permet de créer votre propre librarie. Pour cela, placer vos fichiers .h et .c dans un répertoire (par exemple dans C:\LIB) et tapez makelib mylib C:\LIB\. Un fichier mylib.lib sera alors créé, ajoutez-le dans le dossier des fichiers du compilateur.

Importez-le dans votre programme avec from mylib use votre_composant; où votre_composant correspond au nom de votre fichier .h


Dans des cas rares, il peut arriver que deux composants de libraries différentes portent le même nom. Il est alors possible d'en importer un en le renommant avec une clause as :

  from std use math;
  from mylib use math as math2;

Chapitre 15 : les packages et packages génériques


15.1 Les packages

Un package est une enveloppe qui sert à grouper une série de déclarations.

Remarquez les mots-clés package pour déclarer un package et end pour le terminer.

Exemple:


  package Stack
    const int MAX = 1000;
    void push (int i);
    int pop ();
  end Stack;

Dans l'exemple ci-dessus, vu que le package contient des déclarations de fonctions, on doit aussi déclarer un corps de package pour y déclarer les corps des fonctions correspondantes.

Remarquez les mots-clés package body pour déclarer un corps de package.


  package body Stack

    int table[MAX];
    int index = 0;

    public void push (int i)
    {
      table[index++] = i;
    }

    public int pop ()
    {
      return table[--index];
    }
    
  end Stack;

Les déclarations à l'intérieur du package sont accessibles depuis l'extérieur. Par contre les déclarations à l'intérieur du corps de package sont privées et non-accessibles depuis l'extérieur.

On peut déclarer des packages dans des fichiers .h ou .c, les éventuels corps de package sont uniquement autorisés dans les .c.

Notez qu'on peut imbriquer des packages.

Un corps de package est obligatoire si le package contient des déclarations incomplètes comme des types opaques ou des déclarations de fonctions.

Voici un exemple complet pour un package Stacks qui permet d'empiler des valeurs :


  from std use console;

  package Stacks
    struct STACK;      // an opaque type
    void push (ref STACK s, int i);
    int pop (ref STACK s);
  end Stacks;


  package body Stacks
    const int MAX = 1000;

    struct STACK
    {
      int table[MAX];
      int index;
    }

    public void push (ref STACK s, int i)
    {
      s.table[s.index++] = i;
    }

    public int pop (ref STACK s)
    {
      return s.table[--s.index];
    }

  end Stacks;


  void main()
  {
    STACK s;

    clear s;
    push (ref s, 12);
    printf ("%d\n", pop(ref s));
  }

Cet exemple peut aussi être placé dans 3 fichiers différents :


  // stacks.h

  package Stacks

    struct STACK;      // an opaque type

    void push (ref STACK s, int i);
    int pop (ref STACK s);

  end Stacks;

  // stacks.c

  package body Stacks

    const int MAX = 1000;

    struct STACK
    {
      int table[MAX];
      int index;
    }

    public void push (ref STACK s, int i)
    {
      s.table[s.index++] = i;
    }

    public int pop (ref STACK s)
    {
      return s.table[--s.index];
    }

  end Stacks;

  // prog.c

  from std use console;
  use stacks;

  void main()
  {
    STACK s;

    clear s;
    push (ref s, 12);
    printf ("%d\n", pop(ref s));
  }

Notez qu'on peut placer plusieurs packages dans le même fichier source. Dans ce cas, il peut se produire des ambiguités de noms que l'on peut résoudre en préfixant le nom du package au nom, comme dans cet exemple :


  package A
    const int MAX = 100;
  end A;

  package B
    const int MAX = 200;
  end B;

  void main()
  {
    int i;

    i = MAX;    // ERREUR: MAX est ambigu vu qu'il est déclaré deux fois
    i = A.MAX;  // OK (on a préfixé le nom du package)
    i = B.MAX;  // OK
  }


15.2 Les packages génériques

Un package générique permet de faire un copier-coller de lignes de programme. Chaque copie sera légèrement différente car le package générique comporte divers paramètres de type et de fonction que l'on donne lors de la copie.

Voici un exemple d'un package générique BubbleSort qui permet de trier n'importe quel tableau. Le package générique va comporter deux paramètres :

. d'abord un type "ELEMENT" qui pourra être remplacé par n'importe quel type. C'est le type des éléments du tableau à trier.

. ensuite une fonction "compare" qui prend deux ELEMENT et renvoie un int. Cette fonction permet au package de connaitre l'ordre de deux éléments.

A l'intérieur du package "BubbleSort" il n'y a qu'une fonction "sort" qui traite un paramètre tableau de type ELEMENT en utilisant les deux paramètres fournis.


  // bubble.h
  
  generic <ELEMENT>                      // generic type ELEMENT
    int compare (ELEMENT a, ELEMENT b);  // return -1 if a<b, 0 if a==b, +1 if a>b
  package BubbleSort
    void sort (ref ELEMENT table[]);
  end BubbleSort;

Voici le corps du package. Vous voyez qu'il utilise ELEMENT et compare.


  // bubble.c

  package body BubbleSort
    public void sort (ref ELEMENT table[])
    {
      int     i, j;
      ELEMENT temp;

      for (i=1; i<table'length; i++)
      {
        for (j=i; j>0; j--)
        {
          if (compare (table[j-1], table[j]) <= 0)
            break;

          temp       = table[j-1];
          table[j-1] = table[j];
          table[j]   = temp;
        }
      }
    }
  end BubbleSort;

Comment utiliser le package générique ? Pour faire un copier-coller (qu'on appelle "instanciation" chez les pros), on utilise la syntaxe suivante qui va créer une copie du package BubbleSort en replaçant les deux paramètres. Le package après copie s'appelera "Sort_int" parce qu'il trie les tableaux de "int".


  package Sort_int = new BubbleSort (ELEMENT => int,
                                     compare => compare_int);

Mais d'où provient la fonction compare_int ? et bien vous devez l'écrire vous-même car le package de tri ne connait pas votre type donc à priori il ne sait pas comment comparer deux éléments.

Voici l'exemple complet d'instanciation :


  // p.c
  
  use bubble;
  
  int compare_int (int a, int b)
  {
    if (a < b) return -1;
    if (a > b) return +1;
    return 0;
  }

  package Sort_int = new BubbleSort (ELEMENT => int,
                                     compare => compare_int);

  void main()
  {
    int table[5] = {2, 19, 3, 9, 4};

    sort (ref table);    // à écrire Sort_int.sort si ambigu
  }

Vous voyez que les packages génériques sont un peu lourds syntaxiquement. Ils sont cependant indispensables afin de pouvoir écrire des algorithmes généraux de tri et autres manipulations de structures de données avec une vitesse d'exécution et une sécurité d'accès mémoire maximales.

La seule manière de les éviter serait de "copier-coller" des lignes de programme manuellement en l'adaptant pour chaque type, ce qui n'est pas vraiment souhaitable.


Chapitre 16 : Les fichiers, tableaux de byte et type objet


16.1 fichiers et tableaux de byte

Le composant de librarie standard files permet un accès bas-niveau aux fichiers.

Si vous examinez les fonctions qui permettent de lire et d'écrire un fichier, vous allez constater qu'ils n'acceptent que des paramètres de type "tableau de byte" :


int read (int handle, out byte[] buffer);
int write (int handle, byte[] buffer);

Comment faire alors pour écrire un int, un float, une structure dans un fichier ?

Et bien c'est facile car en Safe-C presque tous les types sont convertibles automatiquement en tableaux de bytes. Vous pouvez donc écrire ceci :


from std use files, strings;

void main()
{
  int   fd;
  float f;
  char  buffer[80];
  bool  b;
  
  fd = create ("myfile.data");
  
  f = 3.14;
  write (fd, f);        // <-- conversion automatique float vers byte[]
  
  strcpy (out buffer, "Hello");
  write (fd, buffer);   // <-- conversion automatique char[] vers byte[]

  b = true;
  write (fd, b);        // <-- conversion automatique bool vers byte[]
  
  close (fd);
}

Quels sont les types qui ne sont pas convertibles en tableaux de byte ?

Les structures, tout d'abord, ne sont convertibles que si elles sont précédées du mot-clé packed ce qui indique qu'elles n'ont pas de trous d'alignement entre leur champs.

Voici comment écrire une structure dans un fichier :


from std use files;

void main()
{
  int   fd;
  
  packed struct REC    // <--- mot-clé "packed" obligatoire !
  {
    char  name[4];
    float f;
    bool  b;
  }
  
  REC rec;
  
  fd = create ("myfile.data");
  
  rec = {"ABCD", 1.234, true};
  write (fd, rec);        // <-- conversion automatique REC vers byte[]
  
  close (fd);
}

Evidemment quand on préfixe le mot-clé packed a une structure, celle-ci ne peut contenir que des champs simples ou également packed.

En outre, la structure ne peut pas contenir des types pointeurs. Vous devez retenir que packed et pointeurs, cela ne se mélange pas. Il n'y a aucun intérêt à écrire des pointeurs sur le disque, et c'est même dangereux d'en lire parce qu'ils ne sont sans doute plus valides. Pour cette raison, les libraries standards ne permettent ni de lire ni d'écrire des valeurs de type pointeur sur le disque, sur le réseau ou autre entrée-sortie (notez qu'il y a moyen de le faire quand même avec une section de code unsafe).

L'attribut 'size s'applique à tous les types packed et permet de calculer le nombre de bytes d'un type. Attention que 'size renvoie un uint (entier non-signé) que vous devrez éventuellement convertir en int.

Dans cet exemple on va vérifier que read() lit le nombre de bytes corrects. Il faudrait faire la même vérification lors d'un write() dans les exemples ci-dessus, nous ne l'avons pas fait pour des raisons de concision.


from std use console, files;

void main()
{
  int fd;
  
  packed struct REC
  {
    char  name[4];
    float f;
    bool  b;
  }
  
  REC rec;
  
  fd = open ("myfile.data");
  
  if (read (fd, out rec) != (int)rec'size)   // <-- attribut size
  {
    printf ("error: cannot read file\n");
    close (fd);
    return;
  }
  
  close (fd);
}


L'attribute 'byte permet de convertir n'importe quel type packed en tableau de bytes. Cela permet de copier, par exemple, les 4 bytes d'un float dans les 4 bytes d'un int :


  float f = 1.2;
  int   i;
  
  i'byte = f'byte;

ou même de copier les 2 derniers bytes d'un float dans un tableau de deux bytes :


  float f = 1.2;
  byte  b[2];
  
  b = f'byte[2:2];


16.2 le type object

En Safe-C, le type object est prédéfini comme tableau de byte. Donc partout où on écrit "byte [ ]" on pourrait écrire "object".

Pour des raisons de clarté on utilise surtout le type 'object' dans les déclarations de fonctions à nombre variable de paramètre. Ainsi, printf est défini comme :


  void printf (string format, object[] arg);

Le Safe-C applique la règle suivante : si le dernier paramètre d'une fonction est de type "object [ ]", alors la fonction accepte un nombre variable de paramètres qui doivent tous être packed, donc convertibles en tableaux de byte.

A l'intérieur de la fonction, ces paramètres sont réceptionnés comme un tableau de tableau de bytes.

Exemple:


  from std use console;
  
  void affichage (int test, object[] par)
  {
    int i, j;
    
    printf ("\n");
    printf ("test %d\n", test);
    for (i=0; i<par'length; i++)
    {
      printf ("parametre %d : ", i+1);
      for (j=0; j<par[i]'length; j++)
        printf ("%u ", par[i][j]);
      printf ("\n");
    }
  }
  
  void main()
  {
    affichage (1);
    affichage (2, 1.1275);
    affichage (3, (double)1.1275);
    affichage (4, "ABCD", 'G', 1.1275);
    affichage (5, (tiny)3);
    affichage (6, 3);
    affichage (7, 3L);
    affichage (8, L"Hello");
  }

va afficher :


test 1

test 2
parametre 1 : 236 81 144 63

test 3
parametre 1 : 10 215 163 112 61 10 242 63

test 4
parametre 1 : 65 66 67 68
parametre 2 : 71
parametre 3 : 236 81 144 63

test 5
parametre 1 : 3

test 6
parametre 1 : 3 0 0 0

test 7
parametre 1 : 3 0 0 0 0 0 0 0

test 8
parametre 1 : 72 0 101 0 108 0 108 0 111 0

Chapitre 17 : Les pointeurs et pointeurs unsafe


17.1 Les pointeurs

Dans les exemples des chapitres précédents, chaque variable d'un programme porte un nom, chaque variable tableau a une taille constante, et chaque variable existe soit pour toute la durée du programme soit tant que la fonction à l'intérieur de laquelle elle est déclarée est active.

Avec les pointeurs, on peut s'affranchir de ces contraintes et créer des variables anonymes, des tableaux de taille variable, et des variables dont on contrôle précisément la durée de vie.

L'exemple p1.c montre la déclaration d'une variable p qui est un pointeur vers un tableau de int (notation int[ ]^, lire de droite à gauche).

Grâce à l'allocateur 'new', on réserve de la mémoire pour un tableau de int anonyme de "count" éléments, et on sauve dans le pointeur p l'adresse mémoire qui permet d'y accéder.

Pour accéder à notre tableau anonyme, on utilise la notation p^[i]. On part du pointeur "p", le symbole "^" permet de suivre le pointeur et d'accéder au tableau, et finalement "[i]" permet d'accéder à l'élément de tableau souhaité. Le résultat est alors une variable int.

La notation "p^'length" permet de déterminer le nombre d'élements du tableau. C'est simplement l'attribut 'length appliqué au tableau p^.

Quand vous n'avez plus besoin du tableau, il ne faut pas oublier de libérer sa mémoire avec l'instruction free sinon vous finiriez par consommer toute la mémoire disponible.


// p1.c

from std use console, math;

void main()
{
  int    count, i;
  int[]^ p;
  
  printf ("Please enter the number of elements : ");
  scanf (" %d", out count);
  
  p = new int[count];
  
  for (i=0; i<p^'length; i++)
    p^[i] = (int)(100.0*sin(100.0 * (float)i / 180.0 * PI));
    
  for (i=0; i<p^'length; i++)
    printf ("p^[%d] = %d\n", i, p^[i]);
    
  free p;
}

Please enter the number of elements : 10
p^[0] = 0
p^[1] = 98
p^[2] = -34
p^[3] = -86
p^[4] = 64
p^[5] = 64
p^[6] = -86
p^[7] = -34
p^[8] = 98
p^[9] = 0


L'exemple p2.c déclare une structure NODE composée d'un int et d'un pointeur vers NODE lui-même. On peut s'en servir pour créer une chaine de variables NODE qui pointent chacune sur le noeud suivant, le dernier noeud pointant sur null.

La valeur constante "null" est utilisée pour indiquer que l'on ne pointe vers rien du tout.

Remarquez ici qu'avec une seule variable "p", on peut accéder à toute une liste de variables anonymes chainées ensembles par des pointeurs. Cette chaine est parcourue pour afficher tous les noeuds (avec display), ou pour les libérer (avec free_list).


// p2.c

from std use console;

struct NODE
{
  int   n;
  NODE^ next;
}

void display (NODE^ ptr)
{
  NODE^ p = ptr;
  while (p != null)
  {
    printf ("%d ", p^.n);
    p = p^.next;
  }
}

void free_list (NODE^ ptr)
{
  NODE^ p = ptr;
  NODE^ q;
  while (p != null)
  {
    q = p;
    p = p^.next;
    free q;
  }
}

void main()
{
  NODE^ p;
  
  p = new NODE ' {17, null};
  p = new NODE ' {12, p};
  p = new NODE ' {8, p};
  p = new NODE ' {3, p};

  display (p);
  free_list (p);
}

Ce qui donne :


3 8 12 17

L'allocateur "new" permet de spécifier une valeur initiale pour la variable anonyme créée en utilisant soit la notation apostrophe et aggrégat si on veut spécifier tous les champs :


  NODE^ p;
  p = new NODE ' {17, null};

ou bien apostrophe et parenthèse pour une valeur de même type que le noeud créé :


  NODE n;
  p = new NODE ' (n);

Si on ne spécifie pas de valeur initiale, la variable anonyme sera mise à zéro :


  p = new NODE;

Il peut arriver que l'on souhaite déclarer plusieurs structures dont les pointeurs se référencent mutuellement. Il faut alors pré-déclarer l'une des structures comme un type "incomplet", avant de le redéclarer une 2ème fois plus bas, complètement cette fois-ci.


  typedef NODE2;   // type incomplet

  struct NODE1
  {
    int    count;
    NODE1^ up;
    NODE2^ gauche, droite; // utilise le type incomplet
  }

  struct NODE2     // redéclaration complète
  {
    int    count;
    NODE1^ gauche, droite;
  }


17.2 Les pointeurs unsafe

Pour pouvoir écrire des librairies standard qui appellent le système d'exploitation, il est parfois nécessaire d'utiliser les anciens pointeurs non-sûrs du C.

Afin de baliser cet usage dangereux, le Safe-C n'autorise l'utilisation des pointeurs unsafe que dans une section unsafe.

Exemple, la fonction read() de files.c :


  #begin unsafe

  [extern "KERNEL32.DLL"]
  int ReadFile (
    int   hFile,
    byte  *buffer,
    uint  size,
    int   *BytesRead,
    byte  *lpOverlapped);

  // returns nb of bytes read (>=0), or a negative error code.
  public int read (int handle, out byte[] buffer)
  {
    int read;
    if (ReadFile (handle, &buffer, buffer'size, &read, null) != 0)
      return read;
    return -GetLastError();
  }
  #end unsafe

Dans une section unsafe (entre #begin unsafe et #end unsafe), vous pouvez utiliser les pointeurs du C avec ses opérateurs (cast), & * -> ++ -- != == < > <= >=.

Vous pouvez déclarer des fonctions qui se trouvent dans des DLL externes ou des fonctions callback que Windows peut appeler.


  #begin unsafe
  [callback]
  int MainWin (HWND hwnd, UINT message, WORD w, LONG l)
  {
    return 0;
  }
  #end unsafe

Par ailleurs les pointeurs unsafe sont considérés comme des types packed donc convertibles en tableau de byte.

La constante null indique la valeur pointeur zéro.


Chapitre 18 : ajouter des ressources


18.1 que sont les ressources ?

Les ressources sont des fichiers ajoutés à un exécutable windows. Ils peuvent contenir par exemple :


18.2 comment créer des ressources ?

Pour créer des icônes, utilisez les outils fournis par Microsoft.

Un petit outil extract.exe est fourni avec ce compilateur afin d'extraire toutes les ressources d'un .EXE windows. Une fois extraits, vous pouvez les ajouter à votre exécutable. L'outil extract.exe va créer un fichier correspondant à chaque resource, et il va afficher le texte de définition des ressources sur la console. Il vous suffit de créer avec notepad un fichier .RC de même nom que votre exécutable contenant le texte de définition, et de placer les fichiers dans le répertoire courant, pour que les ressources soient ajoutées à votre .EXE quand vous compilez votre programme.


18.3 Qu'est-ce qu'un manifeste ?

A partir de la version Vista de Windows, certains noms de programmes (par exemple ceux contenant le mot 'install') sont automatiquement démarrés en mode administrateur (l'utilisateur doit donner son mot de passe).

Pour éviter cela, on peut inclure un manifeste au programme. Voici le manifeste.

Il vous suffit de créer un fichier .RC et d'y indiquer la ligne suivante :

1 24 manifest.txt

Si vous avez tout bien fait, le programme ne demandera plus de mot de passe au démarrage.


***

Voici un autre manifeste2 qui combine :
1) ne pas démarrer en mode Administrateur, et
2) inclure les contrôles windows standard (listbox, edit, ..).


Annexe A : créer un serveur internet


A.1 Un serveur internet simple

Voici un modèle pour créer un serveur internet tout simple :



// iserv1.c : internet server

from std use strings, tcpip, http, tracing, exception;

//-----------------------------------------------------------------------

HTTP_SERVER_DATA  iserv;
HTTP_INITIAL_DATA init;

//-----------------------------------------------------------------------

void handler (ref HANDLER_DATA p)
{
  handle_http_call (ref p, iserv, ref init);
}

//-----------------------------------------------------------------------

void main()
{
  int rc;

  arm_exception_handler();

  open_trace ("iserv1.tra");


  // init internet server

  strcpy (out iserv.html_directory, "c:/www/");
  strcpy (out iserv.start_url, "/home.html");
  iserv.tracing = true;


  // start server

  trace ("============================================\n");
  trace ("info: server starting.\n");

  rc = serve_requests (port            => 80,
                       request_handler => handler,
                       max_handlers    => 512,
                       mode            => MODE_DUAL,
                       stop            => null,
                       server_is_ready => null,
                       incoming_call   => null,
                       trace_calls     => true);

  trace ("info: server stopping. rc=%d\n", rc);
  trace ("============================================\n");
}

Pour tester ce programme, créez tout d'abord un répertoire C:\WWW et placez-y quelques fichiers html (par exemple mapage.html).

Lancez ensuite le serveur, puis démarrez Internet Explorer et tapez http://127.0.0.1/mapage.html Votre page devrait alors d'afficher.

Remarquez que vous pouvez aussi taper http://[::1]/mapage.html car le serveur supporte IPV6.

Le serveur crée un fichier de trace (iserv1.tra) que vous pouvez ouvrir avec notepad pour voir les actions du serveur.




A.2 Des pages virtuelles

Voici comment créer des pages virtuelles, c'est-à-dire des pages qui n'existent pas sur le disque dur mais que vous créez dynamiquement.




// iserv2.c : internet server

from std use strings, tcpip, http, tracing, exception;

//-----------------------------------------------------------------------

HTTP_SERVER_DATA  iserv;
HTTP_INITIAL_DATA init;

const uint WRITE_TIMEOUT = 30;  // write timeout : 30 secs

//-----------------------------------------------------------------------

bool is_virtual_page (int        nr,
                      TCP_CLIENT tcp_client,
                      string     url,      // virtual url
                      ARG_INFO   args)     // http arguments
{
  _unused nr;          // suppresses warning that nr is unused
  _unused tcp_client;

  trace ("is_virtual_page (%s)\n", url);
  return !get_http_physical_file_exists (args);
}

//-----------------------------------------------------------------------

void handle_virtual_page (int            nr,
                          ref TCP_CLIENT tcp_client,
                          string         url,      // virtual url
                          ARG_INFO       args)     // http arguments
{
  char str[1000];
  _unused nr;
  _unused args;

  trace ("handle_virtual_page (%s)\n", url);

  sprintf (out str, "<HTML><HEAD><TITLE>MY TITLE</TITLE></HEAD>\r\n");
  strcat (ref str, "<BODY>");
  strcat (ref str, "Bonjour! Cette page est virtuelle !<br>");
  strcat (ref str, "</BODY></HTML>");

  CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT);
}

//-----------------------------------------------------------------------

void handler (ref HANDLER_DATA p)
{
  handle_http_call (ref p, iserv, ref init);
}

//-----------------------------------------------------------------------

void main()
{
  int rc;

  arm_exception_handler();

  open_trace ("iserv2.tra");


  // init internet server

  strcpy (out iserv.html_directory, "c:/www/");
  strcpy (out iserv.start_url, "/home.html");
  iserv.is_virtual_page     = is_virtual_page;
  iserv.handle_virtual_page = handle_virtual_page;
  iserv.tracing             = true;


  // start server

  trace ("============================================\n");
  trace ("info: server starting.\n");

  rc = serve_requests (port            => 80,
                       request_handler => handler,
                       max_handlers    => 512,
                       mode            => MODE_DUAL,
                       stop            => null,
                       server_is_ready => null,
                       incoming_call   => null,
                       trace_calls     => true);

  trace ("info: server stopping. rc=%d\n", rc);
  trace ("============================================\n");
}

Deux fonctions ont été ajoutées :

is_virtual_page() :

le serveur vous donne une url et vous demande s'il doit créer une page virtuelle. Ici nous vérifions à l'aide de get_http_physical_file_exists() si la page existe sur le disque dur, et si ce n'est pas le cas la fonction dit au serveur d'appeler handle_virtual_page() pour créer une page virtuelle.

handle_virtual_page() :

le serveur vous donne une url et vous demande de créer une page virtuelle et de renvoyer son contenu à l'aide d'un ou plusieurs CS_write().




A.3 Paramètres et formulaires

Voici un exemple qui montre comment interroger les paramètres d'une requête http, (par exemple le nom du navigateur) et aussi de recevoir les champs d'un formulaire :




// iserv3.c : internet server

from std use calendar, files, strings, tcpip, http, tracing, exception;

//-----------------------------------------------------------------------

HTTP_SERVER_DATA  iserv;
HTTP_INITIAL_DATA init;

const uint WRITE_TIMEOUT = 30;  // write timeout : 30 secs

//-----------------------------------------------------------------------

bool is_virtual_page (int        nr,
                      TCP_CLIENT tcp_client,
                      string     url,      // virtual url
                      ARG_INFO   args)     // http arguments
{
  _unused nr;          // suppresses warning that nr is unused
  _unused tcp_client;
  _unused url;

  return !get_http_physical_file_exists (args);
}

//-----------------------------------------------------------------------

void entete_html (out string str, string titre)
{
  sprintf (out str, "<HTML><HEAD><TITLE>");
  strcatf (ref str, "%s", titre);
  strcat  (ref str, "</TITLE></HEAD>\r\n");
}

//-----------------------------------------------------------------------

void fin_html (ref string str)
{
  strcat (ref str, "</BODY></HTML>");
}

//-----------------------------------------------------------------------

void page_home (ref TCP_CLIENT tcp_client, ARG_INFO args)
{
  char str[1000], browser[4096];

  entete_html (out str, titre => "home");

  strcat (ref str, "<BODY>");
  strcat (ref str, "Bonjour !<br><br>");
  strcat (ref str, "Bienvenue sur mon nouveau serveur développé en Safe-C ");
  strcat (ref str, "(le meilleur langage bien sûr)<br><br>");

  strcat (ref str, "Ah I can see your Navigateur is : ");

  get_http_parameter (args, "User-Agent", out browser);

  strcatf (ref str, "%s", browser);

  strcat (ref str, "<br><br>");

  strcat (ref str, "<a href=page1.html> PAGE 1 </a>");
  strcat (ref str, "<br><br>");
  strcat (ref str, "<a href=page2.html> PAGE 2  (laisser un message)  </a>");

  fin_html (ref str);

  CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT);
}

//-----------------------------------------------------------------------

void page_1 (ref TCP_CLIENT tcp_client)
{
  char str[1000];

  entete_html (out str, titre => "page 1");

  strcat (ref str, "<BODY>");
  strcat (ref str, "Youpi !   Nous sommes sur la page 1 !<br><br>");

  strcat (ref str, "<br><br>");
  strcat (ref str, "<a href=home.html> HOME (revenir) </a>");

  fin_html (ref str);

  CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT);
}

//-----------------------------------------------------------------------

void page_2 (ref TCP_CLIENT tcp_client)
{
  char str[2000];

  entete_html (out str, titre => "page 2");

  strcat (ref str, "<BODY>");
  strcat (ref str, "Ah, here you will be able to leave me a message");
  strcat (ref str, " to say what you think about my server ...<br><br>");

  strcat (ref str, "<BODY>");
  strcat (ref str, "All messages will be stored in the file courier.txt on the server !<br><br>");

  strcat (ref str, "<FORM Method=POST Action=/postez.html>"
                   + "<INPUT Type=text Name=PAR1 Size=200 MaxLength=200 Value=''>"
                   + "<INPUT Type=submit Value=' POSTEZ! '>"
                   + "</FORM>");

  fin_html (ref str);

  CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT);
}

//-----------------------------------------------------------------------

void handle_virtual_page (int            nr,
                          ref TCP_CLIENT tcp_client,
                          string         url,      // virtual url
                          ARG_INFO       args)     // http arguments
{
  _unused nr;

  if (strcmp (url, "/home.html") == 0)
    page_home (ref tcp_client, args);
  else if (strcmp (url, "/page1.html") == 0)
    page_1 (ref tcp_client);
  else if (strcmp (url, "/page2.html") == 0)
    page_2 (ref tcp_client);
}

//-----------------------------------------------------------------------

void postez (ref TCP_CLIENT tcp_client, ARG_INFO args)
{
  char str[1000];
  char par1[240], par2[1000];

  entete_html (out str, titre => "postez");

  strcat (ref str, "<BODY> Nous avons bien reçu votre commentaire : <br><br>");

  get_post_parameter (args, "PAR1", out par1);

  // write into a file
  {
    FILE          file;
    PEER          info;
    char[40]      ipstr;
    DATE_TIME     now;

    TCP_query_remote_address (tcp_client, out info);
    ip_to_numericstr (info.ip, out ipstr);
    get_datetime (out now);

    if (fappend (out file, "courier.txt", UTF8) == 0)
    {
      fprintf (ref file, "-------------------------------------------------------\n");
      fprintf (ref file, "Comment received from %s:%u on %02d/%02d/%04d :\n",
               ipstr, info.port, now.day, now.month, now.year);
      fprintf (ref file, "%s\n", par1);
      fprintf (ref file, "-------------------------------------------------------\n");
      fclose (ref file);
    }
  }

  // convert posted text to avoid hacking
  expand_html (par1, out par2);
  strcat (ref str, par2);

  strcat (ref str, "<br><br>");
  strcat (ref str, "<a href=home.html> HOME (revenir) </a>");

  fin_html (ref str);

  CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT);
}

//-----------------------------------------------------------------------

void handle_post_request (int            nr,
                          ref TCP_CLIENT tcp_client,
                          string         url,      // virtual url, ex: "/getlist"
                          ARG_INFO       args)     // post & http arguments
{
  _unused nr;

  if (strcmp (url, "/postez.html") == 0)
    postez (ref tcp_client, args);
}

//-----------------------------------------------------------------------

void handler (ref HANDLER_DATA p)
{
  handle_http_call (ref p, iserv, ref init);
}

//-----------------------------------------------------------------------

void main()
{
  int rc;


  arm_exception_handler();

  open_trace ("iserv.tra");


  // init internet server

  strcpy (out iserv.html_directory, "c:/www/");
  strcpy (out iserv.start_url, "/home.html");
  iserv.is_virtual_page     = is_virtual_page;
  iserv.handle_virtual_page = handle_virtual_page;
  iserv.handle_post_request = handle_post_request;
  iserv.tracing             = true;


  // start server

  trace ("============================================\n");
  trace ("info: server starting.\n");

  rc = serve_requests (port            => 80,
                       request_handler => handler,
                       max_handlers    => 512,
                       mode            => MODE_DUAL,
                       stop            => null,
                       server_is_ready => null,
                       incoming_call   => null,
                       trace_calls     => true);

  trace ("info: server stopping. rc=%d\n", rc);
  trace ("============================================\n");
}




N'hésitez pas à consulter les composants 'http' et 'tcpip' de la librarie std pour d'autres fonctions.



Annexe B : fichiers et packages génériques


Le programme suivant liste les fichiers d'un répertoire, les stocke dans un arbre binaire et les affiche dans l'ordre :



// p.c : list files, store them in a binary tree, and display them in order.

from std use exception, strings, bintree, files, console;

typedef string^ PSTRING;   // pointeur vers string

package P = new BALANCED_BINARY_TREE (ELEMENT => PSTRING, USER_INFO => int);

int compare (int^ line, PSTRING a, PSTRING b)
{
  _unused line;
  return stricmp (a^, b^);
}

int print_element (int^ line, PSTRING p)
{
  printf ("file %d : %s\n", line^++, p^);
  return 0;
}

int free_element (int^ line, PSTRING p)
{
  _unused line;
  free p;
  return 0;
}

void main()
{
  P.BINARY_TREE t;
  int^          pline = new int;
  int           rc;
  FILE_INFO     info;
  string^       p;

  arm_exception_handler();


  create_btree (out t, pline, compare);



  rc = open_directory (out info, "c:/temp/*.*");
  while (rc == 0)
  {
    if (info.type == TYPE_REGULAR_FILE)
    {
      p = new string ' (info.name[0 : strlen(info.name)]);
      rc = insert_btree (ref t, p);
      assert rc == 0;
    }

    rc = read_directory (ref info);
  }

  if (rc != NO_MORE_FILES)
    printf ("problem reading directory\n");

  close_directory (ref info);



  // parcourir l'arbre en affichant tous les elements dans l'ordre
  pline^ = 1;
  rc = traverse_btree (ref t, print_element, order => +1);
  assert rc == 0;


  // pour effacer l'arbre, il faut d'abord le parcourir une fois pour libérer tous les strings
  rc = traverse_btree (ref t, free_element, order => +1);
  assert rc == 0;

  // ensuite on peut l'effacer
  close_btree (ref t);
}


Annexe C : afficher une image dans une fenêtre


Le programme suivant affiche une image dans une fenêtre :



// show.c : afficheur d'images
  
from std use exception, files, image, win;

//======================================================================================

int load_my_image (string filename, int id)
{
  IMAGE_INFO image;
  int        x_res, y_res;
  
  window_get_resolution (out x_res, out y_res, id);
 
  if (load_image (out image, filename) < 0)
    return -1;

  assert resize_image (ref image, (uint)x_res, (uint)y_res) == 0;
  window_raster (0, 0, x_res, y_res, image.pixel^, id);
  free_image (ref image);
  
  return 0;
}

//======================================================================================

void main()
{
  int left, top, right, bottom;
  bool quit = false;

  arm_exception_handler();
  
  get_screen_borders (out left, out top, out right, out bottom);
  
  create_screen (x => 100, y => 100, x_size => left+right+600, y_size => top+bottom+500, title => "Test");

  create_window (x => 2, y => 2, x_size => 400, y_size => 400, id => 100);
  
  create_button (x => 410, y => 20, x_size => 100, y_size => 30, text => " Load ", id => 10);
  create_button (x => 410, y => 60, x_size => 100, y_size => 30, text => " Exit ", id => 20);
  
  while (!quit)
  {
    EVENT event;
    get_event (out event);
    switch (event.typ)
    {
      case EVENT_BUTTON_PRESSED:
        switch (event.id)
        {
          case 10:
            {
              char filename[MAX_FILENAME_LENGTH];
              
              if (select_filename (    dialog_title     => "Load",
                                       mask             => "images=*.jpg;*.bmp;*.gif\n",
                                       saving           => false,     // false=load, true=save
                                   out filename         => filename,
                                       default_filename => "") == 0)
              {
                load_my_image (filename, id => 100);
              }
            }
            break;
            
          case 20:
            quit = true;
            break;
            
          default:
            break;
        }
        break;
        
      default:
        break;
    }
  }
  
  close_screen ();
}

//======================================================================================