Licence CC 0

Quelques petites choses en C

Qui viennent du SdZ !

Préambule

Ceci est à l’origine un post sur mon blog, mais je me suis dit que j’allais vous partager quelques codes C amusants.

Le C est un langage que je n’ai pas pratiqué depuis bien longtemps. Il faut dire, hormis la programmation système et quelques niches, on ne le voit pas trop ailleurs. En tout cas au travail, je n’ai jamais eu à en faire. Et d’un point de vue personnel, je suis plus sur d’autres langages comme Python ou Haskell. Mais il n’empêche que j’ai stocké, au gré de mes pérégrinations, quelques liens intéressants sur ce qu’on peut faire d’amusant en poussant ce langage hors des sentiers battus.

Une classe string

Toute personne ayant fait du C le dira (sauf @Taurre mais il est de mauvaise foi :p ), la gestion des chaînes de caractères est très… sommaire, par rapport à d’autres langages. Allouer des tableaux de char soi-même, c’est sympa 5 min, mais bon, si l’on avait une vraie encapsulation, comme les classes string disponibles dans quasiment tous les langages venus après C, ça serait mieux. Ça tombe bien, @Maëlan l’a fait, à base de listes chaînées pour stocker des sous-chaînes et faciliter les opérations de concaténation, etc.

Le code a été posté de base sur le Site du Zéro, mais par soucis de postérité, on va le remettre ici.

String.h
/**
***  String.h
***
***    module:   String  −  header file
***    function: provides the `String` object type (abstract data type) and
***              functions for using it; the `String` object type is designed to
***              manage dynamic and/or large characters strings.
***              This header is the interface file of the module, it provides
***              the incomplete type declaration of `String` (because `String`
***              is an ADT) and the prototype of the usable functions.
***    author:   Maëlan (aka Maëlan44)
***              (see http://www.siteduzero.com/membres-294-232877.html )
***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
***
***    last modified: 09/07/2011, 15:15
***
**/

#ifndef INCLUDED_STRING_2011_08_25_17_30_MM
#define INCLUDED_STRING_2011_08_25_17_30_MM

#include <stddef.h>   // for size_t
#include <stdio.h>    // for FILE

/*** TYPES ***/

/* the String object, to manage large and/or dynamic strings;
   it takes the form of a linked list of datas including an allocated block
   and informations concerning this block and the linked list */
typedef  struct String  String;

/*** FUNCTIONS ***/

/* Creates a String object filled with the content of the string passed; if NULL
   is passed,  lets it null (ie. empty and with no capacity) */
String* String_new(const char*);

/* Creates an empty String object with at least the capacity specified */
String* String_newEmpty(size_t);

/* Creates a new String object that is a copy of the passed one */
String* String_copy(const String*);

/* Deletes completely the String object */
void String_del(String*);

/* Returns the size of the String */
/*extern inline*/  size_t String_size(const String*);
/* idem */
#define  String_length  String_size

/* Returns the capacity of the String */
/*extern inline*/  size_t String_capacity(const String*);

/* boolean: Is the String empty (ie. the size is zero)? */
/*extern inline*/  int String_isEmpty(const String*);

/* boolean: Is the String null (ie. the size and capacity are zero)? */
/*extern inline*/  int String_isNull(const String*);

/* Empties the String, setting its size to zero without altering its capacity */
void String_empty(String*);

/* Empties the String, setting its size and capacity to zero */
void String_clear(String*);

/* Returns the character of index specified in the String; returns '\\0' if the
   index is out of range */
char String_char(/*const*/ String*, size_t);

/* Sets the character of index specified in the String to the value specified;
   if it is '\\0', then this index will become the end of the String; if the
   index is out of range, the linked String is not modified */
void String_setChar(String*, size_t, char);

/* compare the two Strings the same way as strcmp does */
int String_cmp(const String*, const String*);

/* compare the String object with the ”regular” characters string, the same way
   as strcmp does */
int String_cmpCStr(const String*, const char*);

/* returns the index of the 1st occurrence of the character in the String, or a
   negative number if the the character is no found */
ptrdiff_t String_searchChar(const String*, char c);

/* returns the index of the last occurrence of the character in the String, or a
   negative number if the the character is no found */
ptrdiff_t String_searchLastChar(const String*, char c);

/* Concatenates a copy of the 2nd String object to the first; returns the final
   String, or NULL if there was an error */
String* String_concat(String*, const String*);

/* Write the content of the String on the stream */
void String_fput(const String*, FILE*);

/* idem, the stream is `stdout` */
/*extern inline*/  void String_put(const String*);

/* Returns a buffer containing the content of the String in the form of a plain
   characters string; this buffer shall not be modified by the user, and it may
   be modified by the String functions to stay up-to-date or be freed if it is
   no longer the case */
const char* String_cStr(String*);

/* Returns the buffer containing the "cstr" of the String if it exists and is
   up-to-date, NULL otherwise */
/*extern inline*/  const char* String_getCStr(const String*);

#endif
String.c
/**
***  String.c
***
***    module:   String  −  source file
***    function: provides the `String` object type (abstract data type) and
***              functions for using it; the `String` object type is designed to
***              manage dynamic and/or large characters strings.
***              This file is the main source file of the module, it contains
***              the full declaration of the type `String`, as well as others
***              types and additionnal function declarations (static), both used
***              internally by the implementation. Of course it contains (in
***              fact, includes) also the definitions of all the functions.
***    author:   Maëlan (aka Maëlan44)
***              (see http://www.siteduzero.com/membres-294-232877.html )
***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
***
***    last modified: 09/06/2011, 18:40
***
**/

#include "String.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "error.h"

/*** CONSTANTS ***/

/* see below */
#define  STRING_LOG2BLKSZ      8

/* maximal lenght of the buffer of a String block */
#define  STRING_TOTALMAXBLKSZ  (1<<STRING_LOG2BLKSZ)

/* maximal lenght of the buffer of a String block, without the terminating '\\0' */
#define  STRING_MAXBLKSZ       (STRING_TOTALMAXBLKSZ-1)

/* lenght of the static buffer of a String block; it is designed for a struct of
   type `StringBlock` to fit in 32 bytes */
//#define  STRING_TOTALBUFSZ     ( (32-sizeof(*char)-2*STRING_LOG2BLKSZ/8)       \\
                                 / sizeof(char) )
#define STRING_TOTALBUFSZ 19

/* lenght of the static buffer of a String block, without the terminating '\\0' */
#define  STRING_BUFSZ          (STRING_TOTALBUFSZ-1)

/*** TYPES ***/

/* NOTE:
 * Here, "block capacity" will mean the allocated length for the block minus 1,
 * the latter being reserved for the terminating '\\0'. The "String capacity" or
 * "total capacity" will mean the sum of the capacities of all the blocks
 * composing the String.
 * Similarly, "block size" will mean the used length without the '\\0'. The
 * "String size" of "total size" is the sum of the sizes of all the blocks.
 */

/* a String block, link of a linked list;
   contains a buffer and informations for using it */
typedef struct StringBlk {
    /* static buffer: optimisation for short strings or string blocks
       [OPT:buf] */
    char buf[STRING_TOTALBUFSZ];
    /* buffer; shall point to the member `buf` if it is used, else to the
       allocated memory
       /!\\  Shall never be NULL! */
    char* p;
    /* block size (shall be lesser than or equal to the member `capac`) */
    unsigned int size   :STRING_LOG2BLKSZ,
    /* block capacity (shall be at least STRING_BUFSZ, since it is the size
       of the static buffer; can not be greater than STRING_MAXBLKSZ) */
                 capac  :STRING_LOG2BLKSZ;
    /* block following this one in the linked list */
    struct StringBlk* next;
} StringBlk;

/* the String object itself;
   it takes the form of a linked list of blocks, each including a buffer and
   informations concerning it */
struct String {
    /* total length */
    size_t  size,
    /* total capacity */
            capac;
    /* first block of the string */
    StringBlk *first,
    /* last block: optimisation for operations on the end of the string,
       such as concatenating or adding a character [OPT:last] */
              *last;
    /* optimisation: a cursor pointing to the last character read [OPT:cur] */
    struct {   size_t pos;   /* index of the 1st char. of the block pointed */
               StringBlk* blk;
           } cur;
    /* buffer containing the full character string obtained via
       `String_cStr` (and returned as constant); shall be NULL if this
       function had not be called yet, or if the content is outdated (in
       this case, it shall be freed if needed).
       For reasons of optimisation, this buffer can be that of the first
       block of the String, in which case it shall not be freed.
       If the buffer exists, it can be used to perform optimisations on
       operations involving reading content [OPT:cstr]. */
    char* cStr;
};

/*** AUXILIARY FUNCTIONS ***/

/* returns the power of 2 greater than or equal to `n` */
static  unsigned int pow2sup(unsigned int n) {

    if(!n)   return 0;
    unsigned int r= 1;
    while(n>r)   r<<=1;
    return r;

        /**   // solution found here < http://forum.hardware.fr/hfr/Programmation/C-2/quizz-calculer-superieure-sujet_52944_1.htm#t757526 >
        if(!n || !(n&(n-1))   return n;
        __asm__ (
            "bsr   eax,   n"
            "mov   n,   eax"
        );
        return 1<<n;
        **/
}

/*** MAIN FUNCTIONS ***/

/** functions for object `StringBlock` **/

/* Creates a new String block filled with the content of the string passed, or
   at most its STRING_MAXBLKSZ first characters; the second argument must be the
   size of the string passed */
static  StringBlk* StringBlk_new(const char*, size_t);

/* Creates a new empty String block with at least the specified capacity */
static  StringBlk* StringBlk_newEmpty(unsigned int);

/* Creates a copy of the String block (ie. a new String block filled with the
   content of the one passed) */
static inline  StringBlk* StringBlk_copy(const StringBlk*);

/* Deletes the String block and all the following String blocks of the linked
   list to which it belongs */
static  void StringBlk_del(StringBlk*);

/* boolean: Does the String block uses its static buffer? */
static inline  int StringBlk_useStaticBuf(const StringBlk* this);

#include "String.c.1"

/** functions for object `String` **/

/* boolean: Has the String an existing "cstr"? */
static inline  int String_hasCStr(const String*);

/* boolean: Has the String an existing "cstr", the buffer of which is also that
   of the 1st block? */
static inline  int String_hasSharedCStr(const String*);

/* boolean: Has the String an existing "cstr", the buffer of which is allocated
   separately? */
static inline  int String_hasSeparateCStr(const String*);

/* Delete the "cstr" of the String */
static inline  void String_delCStr(String*);

#include "String.c.2"
String.c.1
/**
***  String.c.1
***
***    module:   String  −  source file (part 1)
***    function: provides the `String` object type (abstract data type) and
***              functions for using it; the `String` object type is designed to
***              manage dynamic and/or large characters strings.
***              This file contains the definitions of functions working on the
***              `StringBlock` object type.
***    author:   Maëlan (aka Maëlan44)
***              (see http://www.siteduzero.com/membres-294-232877.html )
***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
***
***    last modified: 09/07/2011, 20:45
***
**/

static  StringBlk* StringBlk_newEmpty(unsigned int capac) {
    StringBlk* r =  malloc(sizeof(StringBlk));
    if(r==NULL) {
        errorMessage("malloc", "when allocating a String block");
        return NULL;
    }

    if(capac<=STRING_BUFSZ) {
        r->p =      r->buf;
        r->capac =  STRING_BUFSZ;
    } else {
        r->capac =  (capac>STRING_MAXBLKSZ)? STRING_MAXBLKSZ : capac;
        //r->capac =  log2sup(r->blk.capac) - 1;
        r->p =  malloc((r->capac+1)*sizeof(char));
        if(r->p == NULL) {
            errorMessage("malloc", "when allocating a dynamic"
             " buffer for a String block");
            free(r);
            return NULL;
        }
    }

    *r->p =    '\\0';
    r->size =  0;
    //r->next =      NULL;

    return r;
}

static  StringBlk* StringBlk_new(const char* s, size_t size) {
    StringBlk* r;

    //if(s==NULL)    size =  0;
    r =  StringBlk_newEmpty(size);
    if(r==NULL)    return NULL;

    r->size =  (size > r->capac)? r->capac : size;
    /*if(s!=NULL)    */strncpy(r->p, s, r->size);
    r->p[r->size] =  '\\0';

    return r;
}

static inline  StringBlk* StringBlk_copy(const StringBlk* this)
    { //if(this==NULL)    return NULL;
    return StringBlk_new(this->p, this->size); }

static  void StringBlk_del(StringBlk* this) {
    StringBlk* blk;
    while(this!=NULL) {
        blk =   this;
        this =  this->next;
        if(!StringBlk_useStaticBuf(blk))
            free(blk->p);
        free(blk);
    }
}

static inline  int StringBlk_useStaticBuf(const StringBlk* this)
    { return this->p == this->buf; }
String.c.2
/**
***  String.c.2
***
***    module:   String  −  source file (part 2)
***    function: provides the `String` object type (abstract data type) and
***              functions for using it; the `String` object type is designed to
***              manage dynamic and/or large characters strings.
***              This file contains the definitions of functions working on the
***              `String` object type.
***    author:   Maëlan (aka Maëlan44)
***              (see http://www.siteduzero.com/membres-294-232877.html )
***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
***
***    last modified: 09/07/2011, 18:50
***
**/

/**  CREATION, DELETION
 **    newEmpty, new, copy, del
 **/

String* String_newEmpty(size_t capac) {
    String* r;
    StringBlk** p;

    r =  malloc(sizeof(String));
    if(r==NULL) {
        errorMessage("malloc", "when allocating a String");
        return NULL;
    }

    r->capac =  0;
    p =  &r->first;
    do {    /* at least one block (so STRING_BUFSZ) is created, even if the
               capacity required is 0 */
        *p =  StringBlk_newEmpty(capac-r->capac);
        if(*p==NULL) {
            errorMessage("StringBlk_newEmpty", "when creating a"
             " String block");
            String_del(r);
            return NULL;
        }
        r->capac +=  (*p)->capac;
        if(capac > r->capac)    p =  &(*p)->next;
    } while(capac > r->capac);

    r->last =     *p;
    (*p)->next =  NULL;

    r->size =     0;
    r->cur.pos =  0;
    r->cur.blk =  r->first;
    r->cStr =     r->first->p;

    return r;
}

String* String_new(const char* s) {
    String* r;

    r =  malloc(sizeof(String));
    if(r==NULL) {
        errorMessage("malloc", "when allocating a String");
        return NULL;
    }

    r->size =   0;
    r->capac =  0;
    if(s==NULL) {
        r->first =  NULL;
        r->last =   NULL;
    } else {
        size_t size =  strlen(s);
        StringBlk** p =  &r->first;
        do {
            *p =  StringBlk_new(s+r->size, size-r->size);
            if(*p==NULL) {
                errorMessage("StringBlk_new", "when creating a"
                 " String block");
                String_del(r);
                return NULL;
            }
            r->capac +=  (*p)->capac;
            r->size +=   (*p)->size;
            if(size != r->size)    p =  &(*p)->next;
        } while(size != r->size);
        r->last =     *p;
        (*p)->next =  NULL;
    }

    r->cur.pos =  0;
    r->cur.blk =  r->first;
    r->cStr =     NULL;

    return r;
}

String* String_copy(const String* this) {
    if(this==NULL)    return NULL;

    if(String_hasCStr(this))
        return String_new(this->cStr);

    String* r;

    r =  malloc(sizeof(String));
    if(r==NULL) {
        errorMessage("malloc", "when allocating a String");
        return NULL;
    }

    StringBlk *blk =  this->first,
              **p =   &r->first;
    r->size =   0;
    r->capac =  0;
    if(blk==NULL)    r->first =  NULL;
    else while(blk!=NULL) {
        *p =  StringBlk_copy(blk);
        if(*p==NULL) {
            errorMessage("StringBlk_copy", "when creating a copy of"
             " a String block");
             String_del(r);
             return NULL;
        }
        r->capac +=  (*p)->capac;
        r->size +=   (*p)->size;
        blk =  blk->next;
        if(blk!=NULL)    p =  &(*p)->next;
    }
    r->last =     *p;
    (*p)->next =  NULL;
    r->cur.pos =  0;
    r->cur.blk =  r->first;
    r->cStr =     NULL;

    return r;
}

void String_del(String* this) {
    if(this==NULL)    return;
    if(String_hasSeparateCStr(this))
        free(this->cStr);
    if(!String_isNull(this))
        StringBlk_del(this->first);
    free(this);
}

/**  SIZE, CAPACITY
 **    size, capacity, isEmpty, isNull, empty, clear
 **/

inline  size_t String_size(const String* this)
    { return (this!=NULL)? this->size : 0; }

inline  size_t String_capacity(const String* this)
    { return (this!=NULL)? this->capac : 0; }

inline  int String_isEmpty(const String* this)
    { return (this!=NULL)? !this->size : 1; }

inline  int String_isNull(const String* this)
    { return (this!=NULL)? !this->capac : 1; }

void String_empty(String* this) {
    if(this==NULL)    return;
    StringBlk* p =  this->first;
    while(p!=NULL) {
        *p->p =    '\\0';
        p->size =  0;
        p =        p->next;
    }
    this->size =  0;
}

void String_clear(String* this) {
    if(this==NULL)    return;
    if(String_hasCStr(this)) {
        if(String_hasSeparateCStr(this))
            free(this->cStr);
        this->cStr =  NULL;
    }
    if(!String_isNull(this)) {
        StringBlk_del(this->first);
        this->first =  NULL;
        this->last =   NULL;
    }
    this->capac =    0;
    this->size =     0;
    this->cur.pos =  0;
    this->cur.blk =  NULL;
}

/**  CSTR
 **    getCStr, delCStr, hasCStr, hasSharedCStr, hasSeparateCStr
 **/

inline  const char* String_getCStr(const String* this)
    { return this->cStr; }

void String_delCStr(String* this) {
    free(this->cStr);
    this->cStr =  NULL;
}

static inline  int String_hasCStr(const String* this)
    { return this->cStr != NULL; }

static inline  int String_hasSharedCStr(const String* this)
    { return this->capac  &&  this->cStr == this->first->p; }

static inline  int String_hasSeparateCStr(const String* this)
    { return this->cStr != NULL  &&  this->cStr != this->first->p; }

/**  BASIC READ / WRITE
 **    char, setChar
 **/

char String_char(/*const*/ String* this, size_t i) {
    if(this==NULL || i>=this->size)    return '\\0';

    if(String_hasCStr(this))    return this->cStr[i];

    if(i < this->cur.pos) {
        this->cur.pos =  0;
        this->cur.blk =  this->first;
    }
    while(i >= this->cur.pos + this->cur.blk->size) {
        this->cur.pos +=  this->cur.blk->size;
        this->cur.blk =   this->cur.blk->next;
    }
    return this->cur.blk->p[i - this->cur.pos];
}

void String_setChar(String* this, size_t i, char c) {
    if(this==NULL || i>=this->size)    return;

    if(i < this->cur.pos) {
        this->cur.pos =  0;
        this->cur.blk =  this->first;
    }
    while(i >= this->cur.pos + this->cur.blk->size) {
        this->cur.pos +=  this->cur.blk->size;
        this->cur.blk =   this->cur.blk->next;
    }
    this->cur.blk->p[i-this->cur.pos] =  c;

    if(c=='\\0') {
        this->size =  i;
        this->cur.blk->size =  i - this->cur.pos;
        this->last =  this->cur.blk;
        if(this->last->next!=NULL) {
            StringBlk_del(this->last->next);
            this->last->next =  NULL;
        }
    }

    if(String_hasSeparateCStr(this)) {
        this->cStr[i] =  c;
        if(c=='\\0') {
            // cf here: < http://www.siteduzero.com/forum-83-704272-p1-causes-d-echec-de-realloc.html >
            char* tmp =  realloc(this->cStr, (i+1)*sizeof(char));
            if(tmp==NULL)
                String_delCStr(this);
            else
                this->cStr =  tmp;
        }
    }
}

/**  UTILITIES
 **    cmp, cmpCStr, searchChar, searchLastChar
 **/

int String_cmp(const String* s1, const String* s2) {
    if(String_isEmpty(s1))    return !String_isEmpty(s2);
    if(String_isEmpty(s2))    return -1;

    if(String_hasCStr(s1) && String_hasCStr(s2))
        return strcmp(s1->cStr, s2->cStr);

    StringBlk *b1 =  s1->first,
              *b2 =  s2->first;
    size_t i1 =  0,
           i2 =  0;

    while(b1->p[i1] == b2->p[i2]) {
        if(b1->p[i1]=='\\0')    return 0;
        ++i1;    ++i2;
        while(i1 >= b1->size) {    /* while and not if, since there can
                                     be empty blocks */
            i1 =  0;
            b1 =  b1->next;
            if(b1==NULL)    return s1->size < s2->size;
        }
        while(i2 >= b2->size) {
            i2 =  0;
            b2 =  b2->next;
            if(b2==NULL)    return -(s1->size > s2->size);
        }
    }
    return  (b1->p[i1] > b2->p[i2])?  -1 : 1;
}

int String_cmpCStr(const String* this, const char* s) {
    if(String_isEmpty(this))    return s!=NULL && *s!='\\0';
    if(s==NULL || *s=='\\0')     return -1;

    if(String_hasCStr(this))
        return strcmp(this->cStr, s);

    StringBlk *blk =  this->first;
    size_t i =  0,
           j =  0;

    while(blk->p[i] == s[j]) {
        if(s[j]=='\\0')    return 0;
        ++i;    ++j;
        while(i >= blk->size) {    /* while and not if, since there can
                                     be empty blocks */
            i =    0;
            blk =  blk->next;
            if(blk==NULL)    return s[j]!='\\0';
        }
    }
    return  (blk->p[i] > s[j])?  -1 : 1;
}

ptrdiff_t String_searchChar(const String* this, char c) {
    if(String_isEmpty(this))    return (c=='\\0')? 0 : -1;
    if(c=='\\0')                 return this->size;

    if(String_hasCStr(this))    return strchr(this->cStr, c) - this->cStr;

    StringBlk* blk =  this->first;
    size_t pos =      0;
    char* r;
    while(blk!=NULL) {
        r =  strchr(blk->p, c);
        if(r!=NULL)    return r - blk->p + pos;
        pos +=  blk->size;
        blk =   blk->next;
    }
    return -1;
}

ptrdiff_t String_searchLastChar(const String* this, char c) {
    if(String_isEmpty(this))    return (c=='\\0')? 0 : -1;
    if(c=='\\0')                 return this->size;

    if(String_hasCStr(this))    return strrchr(this->cStr, c) - this->cStr;

    StringBlk* blk =   this->first;
    size_t pos =       0;
    char* r;
    ptrdiff_t found =  -1;
    while(blk!=NULL) {
        r =  strrchr(blk->p, c);
        if(r!=NULL)    return found =  r - blk->p + pos;
        pos +=  blk->size;
        blk =   blk->next;
    }
    return found;
}

/**  INSERTION
 **    concat
 **/

String* String_concat(String* this, const String* s2) {
    if(this==NULL)    return NULL;
    if(s2==NULL)      return this;

    String* cpy =  String_copy(s2);
    if(cpy==NULL) {
        errorMessage("String_copy", "when copying a String to add it to"
         " the end of another one");
        return NULL;
    }

    if(String_isNull(this))    this->first =       cpy->first;
    else                       this->last->next =  cpy->first;
    this->last =    cpy->last;
    this->capac +=  cpy->capac;
    this->size +=   cpy->size;
    /*  /!\\  UPDATE CSTR DATAS */

    free(cpy);
    return this;
}

/**  IN / OUT
 **    fput, put
 **/

void String_fput(const String* this, FILE* stream) {
    if(this==NULL || stream==NULL)    return;
    StringBlk* p =  this->first;
    while(p!=NULL) {
        fputs(p->p, stream);
        p =  p->next;
    }
}

inline  void String_put(const String* this)
    { String_fput(this, stdout); }

Une implémentation de vector

De même que pour string, on ne dispose de base en C d'aucune abstraction pour un conteneur dynamique comme on en a en C++, Python, etc. Et bon, faire 40 malloc / free par fonction, c’est sympa 5 min. Mais c’était sans compter sur les expériences que Qnope a fait subir au préprocesseur (disponible ici) et dont nous remettrons le code ici dans un soucis de disponibilité. Notons au passage que le préprocesseur est souvent utilisé pour implémenter une pseudo-généricité en C.

Commençons par la gestion des erreurs.

error.h
#ifndef ERROR_H_INCLUDED
#define ERROR_H_INCLUDED
 
typedef enum{
    /** Succès **/
    QGM_SUCCESS,            /** Rien à signalé **/
 
    /** Mauvaise allocations **/
    QGM_BADALLOC,           /** Allocation de mémoire échouée **/
 
    /** Fuites de mémoires **/
    QGM_MEMORYLEAKSALLOC,   /** Fuites de mémoires  (Allocation) **/
 
    /** L'élément n'existe pas **/
    QGM_NOELEMENTALLOC,     /** Pointeur à détruire n'existe pas **/
    QGM_NOELEMENTSTACK,     /** Aucun élément à dépiler **/
    QGM_NOELEMENTQUEUE,     /** Aucun élement à défiler **/
    QGM_NOELEMENTVECTOR,    /** Aucun élément à supprimer **/
}QGM_Error_t;
 
/** Variable recevant l'erreur **/
extern QGM_Error_t QGM_Error;
 
/** Synopsis    : Indique si il y a ou non une erreur
    Retour      : 1 : erreur. 0 : Pas d'erreur **/
int QGM_IsError(void);
 
/** Synopsis    : Permet de récupèrer l'erreur
    Retour      : Chaîne de caractère contenant l'erreur **/
char const *QGM_GetError(void);
 
#endif
error.c
#include <stdio.h>
 
#include "error.h"
 
/** Variable recevant l'erreur **/
QGM_Error_t QGM_Error = QGM_SUCCESS;
 
/** Tableau des erreurs en fonction de QGM_Error **/
char const *QGM_String_Error[] = {
/** Succès **/
"",
 
/** Mauvaise allocations **/
"Not space\n",
 
/** Fuites de mémoires **/
"Memory Leaks Are Detected (Allocation)\n",
 
/** L'élément n'existe pas **/
"This pointer can't be free (Allocation)\n",
"Nothing element to pop (Stack)\n",
"Nothing element to pop (Queue)\n",
"Nothing element to delete (Vector)\n",[[secret]]

};
 
int QGM_IsError(void){
    return (QGM_Error == QGM_SUCCESS) ? 0 : 1;
}
 
/** Fonction permettant de récupérer l'erreur **/
char const *QGM_GetError(void)
{
    char const *t = QGM_String_Error[QGM_Error];
    QGM_Error = QGM_SUCCESS;
    return t;
}

Maintenant, le code qui nous intéresse vraiment (attention, détournement de préprocesseur en approche).

error.c
#ifndef DATA_H_INCLUDED
#define DATA_H_INCLUDED
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include "error.h"
 
/*****************************************************************************/
/********************************** HEADER ***********************************/
/*****************************************************************************/
/********************************** VECTOR ***********************************/
/*****************************************************************************/
#define QGM_Vector_Header(TYPE)\
typedef struct{\
    TYPE *data;\
    size_t size;\
    unsigned nElem;\
}QGM_Vector_##TYPE;\
\
QGM_Vector_##TYPE *QGM_Vector_##TYPE##_Init(void);\
int QGM_Vector_##TYPE##_Resize(QGM_Vector_##TYPE*);\
int QGM_Vector_##TYPE##_PushFront(QGM_Vector_##TYPE*, TYPE);\
int QGM_Vector_##TYPE##_PushBack(QGM_Vector_##TYPE*, TYPE);\
int QGM_Vector_##TYPE##_Insert(QGM_Vector_##TYPE*, TYPE, unsigned);\
TYPE QGM_Vector_##TYPE##_PopFront(QGM_Vector_##TYPE*);\
TYPE QGM_Vector_##TYPE##_PopBack(QGM_Vector_##TYPE*);\
TYPE QGM_Vector_##TYPE##_Erase(QGM_Vector_##TYPE*, unsigned);\
size_t QGM_Vector_##TYPE##_Size(QGM_Vector_##TYPE*);\
void QGM_Vector_##TYPE##_Quit(QGM_Vector_##TYPE*);
 
/*****************************************************************************/
/********************************** STACK ************************************/
/*****************************************************************************/
#define QGM_Stack_Header(TYPE)\
typedef struct{\
    TYPE *data;\
    size_t size;\
    unsigned nElem;\
}QGM_Stack_##TYPE;\
\
QGM_Stack_##TYPE *QGM_Stack_##TYPE##_Init(void);\
int QGM_Stack_##TYPE##_Resize(QGM_Stack_##TYPE*);\
int QGM_Stack_##TYPE##_Push(QGM_Stack_##TYPE*, TYPE);\
TYPE QGM_Stack_##TYPE##_Pop(QGM_Stack_##TYPE*);\
size_t QGM_Stack_##TYPE##_Size(QGM_Stack_##TYPE*);\
void QGM_Stack_##TYPE##_Quit(QGM_Stack_##TYPE*);
 
/*****************************************************************************/
/********************************** Queue ************************************/
/*****************************************************************************/
#define QGM_Queue_Header(TYPE)\
typedef struct{\
    TYPE *data;\
    size_t size;\
    unsigned nElem;\
    unsigned pop;\
    unsigned push;\
}QGM_Queue_##TYPE;\
\
QGM_Queue_##TYPE *QGM_Queue_##TYPE##_Init(void);\
int QGM_Queue_##TYPE##_Resize(QGM_Queue_##TYPE*);\
void QGM_Queue_##TYPE##_Adjust(QGM_Queue_##TYPE*);\
int QGM_Queue_##TYPE##_Push(QGM_Queue_##TYPE*, TYPE);\
TYPE QGM_Queue_##TYPE##_Pop(QGM_Queue_##TYPE*);\
size_t QGM_Queue_##TYPE##_Size(QGM_Queue_##TYPE*);\
void QGM_Queue_##TYPE##_Quit(QGM_Queue_##TYPE*);
 
/*****************************************************************************/
/********************************* FONCTIONS *********************************/
/*****************************************************************************/
/********************************** VECTOR ***********************************/
/*****************************************************************************/
 
#define QGM_Vector_Functions(TYPE)\
QGM_Vector_##TYPE *QGM_Vector_##TYPE##_Init(void){\
    QGM_Vector_##TYPE *self;\
    if((self = (malloc)(sizeof *self)) == NULL)\
        goto _error;\
    if((self->data = (malloc)(sizeof *self->data)) == NULL)\
        goto _error;\
    self->nElem = 0;\
    self->size  = 1;\
    return self;\
    _error:\
        free(self);\
        QGM_Error = QGM_BADALLOC;\
        return NULL;\
}\
\
int QGM_Vector_##TYPE##_Resize(QGM_Vector_##TYPE *self){\
    TYPE *ptr = (realloc)(self->data,\
                          self->size * 3 * sizeof *ptr);\
    if(ptr == NULL)\
        goto _error;\
    self->size *= 3;\
    self->data  = ptr;\
    return 1;\
    _error :\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
\
int QGM_Vector_##TYPE##_PushFront(QGM_Vector_##TYPE *self, TYPE data){\
    if(self->nElem >= self->size)\
        if(QGM_Vector_##TYPE##_Resize(self) == 0)\
            goto _error;\
    memmove(self->data + 1,\
            self->data,\
            self->nElem++ * sizeof *self->data);\
    *self->data = data;\
    return 1;\
    _error :\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
int QGM_Vector_##TYPE##_PushBack(QGM_Vector_##TYPE *self, TYPE data){\
    if(self->nElem >= self->size)\
        if(QGM_Vector_##TYPE##_Resize(self) == 0)\
            goto _error;\
    self->data[self->nElem++] = data;\
    return 1;\
    _error :\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
\
int QGM_Vector_##TYPE##_Insert\
(QGM_Vector_##TYPE *self, TYPE data, unsigned place){\
    if(self->nElem >= self->size)\
        if(QGM_Vector_##TYPE##_Resize(self) == 0)\
            goto _error;\
    if(place >= self->nElem)\
        place = self->nElem;\
    memmove(self->data + place + 1,\
            self->data + place,\
           (self->nElem++ - place) * sizeof *self->data);\
    self->data[place] = data;\
    return 1;\
    _error:\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
\
TYPE QGM_Vector_##TYPE##_PopFront(QGM_Vector_##TYPE *self){\
    TYPE ifError;\
    TYPE data;\
    if(self->nElem == 0)\
        goto _error;\
    data = *self->data;\
    memmove(self->data,\
            self->data + 1,\
            --self->nElem * sizeof *self->data);\
    return data;\
    _error:\
        QGM_Error = QGM_NOELEMENTVECTOR;\
        memset(&ifError, 255, sizeof ifError);\
        return ifError;\
}\
\
TYPE QGM_Vector_##TYPE##_PopBack(QGM_Vector_##TYPE *self){\
    TYPE ifError;\
    if(self->nElem == 0)\
        goto _error;\
    return self->data[--self->nElem];\
    _error:\
        QGM_Error = QGM_NOELEMENTVECTOR;\
        memset(&ifError, 255, sizeof ifError);\
        return ifError;\
}\
\
TYPE QGM_Vector_##TYPE##_Erase(QGM_Vector_##TYPE *self, unsigned place){\
    TYPE ifError;\
    TYPE data;\
    if(self->nElem == 0)\
        goto _error;\
    if(place >= self->nElem)\
        place = self->nElem;\
    data = self->data[place];\
    memmove(self->data + place,\
            self->data + place + 1,\
         (--self->nElem - place) * sizeof *self->data);\
    return data;\
    _error:\
        QGM_Error = QGM_NOELEMENTVECTOR;\
        memset(&ifError, 255, sizeof ifError);\
        return ifError;\
}\
\
size_t QGM_Vector_##TYPE##_Size(QGM_Vector_##TYPE *self){\
    return self->nElem;\
}\
\
void QGM_Vector_##TYPE##_Quit(QGM_Vector_##TYPE *self){\
    free(self->data);\
    free(self);\
}
 
/*****************************************************************************/
/********************************** STACK ************************************/
/*****************************************************************************/
 
#define QGM_Stack_Functions(TYPE)\
QGM_Stack_##TYPE *QGM_Stack_##TYPE##_Init(void){\
    QGM_Stack_##TYPE *self;\
    if((self = (malloc)(sizeof *self)) == NULL)\
        goto _error;\
    if((self->data = (malloc)(sizeof *self->data)) == NULL)\
        goto _error;\
    self->nElem = 0;\
    self->size  = 1;\
    return self;\
    _error:\
        free(self);\
        QGM_Error = QGM_BADALLOC;\
        return NULL;\
}\
\
int QGM_Stack_##TYPE##_Resize(QGM_Stack_##TYPE *self){\
    TYPE *ptr = (realloc)(self->data,\
                          self->size * 3 * sizeof *ptr);\
    if(ptr == NULL)\
        goto _error;\
    self->size *= 3;\
    self->data  = ptr;\
    return 1;\
    _error :\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
\
int QGM_Stack_##TYPE##_Push(QGM_Stack_##TYPE *self, TYPE data){\
    if(self->nElem >= self->size)\
        if(QGM_Stack_##TYPE##_Resize(self) == 0)\
            goto _error;\
    self->data[self->nElem++] = data;\
    return1;\
    _error :\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
\
TYPE QGM_Stack_##TYPE##_Pop(QGM_Stack_##TYPE *self){\
    TYPE ifError;\
    if(self->nElem == 0)\
        goto _error;\
    return self->data[--self->nElem];\
    _error:\
        QGM_Error = QGM_NOELEMENTSTACK;\
        memset(&ifError, 255, sizeof ifError);\
        return ifError;\
}\
\
size_t QGM_Stack_##TYPE##_Size(QGM_Stack_##TYPE *self){\
    return self->nElem;\
}\
\
void QGM_Stack_##TYPE##_Quit(QGM_Stack_##TYPE *self){\
    free(self->data);\
    free(self);\
}
 
/*****************************************************************************/
/********************************** Queue ************************************/
/*****************************************************************************/
 
#define QGM_Queue_Functions(TYPE)\
QGM_Queue_##TYPE *QGM_Queue_##TYPE##_Init(void){\
    QGM_Queue_##TYPE *self;\
    if((self = (malloc)(sizeof *self)) == NULL)\
        goto _error;\
    if((self->data = (malloc)(sizeof *self)) == NULL)\
        goto _error;\
    self->nElem = 0;\
    self->size  = 1;\
    self->push = self->pop = 0;\
    return self;\
    _error:\
        free(self);\
        QGM_Error = QGM_BADALLOC;\
        return NULL;\
}\
\
int QGM_Queue_##TYPE##_Resize(QGM_Queue_##TYPE *self){\
    TYPE *ptr = (realloc)(self->data,\
                          self->size * 3 * sizeof *ptr);\
    if(ptr == NULL)\
        goto _error;\
    self->size *= 3;\
    self->data  = ptr;\
    return 1;\
    _error:\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
\
void QGM_Queue_##TYPE##_Adjust(QGM_Queue_##TYPE *self){\
    memmove(self->data,\
            self->data + self->pop,\
           (self->push - self->pop) * sizeof *self->data);\
    self->push -= self->pop;\
    self->pop = 0;\
}\
\
int QGM_Queue_##TYPE##_Push(QGM_Queue_##TYPE *self, TYPE data){\
    if(self->nElem >= self->size)\
        if(QGM_Queue_##TYPE##_Resize(self) == 0)\
            goto _error;\
    if(self->push == self->size)\
        QGM_Queue_##TYPE##_Adjust(self);\
    self->data[self->push++] = data;\
    ++self->nElem;\
    return 1;\
    _error:\
        QGM_Error = QGM_BADALLOC;\
        return 0;\
}\
\
TYPE QGM_Queue_##TYPE##_Pop(QGM_Queue_##TYPE *self){\
    TYPE ifError;\
    TYPE data;\
    if(self->nElem == 0)\
        goto _error;\
    data = self->data[self->pop++];\
    if(--self->nElem == 0)\
        self->push = self->pop = 0;\
    return data;\
    _error:\
        QGM_Error = QGM_NOELEMENTQUEUE;\
        memset(&ifError, 255, sizeof ifError);\
        return ifError;\
}\
\
size_t QGM_Queue_##TYPE##_Size(QGM_Queue_##TYPE *self){\
    return self->nElem;\
}\
\
void QGM_Queue_##TYPE##_Quit(QGM_Queue_##TYPE *self){\
    free(self->data);\
    free(self);\
}
 
#endif

Qnope nous fournit plusieurs exemples pour tester son code. D’abord un vector.

Test vector
#include "QGM/alloc.h"
#include "QGM/error.h"
#include "QGM/data.h"
 
/** Active le vector int**/
QGM_Vector_Header(int);
QGM_Vector_Functions(int);
 
/** Active le vector double **/
QGM_Vector_Header(double);
QGM_Vector_Functions(double);
 
int main(void){
    QGM_Vector_int *vectorInt;
    QGM_Vector_double *vectorDouble;
    int i;
 
    if((vectorDouble = QGM_Vector_double_Init()) == NULL)
        return 1;
 
    if((vectorInt = QGM_Vector_int_Init()) == NULL){
        QGM_Vector_double_Quit(vectorDouble);
        return 1;
    }
 
    for(i = 0; i < 20; ++i)
        if(QGM_Vector_double_PushFront(vectorDouble, (double)i / 10.0) == 0)
            goto end;
 
    for(i = 0; i < 20; ++i)
        if(QGM_Vector_int_PushFront(vectorInt, i) == 0)
            goto end;
 
    /** Affichage du vector double sans rien supprimer **/
    printf("Vector Double\n");
 
    for(i = 0; i < 20; ++i)
        printf("%.1f ", vectorDouble->data[i]);
    printf("Il y a %u elements dans le vectorDouble\n",
           QGM_Vector_double_Size(vectorDouble));
 
    /** Affichage du vector int en supprimant toutes les données **/
    printf("\nVector int\n");
 
    for(i = 0; i < 20; ++i)
        printf("%d ", QGM_Vector_int_PopBack(vectorInt));
    printf("\nIl y a %u element dans le vectorInt\n",
           QGM_Vector_int_Size(vectorInt));
 
    end:
    QGM_Vector_int_Quit(vectorInt);
    QGM_Vector_double_Quit(vectorDouble);
 
    return 0;
}

Puis une pile.

Test pile
#include "QGM/alloc.h"
#include "QGM/error.h"
#include "QGM/data.h"
 
/** Active la pile double **/
QGM_Stack_Header(double);
QGM_Stack_Functions(double);
 
int main(void){
    QGM_Stack_double *stack = QGM_Stack_double_Init();
    int i;
 
    for(i = 0; i < 20; ++i)
        if(QGM_Stack_double_Push(stack, (double)i / 10.0) == 0)
            goto end;
 
    for(i = 0; i < 21; ++i)
        printf("%.1f ", QGM_Stack_double_Pop(stack));
 
    printf("\n%s", QGM_GetError());
 
    end:
    QGM_Stack_double_Quit(stack);
 
    return 0;
}

Et enfin une file.

Test file
#include "QGM/alloc.h"
#include "QGM/error.h"
#include "QGM/data.h"
 
/** Active la file long **/
QGM_Queue_Header(long);
QGM_Queue_Functions(long);
 
int main(void){
    QGM_Queue_long *queue = QGM_Queue_long_Init();
    int i;
 
    for(i = 0; i < 20; ++i)
        if(QGM_Queue_long_Push(queue, i) == 0)
            goto end;
 
    for(i = 0; i < 21; ++i)
        printf("%d ", QGM_Queue_long_Pop(queue));
 
    printf("\n%s", QGM_GetError());
 
    end:
    QGM_Queue_long_Quit(queue);
 
    return 0;
}

Gestion des exceptions

Je vois déjà ceux qui sautent de leur siège en hurlant à l’hérésie, qu’en C il n’y a pas d’exception. Oui, mais c’était sans compter sur une bande de fans de C99 et de longjumps. Et aussi de détournement de macros. Le résultat n’est évidemment pas aussi souple qu’un langage implémentant naturellement les exceptions tel C++, mais c’est quand même impressionnant et c’est ici que ça se passe.

@Taurre

exception.h
#ifndef EXCEPTION_H
#define EXCEPTION_H
 
#include <setjmp.h>
 
#define TRY for (es->next = &(struct exception) { .next = es->next } \
, es->num = 1; es->num && !setjmp(es->next->buf) \
; es->next = es->next->next, es->num = 0)
#define CATCH(NUM) for (; es->num == (NUM); es->num = 0)
#define FINALLY for (; es->num; es->num = 0)
#define THROW(NUM) for (jmp_buf *buf = &es->next->buf \
; (es->next = es->next->next, es->num = (NUM)); longjmp(*buf, es->num))
 
extern struct exception {
    jmp_buf buf;
    int num;
    struct exception *next;
} *es;
 
#endif /* EXCEPTION_H */
#include <stddef.h>
 
#include "exception.h"
 
struct exception *es = &(struct exception) { .next = NULL };
#include <stdio.h>
#include <stdlib.h>
 
#include "exception.h"
 
enum {
    TEST1 = 1, TEST2 = 2, TEST3 = 3
};
 
 
static void
test3(void)
{
    THROW(TEST3);
}
 
 
static void
test2(void)
{
    THROW(TEST2);
}
 
 
static void
test1(void)
{
    TRY {
        test2();
    } CATCH (TEST2) {
        puts("TEST2");
        THROW(TEST1);
    }
}
 
 
int
main(void)
{
    TRY {
        test1();
    } CATCH (TEST1) {
        puts("TEST1");
    }
 
    TRY {
        ;
    }
 
    TRY {
        test3();
    } FINALLY {
        puts("Exception!");
    }
 
    return EXIT_SUCCESS;
}

@Maëlan

Caractéristiques communes :

  • Syntaxe confortable (enfin, question de goût) car très proche du C++ : les mots-clés try, catch() (ces derniers commençant un bloc délimité par des accolades) et throw(), qui s’utilisent de la même manière qu’en C++. Les seules différences sont les parenthèses du throw() et l’inclusion du header trycatch.h (et liaison à la bibliothèque). Au fait, le paramètre de catch n’est pas forcément une déclaration, ça peut être le nom d’une variable déclarée précédemment (portée plus large).
  • On ne peut lancer et attraper que des int (ou équivalents) non nuls.
  • J’ai veillé à ne pas réserver de noms autres que « try », « catch » et « throw », pour ne pas perturber l’utilisation.
Maëlan

Avec une pile statique

Limitations :

  • Les imbrications sont limitées (jusqu’à CATCH_STACKSZ fois).
  • La pile statique prend une place en mémoire assez importante.
  • Il doit impérativement y avoir un et un seul bloc catch pour chaque bloc try (sinon, plantage vu que l’on empile dans try et dépile dans throw). Ce n’est toutefois pas contraignant vu que ça correspond à l’utilisation normale, mais il faut le garder à l’esprit.

Je précise que ce code n’est pas testé !

Maëlan
Avec une pile statique
#define  CATCH_STACKSZ  16
 
 
/* pile globale : st est la pile, p est un pointeur sur l’élément actuel */
extern struct {
    struct {
        int      code;
        jmp_buf  env;
    } *p, st[CATCH_STACKSZ];
} catch;
/* catch.p est initialisé au 1er élément de catch.st - 1 (indique qu’on
   n’est dans aucun bloc try). */
 
 
 
#define  CATCH_ERROR(msg)  \
    fputs(  \
      "error: file `" #__FILE__ "`, line " #__LINE__ ", function `"  \
        #__func__ "`: "  msg  ".\n" ,  \
      stderr)
 
 
/* version non sécurisée */
#define  _try        \
    if(  ++catch.p ,  !( catch.p->code = setjmp(catch.p->env) )  )
       /* entrée dans un bloc try → on empile */
 
/* version sécurisée (attention les yeux) */
#define  try           \
    if(catch.p == catch.st + CATCH_STACKSZ - 1)  /* On est déjà au sommet de la pile. */  \
        CATCH_ERROR("too much nested `try` blocks (a maximum of "
          #CATCH_STACKSZ " is supported), skipping this one");  \
    else _try
 
 
/* version non sécurisée */
#define  _catch(ID)    \
    for(ID = (catch.p--)->code  ;  (catch.p+1)->code;  (catch.p+1)->code = 0)
       /* sortie d’un bloc try → on dépile */
 
/* version sécurisée */
#define  catch(ID)     \
    if(catch.p == catch.st + CATCH_STACKSZ - 1)  /* On est déjà au sommet de la pile. */  \
        CATCH_ERROR("skipping corresponding `catch` block");  \
    else _catch(ID)
 
 
/*  version non sécurisée */
#define  _throw(CODE)  \
    longjmp(catch.p->env, (CODE))
     
/*  version sécurisée */
#define  throw(CODE)   \
    if(catch.p == catch.st - 1) {  /* On n’est dans aucun bloc try. */  \
        CATCH_ERROR("throw not in a try block");  \
        exit(CODE);  /* Pourquoi pas ? */  \
    } else _throw(CODE);

Avec une pile automatique

Avantages :

  • Comportement « parfait » (pas de limite d’imbrication).
  • Pile automatique : utilisation plus efficace de la mémoire (et plus rapide qu’avec de l’allocation dynamique).
  • Code plus peaufiné et complet (j’ai fait un effort dessus, j’aurais pu le faire aussi pour le code précédent), dont des macros « bonus » : catchSwitch() (raccourci pour faire un switch sur l’exception capturée) et rethrow() (pour relancer une exception capturée, l’équivalent de throw sans rien en C++).
Maëlan
Avec une pile automatique
#ifndef INCLUDED_TRYCATCH_H
#define INCLUDED_TRYCATCH_H
 
/* Ce header nécessite C99. */
#if !defined (__STDC_VERSION__)  ||  __STDC_VERSION__ < 199901L
#error : USE OF “TRYCATCH.H” REQUIRES C99.
#endif
 
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
 
 
 
/* type : pile d’environnements `jmp_buf` (utilisée dans la macro `try`) */
struct try {
    struct try  *prev;
    jmp_buf     env;
};
 
/* type : informations de gestion de la pile de jmp_buf et des exceptions */
struct catch {
    struct try  *envs;    /* haut de la pile (NULL si pas dans un bloc `try`) */
    int         code;     /* code numérique de l’exception (0 si sans objet) */
};
 
/* globale de gestion du système d’exceptions */
extern  struct catch  catch;
 
 
 
/* gestion des erreurs (message d’erreur détaillé) */
#define  Throw(msg)                                                         \
    fprintf(stderr, "error [file `%s`, line %u, function `%s`]: %s.\n",        \
      __FILE__, __LINE__, __func__, (msg))
 
 
 
/** TRY **/
 
/* Crée un élément de la pile de classe automatique (non nommé et local au bloc
   `try`), le rajoute au sommet de la pile globale, puis appelle `setjmp` pour
   définir le point de retour de `throw`.
   Dépile à la fin. On doit dépiler dans le `for` car l’élément de pile lui est
   local. Dans tous les cas, la boucle `for` ne doit pas se répéter.
   Deux cas de figure :
   − sortie normale du bloc `try` : cf la 3è partie du `for` pour dépiler, et la
     1ère condition pour ne pas recommencer le bloc (utilise le comportement en
     cours-circuit de &&) ;
   − sortie avec un `throw` : cf le ternaire de la 2ème condition, pour dépiler
     et arrêter le `for`. */
 
#define  try                                                                   \
    for( catch.code = 1,  catch.envs = & (struct try) { .prev = catch.envs }   \
       ; catch.code  &&  ( setjmp(catch.envs->env)                             \
                           ? (catch.envs = catch.envs->prev, 0)                \
                           : 1                                  )              \
       ; catch.code = 0,  catch.envs = catch.envs->prev                        \
    )
 
 
 
/** CATCH (+ catchSwitch) **/
 
/* Exécute le contenu du bloc s’il y a une exception (code non nul), en stockant
   son code dans la variable de type `int` passée (qui peut être une déclaration,
   auquel cas cette variable sera locale au bloc `catch`).
   Un bloc `catch` n’aura d’effet que s’il est le premier après un bloc `try`
   (remet le code d’exception à 0). */
 
#define  catch(VAR)                                                            \
    for(VAR = catch.code  ;  catch.code  ;  catch.code = 0)
 
/* Raccourci pour effectuer un `switch` sur le code de l’exception, sans nommer
   une variable pour le stocker (s’utilise comme un `switch` normal). */
 
#define  catchSwitch()                                                         \
    for(;  catch.code  ;  catch.code = 0)                                      \
        switch(catch.code)
 
 
 
/** THROW (+ rethrow) **/
 
/* « Lance » le code d’exception passé, qui doit être non nul. L’exécution du
   programme revient grâce à `longjmp` au dernier bloc `try` englobant. Si aucun
   bloc `try` n’englobe `throw`, quitte le programme avec le code lancé. */
 
#define  throw(CODE)                                                           \
    do {                                                                       \
        catch.code = (CODE);                                                   \
        if(!catch.code) {                                                      \
            Throw("throwing a null exception code");                           \
            exit(EXIT_FAILURE);                                                \
        }                                                                      \
        else if(!catch.envs) {                                                 \
            Throw("`throw` outside of a `try` block");                         \
            exit(catch.code);                                                  \
        }                                                                      \
        else                                                                   \
            longjmp(catch.envs->env, catch.code);                              \
    } while(0)
 
/* « Relance » l’exception qui a été capturée (possible seulement dans un bloc
   `catch`). */
 
#define  rethrow()                                                             \
    throw(catch.code)
 
 
 
#endif
#include "trycatch.h"
 
struct catch  catch =  {NULL, 0};
Tests
#include "trycatch.h"
#include <stdio.h>
 
 
 
typedef  enum {EXC_Z, EXC_NOTRY, EXC_42, EXC_DIVBYZ, EXC_ORDER}  Exception;
 
int division(int a, int b) {
    if(!a)
        throw(EXC_Z);        /* doit provoquer une erreur (code nul) */
    else if(a==-1)
        throw(EXC_NOTRY);    /* doit provoquer une erreur (throw sans try) */
    else if(b==42)
        throw(EXC_42);       /* exception relancée avec rethrow() */
    else if(!b)
        throw(EXC_DIVBYZ);     /* exception gérée dans le catchSwitch() */
    else if(a<b)
        throw(EXC_ORDER);      /* idem */
    return a / b;
}
 
int main(void) {
    int a,  b;
 
    while(1) {
        fputs("a, b? ", stdout);
        scanf("%i%i", &a, &b);
 
        try {
         
            try {
                printf("%i / %i  =  %i\n", a, b, division(a,b));
            }
            catchSwitch() {
              case EXC_DIVBYZ:
                puts("exc: /0");     break;
              case EXC_ORDER:
                puts("exc: a<b");    break;
              default:    /* EXC_42 ou EXC_NOTRY */
                rethrow();          break;
            }
             
        }
         
        catch(Exception e) {
            if(e == EXC_42)
                puts("exc: 42");
            else    /* EXC_NOTRY */
                rethrow();    /* erreur : pas dans un bloc `try` */
        }
    }
}

Implémenter la libC

Le langage C a une bibliothèque standard assez légère et peu fournie. Il arrive qu’on propose d’en recoder des fonctions en guise d’exercices, la plus connue étant sans doute printf et sa famille. Mais un membre de l’ex-SdZ s’est dit que ce serait plus marrant de TOUT recoder. Le projet n’a jamais abouti, mais il y a quelques jolis codes qui traînent toujours là-bas. On trouve du printf, de la gestion d’erreurs, des manipulations de chaînes de caractères, du tri, bref, plein de trucs cools.

@informaticienzero

Les horribles codes ci-dessous sont par votre humble serviteur. :)

my_string.h
#ifndef MY_STRING_H
#define MY_STRING_H
 
char * my_strcpy(char * copie, const char * origine);
char * my_strncpy(char * dst, const char * src, int n);
size_t my_strlen(const char * str);
const char * my_strchr(const char * str, int car);
char * my_strcat(char * dest, const char * orig);
int my_strcmp(const char * str1, const char *str2);
int my_strncmp(const char * str1, const char *str2, int n);
char * my_strstr (const char * str, const char * unstr);
char * my_strpbrk(const char * str, const char * c);
 
#endif
my_string.c
#include <stddef.h>
#include "my_string.h"
 
char * my_strcpy(char * dst, const char * src)
{
    size_t i;
 
    for (i = 0; (dst[i] = src[i]); i++);
    return dst;
}
 
char * my_strncpy(char * dst, const char * src, int n)
{
    size_t i;
 
    for (i = 0; (dst[i] = src[i]) && i < n; i++);
 
    dst[i] = '\0';
 
    return dst;
}
 
size_t my_strlen(const char * str)
{
    size_t lg = 0;
 
    while(str[lg])
        lg++;
 
    return lg;
}
 
const char * my_strchr(const char * str, int car)
{
    while(*str)
    {
        if (*str == car || *str == '\0')
            return str;
 
        else
            str++;
    }
 
    return NULL;
}
 
char * my_strcat(char * dest, const char * orig)
{
    my_strcpy(dest + my_strlen(dest), orig);
 
    return dest;
}
 
char * my_strncat(char * dest, const char * orig, int n)
{
    my_strncpy(dest + my_strlen(dest), orig, n);
 
    return dest;
}
 
int my_strcmp(const char * str1, const char *str2)
{
    while(*str1 == *str2)
    {
        if (*str1 == 0)
            return 0;
 
        str1++;
        str2++;
    }
 
    return *str1 < *str2 ? -1 : 1;
}
 
int my_strncmp(const char * str1, const char *str2, int n)
{
    while(n--)
    {
        if (*str1 == 0 || *str1 != *str2)
            return *str1 < *str2 ? -1 : 1;
 
        str1++;
        str2++;
    }
 
    return 0;
}
 
char * my_strstr (const char * str, const char * unstr)
{
    while(*str)
    {
        if (my_strncmp(str, unstr, my_strlen(unstr)) == 0)
            return (char*)str;
 
        str++;
    }
 
    return NULL;
}
 
char * my_strpbrk(const char * str, const char * c)
{
    size_t i, len = my_strlen(c);
 
    while(*str)
    {
        for (i = 0; i < len; i++)
        {
            if(c[i] == *str)
                return (char*)str;
        }
 
        str++;
    }
 
    return NULL;
}

Par @Taurre

strtol
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
 
long
strtol(char const * restrict s, char * restrict * p, int base)
{
    static long conv[] = {
        ['0'] = 0,
        ['1'] = 1,
        ['2'] = 2,
        ['3'] = 3,
        ['4'] = 4,
        ['5'] = 5,
        ['6'] = 6,
        ['7'] = 7,
        ['8'] = 8,
        ['9'] = 9,
        ['a'] = 10,
        ['b'] = 11,
        ['c'] = 12,
        ['d'] = 13,
        ['e'] = 14,
        ['f'] = 15,
        ['A'] = 10,
        ['B'] = 11,
        ['C'] = 12,
        ['D'] = 13,
        ['E'] = 14,
        ['F'] = 15,
    };
    long n = 0;
    int neg = (*s == '-') ? (++s, 1) : 0;
 
    if (base > 16)
        goto err;
 
    for (; *s != '\0'; ++s) {
        if (!isxdigit(*s))
            goto err;
 
        if (LONG_MAX / base < n) {
            n = (neg) ? LONG_MIN : LONG_MAX;
            errno = ERANGE;
            goto err;
        } else
            n *= base;
 
        if (LONG_MAX - conv[*s] < n) {
            n = (neg) ? LONG_MIN : LONG_MAX;
            errno = ERANGE;
            goto err;
        } else
            n += conv[*s];
    }
 
    goto ret;
err:
    if (p != NULL)
        *p = s;
ret:
    return (neg) ? -n : n;
}

Par Holt

Et maintenant, une petite implémentation de printf.

#include "printf.h"
 
#include <stdarg.h>
#include <stdio.h> /* Only for putchar */
#include <wchar.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <float.h>
#include <math.h>
 
#if __STDC_VERSION__ >= 199901L
#include <stdint.h>
#define VP_C99__
 
typedef intmax_t vp_intmax_t ;
typedef uintmax_t vp_uintmax_t ;
#else
typedef long int vp_intmax_t ;
typedef unsigned long int vp_uintmax_t ;
#endif
 
enum flag_t {
    FLAG_LJUST = 1 << 0,
    FLAG_PSIGN = 1 << 1,
    FLAG_SPACE = 1 << 2,
    FLAG_SHARP = 1 << 3,
    FLAG_ZEROS = 1 << 4
};
 
#define HAS_LEFT_JUSTIFIED__(c) ((c) & FLAG_LJUST)
#define HAS_ZERO_PADDED__(c)    ((c) & FLAG_ZEROS)
#define HAS_PRINT_SIGN__(c)     ((c) & FLAG_PSIGN)
#define HAS_BLANK_IF_POS__(c)   ((c) & FLAG_SPACE)
#define HAS_SHARP_FLAG__(c)     ((c) & FLAG_SHARP)
 
 
/* Check if the specified character is a "precision" character. i.e, it gives information on paramater size (long, short, long long). */
static int is_spe_ (char c) {
    return c == 'l'
    || c == 'h' ;
}
 
/* Check if the specified character is a "format" character. i.e, it gives information about the type of parameter and the conversion to do. */
static int is_format_ (char c) {
    return c == 's'
    || c == 'i'
    || c == 'x'
    || c == 'u'
    || c == 'd'
    || c == 'c'
    || c == 'u'
    || c == 'x'
    || c == 'X'
    || c == 'o'
    || c == 'p'
    || c == 'f'
    || c == 'e'
    || c == 'E'
    || c == 'g'
    || c == 'G'
    || c == 'n';
}
 
static enum flag_t get_flags_ (const char **format) {
    enum flag_t flags = 0 ;
    for (; ; (*format)++) {
    switch (**format) {
    case '-': flags |= FLAG_LJUST ; break ;
    case '0': flags |= FLAG_ZEROS ; break ;
    case '+': flags |= FLAG_PSIGN ; break ;
    case ' ': flags |= FLAG_SPACE ; break ;
    case '#': flags |= FLAG_SHARP ; break ;
    default:
        goto end ;
    }
    }
end:
    return flags ;
}
 
/* Get information from format, and store it into the specified arguments :
   flags is set to a combination of allowed FLAG (see above), if there is flag in format
   min_width is set to the minimum width the representation may be, if there is one specified
   max_width is set to the maximum width the representation may be, if there is one specified
   args is used in case of the minimum width must be taken from a parameter
   All parameters, except format and args, may be NULL. In this cas, they are not set. */
static void get_format_parts_ (const char *format,
                   enum flag_t *flags,
                   int *min_width,
                   int *max_width,
                   va_list *args) {
    int tmp ;
    char *aux ;
    enum flag_t f ;
    /* Set flags value, if it's not NULL. */
    f = get_flags_ (&format) ;
    if (flags != NULL) *flags = f;
    /* Set min_width value. If format contains '*', min_width is taken from arg_list, otherwize it's taken from format. */
    tmp = -1 ;
    if (*format != '*') {
    tmp  = strtol(format, &aux, 10) ;
    if (aux == format) {
        tmp = -1 ;
    }
    format = aux ;
    }
    else {
        tmp = va_arg(*args, unsigned int) ;
    }
    if (min_width != NULL) *min_width = tmp ;
    /* Set max_width, if it's 0, just set it to UINT_MAX. */
    tmp = -1 ;
    if (*format == '.') {
    ++format ;
    if (*format != '*') {
        tmp = strtol(format, &aux, 10) ;
    }
    else {
        tmp = va_arg(*args, unsigned int) ;
    }
    }
    if (max_width != NULL) *max_width = tmp ;
}
 
/* Get an vp_intmax_t from va_list after converting it according to spe.
   spe must be h (short), l (long int), L (vp_intmax_t) or any other character (int)
   args must not be NULL */
static vp_intmax_t get_lli_ (char spe, va_list *args) {
    switch (spe) {
    case 'h':
    return (vp_intmax_t)va_arg(*args, int) ;
    break ;
    case 'l':
    return (vp_intmax_t)va_arg(*args, long int) ;
    break ;
#ifdef VP_C99__
    case 'L':
    return (vp_intmax_t)va_arg(*args, vp_intmax_t) ;
    break ;
#endif
    default:
    return (vp_intmax_t) va_arg(*args, int) ;
    }
}
 
/* Get an vp_uintmax_t from va_list after converting it according to spe.
   spe must be h (unsigned short), l (unsigned long int), L (vp_uintmax_t) or any other character (unsigned int)
   args must not be NULL */
static vp_uintmax_t get_ulli_ (char spe, va_list *args) {
    switch (spe) {
    case 'h':
    return (vp_uintmax_t)va_arg(*args, unsigned int) ;
    break ;
    case 'l':
    return (vp_uintmax_t)va_arg(*args, unsigned long int) ;
    break ;
#ifdef VP_C99__
    case 'L':
    return (vp_uintmax_t)va_arg(*args, vp_uintmax_t) ;
    break ;
#endif
    default:
    return (vp_uintmax_t) va_arg(*args, unsigned int) ;
    }
}
 
/* Return the number of character needed to print var in base base. */
static int ullinchar_ (vp_uintmax_t var, int base) {
    int count = 0 ;
    if (var == 0) {
    return 1 ;
    }
    for (; var != 0; var /= base) {
    count ++ ;
    }
    return count ;
}
 
/* Convert the vp_uintmax_t val into a string, stored in the array pointed by buff. The conversion is made
   according to base and upper. If nchar is not NULL, it contains the number of character used to represend val.
   The char array is NOT nul terminated. */
static int ulli2str_ (vp_uintmax_t val, 
              char *buff, 
              int base,
              int upper,
              int *nchar)
{
    static const char *hexxdigits = "0123456789abcdefghijklmnopqrstuvwxyz" ;
    int i ;
    int ntchar ;
    base = (base == 0) ? 10 : base ;
    ntchar = ullinchar_ (val, base) ;
    for (i=ntchar-1; i >= 0; i--, val /= base) {
    buff[i] = hexxdigits[val%base] ;
    if (upper) {
        buff[i] = toupper(buff[i]) ;
    }
    }
    if (nchar != NULL) {
    *nchar = ntchar ;
    }
    return ntchar ;
}
 
/* Print everything needed before a number according to specification :
   neg if the number is negative
   padd the minimum_width
   sign if the sign must be printed, even with positive value
   blank if a blank character must be printed before positive value
   left_justified
   nchar the number of char used to represent the number */
static int vp_before_number_ (int neg,
                  int padd,
                  int sign,
                  int blank,
                  int zero_padded,
                  int left_justified,
                  int nchar)
{
    int count = 0 ;
    char padchar = (zero_padded) ? '0' : ' ' ;
    if (left_justified || zero_padded) {
    if (neg) {
        putchar('-') ;
        ++ count ;
    }
    else if (sign) {
        putchar('+') ;
        ++ count ;
    }
    else if (blank && !zero_padded) {
        putchar(' ') ;
        ++ count ;
    }
    }
    if (!left_justified) {
    count += ((sign || blank) && !zero_padded) ? 1 : 0 ;
    for (; count + nchar < padd; ++count) {
        putchar(padchar) ;
    }
    if (sign && !zero_padded) {
        putchar('+') ;
    }
    else if (blank && !zero_padded) {
        putchar(' ') ;
    }
    }
    return count ;
}
 
static int vp_after_number_ (int padd,
                 int nchar_printed,
                 int left_justified) {
    int count = 0  ;
    if (left_justified) {
    for (; count + nchar_printed < padd; ++count) {
        putchar(' ') ;
    }
    }
    return count ;
}
 
#define BUFF_INT_REPR_SIZE__ 200
 
/* Print a formated string corresponding to val according to the specified parameters :
   padd        : the representation of val will be at least of padd character
   sign        : if not 0, a '+' character will be put before positive value
   blank       : if not 0 and sign not 1, a blank character will be put before positive value
   zero_padded : if not 0, '0' character will be used instead of blank character to do padding
   left_justified
   base        : the base conversion to for the conversion
   upper       : if specified and base >= 10, the letters will be output upper instead of lower */
static int vp_format_ulli_ (vp_uintmax_t val,
                int padd,
                int sign,
                int blank,
                int zero_padded,
                int left_justified,
                int base,
                int upper)
{
    char buff[BUFF_INT_REPR_SIZE__] ;
    int ncharprint, count = 0;
    int i ;
    ulli2str_ (val, buff, base, upper, &ncharprint) ;
    count += vp_before_number_ (0, padd, sign, blank, zero_padded, left_justified, ncharprint) ;
    for (i=0; i<ncharprint; ++i, ++count) {
    putchar(buff[i]) ;
    }
    count += vp_after_number_ (padd, count, left_justified) ;
    return count ;
}
 
/* Print a formated string corresponding to val according to the specified parameters (base conversion is 10) :
   padd        : the representation of val will be at least of padd character
   sign        : if not 0, a '+' character will be put before positive value
   blank       : if not 0 and sign not 1, a blank character will be put before positive value
   zero_padded : if not 0, '0' character will be used instead of blank character to do padding
   left_justified  */
static int vp_format_lli_ (vp_intmax_t val,
               int padd,
               int sign,
               int blank,
               int zero_padded,
               int left_justified)
{
    int count = 0;
    vp_uintmax_t abs = (val < 0) ? -val : val ;
    if (val < 0 && (left_justified || zero_padded)) {
    putchar('-') ;
    count ++ ;
    padd -- ;
    sign = 0 ;
    blank = 0 ;
    }
    count += vp_format_ulli_ (abs, padd, sign, blank, zero_padded, left_justified, 10, 0) ;
    return count ;
}
 
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as a signed integer. */
static int vp_li_ (const char *format, va_list *args, char spe) {
    vp_intmax_t val ;
    int padd ;
    enum flag_t flags ;
    get_format_parts_ (format, &flags, &padd, NULL, args) ;
    padd = (padd < 0) ? 0 : padd ;
    val = get_lli_ (spe, args) ;
    return vp_format_lli_ (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags)) ;
}
 
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as un unsigned integer. */
static int vp_ulli_ (const char *format, va_list *args, char spe) {
    vp_uintmax_t val ;
    int padd ;
    enum flag_t flags ;
    get_format_parts_ (format, &flags, &padd, NULL, args) ;
    padd = (padd < 0) ? 0 : padd ;
    val = get_ulli_ (spe, args) ;
    return vp_format_ulli_ (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), 10, 0) ;
}
 
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as un unsigned integer.
   Upper paramater just specified if letter must be set upper (1) or lower (0). */
static int vp_ullx_ (const char *format, va_list *args, char spe, char upper) {
    vp_uintmax_t val ;
    enum flag_t flags ;
    int padd;
    get_format_parts_ (format, &flags, &padd, NULL, args) ;
    padd = (padd < 0) ? 0 : padd ;
    val = get_ulli_ (spe, args) ;
    if (HAS_SHARP_FLAG__ (flags) && val != 0) {
    padd -= 2 ;
    putchar('0') ;
    putchar((upper) ? 'X' : 'x') ;
    }
    return vp_format_ulli_ (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), 16, upper) ;
}
 
#define CHAR_FOR_PTR__ 8
 
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification and converted as un unsigned int.
   Only '-' flags is allowed in format, and the only value used from format is min_width (see above). The conversion is done with no zero padded, with at least a
   minimum width of CHAR_FOR_PTR, and in uppercase letter. */
static int vp_ptr_ (const char *format, va_list *args, char spe) {
    vp_uintmax_t val = (unsigned int)va_arg(*args, void*) ;
    int padd ;
    enum flag_t flags ;
    int count = 0 ;
    get_format_parts_ (format, &flags, &padd, NULL, args) ;
    padd = (padd < 0) ? 0 : padd ;
    if (HAS_LEFT_JUSTIFIED__(flags)) {
    count = vp_format_ulli_ (val, CHAR_FOR_PTR__, 0, 0, 1, 0, 16, 1) ;
    for (; count < padd; ++count) {
        putchar(' ') ;
    }
    }
    else {
    count = CHAR_FOR_PTR__ ;
    for (; count < padd; ++count) {
        putchar(' ') ;
    }
    vp_format_ulli_ (val, CHAR_FOR_PTR__, 0, 0, 1, 0, 16, 1) ;
    }
    return count ;
}
 
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as un unsigned integer. */
static int vp_ullo_ (const char *format, va_list *args, char spe) {
    vp_uintmax_t val ;
    enum flag_t flags;
    int padd;
    get_format_parts_ (format, &flags, &padd, NULL, args) ;
    padd = (padd < 0) ? 0 : padd ;
    if (HAS_SHARP_FLAG__ (flags)) {
    putchar('0') ;
    padd -- ;
    }
    val = get_ulli_ (spe, args) ;
    return vp_format_ulli_  (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), 8, 0);
}
 
/* Print the wide string pointed by str according to specified parameters :
   max_width specify the maximum number of character from str that must be printed, this value must be lower than str length
   padd specify the minimum amount of character that must be printed, if it is more than str length or max_width, blank character are added
   left_justified specify if string must be left justified */
static int vp_format_wstr_ (const wchar_t *str,
                int max_width,
                int padd,
                int left_justified) {
    int count = 0 ;
    if (left_justified) {
    for (; count < max_width && *str != '\0'; ++count, ++str) {
        putwchar(*str) ;
    }
    }
    else {
    count = wcslen(str) ;
    count = (count > max_width) ? max_width : count ;
    }
    for (; count < padd ; ++count) {
    putchar(' ') ;
    }
    if (!left_justified) {
    for (; max_width > 0 && *str != '\0'; ++str, --max_width) {
        putwchar(*str) ;
    }
    }
    return count ;
}
 
/* Print the string pointed by str according to specified parameters :
   max_width specify the maximum number of character from str that must be printed, this value must be lower than str length
   padd specify the minimum amount of character that must be printed, if it is more than str length or max_width, blank character are added
   left_justified specify if string must be left justified */
static int vp_format_str_ (const char *str,
               int max_width,
               int padd,
               int left_justified) {
    int count = 0 ;
    if (left_justified) {
    for (; count < max_width && *str != '\0'; ++count, ++str) {
        putchar(*str) ;
    }
    }
    else {
    count = strlen(str) ;
    count = (count > max_width) ? max_width : count ;
    }
    for (; count < padd ; ++count) {
    putchar(' ') ;
    }
    if (!left_justified) {
    for (; max_width > 0 && *str != '\0'; ++str, --max_width) {
        putchar(*str) ;
    }
    }
    return count ;
}
 
/* Print the string (or wide string, specified by spe) contains in args, according to format. Only '-' flags is allowed. */
static int vp_str_ (const char *format, va_list *args, char spe) {
    char *str ;
    wchar_t *wstr ;
    int max_width, min_width ;
    enum flag_t flags ;
    get_format_parts_ (format, &flags, &min_width, &max_width, args) ;
    min_width = (min_width < 0) ? 0 : min_width ;
    max_width = (max_width < 0) ? UINT_MAX : max_width ;
    if (spe == 'l') {
    wstr = va_arg(*args, wchar_t*) ;
    return vp_format_wstr_ (wstr, max_width, min_width, HAS_LEFT_JUSTIFIED__(flags)) ;
    }
    else {
    str = va_arg(*args, char *) ;
    return vp_format_str_ (str, max_width, min_width, HAS_LEFT_JUSTIFIED__(flags)) ;
    }
    return 0 ;
}
 
/* Simple output for string with no format ! */
static int vp_simple_str_ (const char *str) {
    int count = 0 ;
    for (; *str != '\0'; ++count, ++str) {
    putchar(*str) ;
    }
    return count ;
}
 
/* Print the char (or whar_t, specified by spe) contains in args. */
static int vp_char_ (const char *format, va_list *args, char spe) {
    if (spe == 'l') {
    putwchar(va_arg(*args, int)) ;
    return sizeof(wchar_t) ;
    }
    else {
    putchar(va_arg(*args, int)) ;
    return 1 ;
    }
}
 
/* Get the number of character needed to represent the integer part of a double. */
static int dbl_intp_nchar_ (double val) {
    int count = 0 ;
    if (val < 1) {
    return 1 ;
    }
    for (val = floor(val); val > DBL_MIN; val = floor(val/10)) {
    count ++ ;
    }
    return count;
}
 
#ifndef VP_C99__
double round (double x) {
    double r = floor(x) + 0.5;
    return (r < x) ? ceil(x) : r ;
}
#endif
 
/* Convert the double val into a string, stored in the array pointed by buff. val must be positive The conversion is made
   according to base and upper. If nchar is not NULL, it contains the number of character used to represend val.
   The char array is NOT nul terminated. */
static int dbl2strf_ (double val, 
              char *buff,
              int always_point,
              int precision,
              int *nchar)
{
    static const char *digits = "0123456789" ;
    int i, count = 0 ;
    int nchar_intp = dbl_intp_nchar_ (val) ;
    int nchar_total ;
    for (i=0; i<precision; ++i) {
    val *= 10.0 ;
    }
    val = round(val) ;
    /* nchar = number of char for integer part (from dbl_intp_nchar_) + one for point + precision. */
    nchar_total = nchar_intp + precision + ((always_point || precision > 0) ? 1 : 0);
    for (i = nchar_total - 1; i > nchar_intp; --i, val = floor(val/10), ++count) {
    if (i > DBL_DIG) {
        buff[i] = '0' ;
    }
    else {
        buff[i] = digits[(int)fmod(val, 10)] ;
    }
    }
    if (always_point || precision > 0) {
    buff[i--] = '.' ;
    ++ count ;
    }
    for (; i >= 0; --i, val = floor(val/10), ++count) {
    if (i > DBL_DIG) {
        buff[i] = '0' ;
    }
    else {
        buff[i] = digits[(int)fmod(val, 10)] ;
    }
    }
    if (nchar != NULL) {
    *nchar = count ;
    }
    return count ;
}
 
#define BUFF_DOUBLE_REPR_SIZE__ BUFF_INT_REPR_SIZE__
 
static int vp_double_f_ (double val,
             char flags,
             int min_width,
             int precision) {
    char buff[BUFF_DOUBLE_REPR_SIZE__] ;
    int neg=0, nbchar_print, i ;
    int count = 0 ;
    if (val < 0) {
    val = -val ;
    neg = 1 ;
    }
    nbchar_print = dbl2strf_ (val, buff, HAS_SHARP_FLAG__ (flags), precision, NULL) ;
    count += vp_before_number_ (neg, min_width, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags),
                HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), nbchar_print) ;
    for (i=0; i<nbchar_print; ++i, ++count) {
    putchar(buff[i]) ;
    }
    count += vp_after_number_ (min_width, count, HAS_LEFT_JUSTIFIED__ (flags)) ;
    return count ;
}
 
#define DBL_MIN_EXP_DIG__ 3
 
/* Transforme a double representation as f format (2005465.0) in an exponent representation WITHOUT exponent (2.005465),
   the exponent value is stored in exp args and the number of char necessary is returned.
   val is the value represented in buff
   nbchar is the number of character used to represent val in buff
   precision is the precision required (i.e the number of character after point)
   always_point must  be true if a point should always be printed (event for 2.e+001 for example)
   start_buff will point to the beginning of the buffer, it is possible that this function modified it */
static int dbl2stre_ (double val,
              char *buff,
              int always_point,
              int precision,
              int *nchar,
              int *exp) {
    *exp = 0 ;
    for (; val > 10.0; val /= 10.0, ++(*exp)) { }
    for (; val < 1.0; val *= 10.0, --(*exp)) { }
    return dbl2strf_ (val, buff, always_point, precision, nchar) ;
}
 
#define DBL_PRECI_MAX__ DBL_DIG
 
static int vp_double_e_ (double val,
             enum flag_t flags,
             int min_width,
             int precision,
             int upper) {
    char buff[BUFF_DOUBLE_REPR_SIZE__] ;
    int neg=0, nbchar, count ;
    int i ;
    int exp ;
    if (val < 0) {
    val = -val ;
    neg = 1 ;
    }
    nbchar = dbl2stre_ (val, buff, HAS_SHARP_FLAG__ (flags), precision, NULL, &exp) ;
    /* Put the e and sign : */
    buff[nbchar++] = (upper) ? 'E' : 'e' ;
    /* Nom, we print ! */
    count = 0 ;
    count += vp_before_number_ (neg, min_width, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags),
                HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), nbchar + DBL_MIN_EXP_DIG__ + 1) ;
    for (i=0; i<nbchar; ++i, ++count) {
    putchar(buff[i]) ;
    }
    /* Juste use the unsigned long long function to put the exponent. */
    count += vp_format_lli_ (exp, DBL_MIN_EXP_DIG__ + 1, 1, 0, 1, 0) ;
    count += vp_after_number_ (min_width, count, HAS_LEFT_JUSTIFIED__ (flags)) ;
    return count ;
}
 
#define DBL_MIN_REP_G__ 1e-3
#define DBL_MAX_REP_G__ 1e3
 
static int vp_double_g_ (double val,
             enum flag_t flags,
             int min_width,
             int precision,
             int upper) {
    if (val < DBL_MAX_REP_G__ && val > DBL_MIN_REP_G__) {
    if (precision > 0) {
        if (DBL_EPSILON > abs(val - ceil(val))) {
        precision = 0 ;
        }
    }
    return vp_double_f_ (val, flags, min_width, precision) ;
    }
    else {
    return vp_double_e_ (val, flags, min_width, precision, upper) ;
    }
}
 
#ifndef VP_C99__
static int isnan(double x) {
    return x != x ;
}
static int isfinite(double x) {
    return !((x == x) && (x != 0) && (x + 1 == x));
}
#endif
 
#define NAN_STR__ "NaN"
#define INF_STR__ "Inf"
 
static int vp_double_nan_or_inf_ (const char *str,
                  enum flag_t flags,
                  int min_width) {
    int count ;
    count = vp_before_number_ (0, min_width, 0, 0, 0, HAS_LEFT_JUSTIFIED__(flags), strlen(str)) ;
    count += vp_simple_str_ (str) ;
    count += vp_after_number_ (min_width, count, HAS_LEFT_JUSTIFIED__ (flags)) ;
    return count ;
}
 
#define DEFAULT_PRECISION__ 6
 
static int vp_double_ (const char *format, va_list *args, char type) {
    double val ;
    int min_width, precision ;
    int count ;
    enum flag_t flags ;
    get_format_parts_ (format, &flags, &min_width, &precision, args) ;
    min_width = (min_width < 0) ? 0 : min_width ;
    precision = (precision < 0) ? DEFAULT_PRECISION__ : precision ;
    val = va_arg(*args, double) ;
    if (isnan(val)) {
    return vp_double_nan_or_inf_ (NAN_STR__, flags, min_width) ;
    }
    else if (!isfinite(val)) {
    return vp_double_nan_or_inf_ (INF_STR__, flags, min_width) ;
    }
    switch (type) {
    case 'e': count = vp_double_e_ (val, flags, min_width, precision, (toupper(type) == type)) ; break ;
    case 'g': count = vp_double_g_ (val, flags, min_width, precision, (toupper(type) == type)) ; break ;
    case 'f':
    default : count = vp_double_f_ (val, flags, min_width, precision) ;
     
    }
    return count ;
}
 
void vp_store_count_ (const char *format,
              va_list *args,
              int count) {
    int *n = va_arg (*args, int*) ;
    *n = count ;
}
 
 
/* Get the type and specification of the format :
   type is set to a format type (d, f, x, X, etc... )
   spe is set to a specification (l, h, L for ll) if one is found, otherwize it is set to '\0'
   The function returns a pointer to the type character (i.e containing format type information) from the format parameter */
static const char *get_type_and_spe_ (const char *format, char *type, char *spe) {
    *spe = '\0' ;
    for (; *format != '\0' && !is_format_(*format); ++format) {
    if (is_spe_(*format)) {
        *spe = *format ;
        format ++ ;
#ifdef VP_C99__
        if (*spe == 'l' && *format == 'l') {
        *spe = 'L' ;
        format ++ ;
        }
#endif
        break ;
    }
    }
    *type = *format ;
    return format ;
}
 
static int vprintf_ (const char *format, va_list args) {
    int count = 0 ;
    char f, s ;
    const char *tmp ;
    for (; *format != '\0'; format++) {
    switch (*format) {
    case '%':
        ++ format ;
        tmp = get_type_and_spe_ (format, &f, &s) ;
        switch (f) {
        case 'd':
        case 'i': count += vp_li_   (format, &args, s) ;    break ;
        case 's': count += vp_str_  (format, &args, s) ;    break ;
        case 'c': count += vp_char_ (format, &args, s) ;    break ;
        case 'u': count += vp_ulli_ (format, &args, s) ;    break ;
        case 'x': count += vp_ullx_ (format, &args, s, 0) ; break ;
        case 'X': count += vp_ullx_ (format, &args, s, 1) ; break ;
        case 'p': count += vp_ptr_  (format, &args, s) ;    break ;
        case 'o': count += vp_ullo_ (format, &args, s) ;    break ;
        case 'f':
        case 'e':
        case 'E':
        case 'g':
        case 'G': count += vp_double_ (format, &args, f) ;  break ;
        case 'n': vp_store_count_ (format, &args, count) ; break ;
        case '%':
        default: putchar(*format) ; count ++ ;
        }
        format = (*tmp) ? tmp : format  ;
        break ;
    default:
        putchar(*format) ;
        ++ count ;
    }
    }
    return count ;
}
 
int my_printf (const char *format, ...) {
    int r ;
    va_list args ;
    va_start(args, format) ;
    r = vprintf_ (format, args) ;
    va_end(args) ;
    return r ;
}

Les mots-clefs du C

Un projet que j’avais initié, du temps où le SdZ était un site que j’aimais fréquenter. Le but ? Donner une explication simple et illustrée des différents mot-clefs qui existent en C. La plupart y sont présents, dont une explication sur asm que j’avais pris beaucoup de temps à rédiger et qui me parait pas claire du tout avec quelques années de recul (à moins que ce soit parce que c’est l’assembleur qui est un langage peu clair).

Le vieux qui parle

Lire ce topic, c’est aussi voir, pour ceux qui n’ont pas connu, à quoi ressemblait le zCode et se dire que le Markdown, c’est plus simple. :lol:

Voici une liste des mots-clefs sur lesquels on avait écrit à l’époque.

Un ECS en C

Un dernier mais non des moindres, une implémentation du principe d'Entities, Components, Systems en C, alors que la plupart des articles sur le sujet que j’ai vu le font en C++.


Voilà, fin de cette série de liens sur ce qu’on peut s’amuser à faire en C. Bon personnellement, je ne le ferai pas parce que je suis habitué au confort apporté par des langages comme C++, Python ou C#. Mais ça prouve que le C est vraiment un langage à tout faire qu’on peut tordre dans tous les sens pour faire des trucs insensés.

1 commentaire

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte