Snippets C

Cette page sera alimentée de fonctions et extraits de code en C en guise d'aide mémoire. Ne prenez rien pour parfait, ces extraits peuvent certainement être améliorés.

Modifier une chaîne de caratères dans une fonction

Il est nécessaire de connaître la taille de la chaîne pour que tout fonctionne correctement :

void
modif_str(char *str, ssize_t strsiz)
{
    strlcpy(str, "coucou", strsiz);
}

char    test[256] = {'\0'};

modif_str(test, sizeof(test));

ecalloc, erealloc : allocation avec gestion d'erreur

/* same thing that calloc and reallocarray but check for
 * error and exit if necessary
 */

void *
ecalloc(size_t nmemb, size_t size)
{
    void *p;

    if ((p = calloc(nmemb, size)) == NULL)
        err(1, "calloc");
    return p;
}

void *
ereallocarray(void *ptr, size_t nmemb, size_t size)
{
    if ((ptr = reallocarray(ptr, nmemb, size)) == NULL)
        err(1, "reallocarray");

    return ptr;
}

get_stream_txt: Récupérer le contenu d'un fichier ouvert

/* return text in fp
 * the fp must be closed properly after
 * the returned string is mallocated and must be freed.
 */
char *
get_stream_txt(FILE *fp)
{
    char *buf = NULL;
    size_t s;
    size_t len = 0;
    size_t bsize = 2 * BUFSIZ;

    buf = ecalloc(bsize, sizeof(char));
    while((s = fread(buf + len, 1, BUFSIZ, fp))) {
        len += s;
        if(BUFSIZ + len + 1 > bsize) {
            bsize += BUFSIZ;
            buf = ereallocarray(buf, bsize, sizeof(char));
        }
    }
    buf[len] = '\0';

    return(buf);
}

lire un fichier

FILE *fd = NULL;
char buf[BUFSIZ] = {0};
size_t nread = 0;

if ((fd = fopen(path, "rb")) == NULL) {
        err(1, "%s: %s", "Fail to open", path);
}

while ((nread = fread(buf, 1, BUFSIZ, fd)) != 0 ) {
        fwrite(buf, 1, nread, stdout);
}
fclose(fd);
if (ferror(fd)) {
        err(1, "%s: %s", "close", path);
}

get_cmd_output: Récupérer la sortie d'une commande

/* return the output of cmd in an allocated
 * string created by get_stream_txt().
 * It is the caller responsibility to free it later
 */
char *
get_cmd_output(const char *cmd)
{
    FILE *fp = NULL;
    char *res = NULL;

    if ((fp = popen(cmd, "r")) == NULL)
        err(1, "%s: %s", "Error opening pipe for cmd", cmd);

    res = get_stream_txt(fp);
    if (pclose(fp) != 0)
        err(1, "%s %s", cmd, "not found or exited with error status");

    return res;
}

la fonction popen peut poser des soucis dans le cas où ce n'est pas le développeur qui précise la commande à entrer : les variables d'environnement sont conservées. Voici donc une autre façon de faire :

Créer un "pipe" pour lequel "0" est la sortie et "1" l'entrée.

Indiquer que "stdout" correspond à la sortie du pipe avec la fonction dup2(). Ceci permet de relier la sortie de la commande qu'on veut lancer au pipe pour pouvoir le lire ensuite comme un fichier normal.

Lancer la commande avec execlp() aprèq avoir fork()

Lecture du retour de la commande dans le processus parent. On utilise fdopen() puisque tout ce qu'on connaît, c'est le "file descriptor", pas un chemin vers un fichier.

<!-- -->
/* use pipe and fork to avoid shell with popen */
int fildes[2] = {0};
int status = 0;
pid_t pid = 0;
char *str = "blabla";
char *cmd = "hello";

if (pipe(fildes) != 0) {
    err(1, "pipe failed");
}

pid = fork();

if (pid < 0) {
    close(fildes[0]);
    close(fildes[1]);
    err(1, "fork failed");
}

if (pid > 0) { /* parent */
    char buf[3];
    size_t nread = 0;
    FILE *output = NULL;

    close(fildes[1]); /* make sure entry is closed so fread() gets EOF */

    /* use fread/fwrite because are buffered */
    output = fdopen(fildes[0], "r");
    if (output == NULL) {
        err(1, "fdopen failed");
    }

    /* read pipe output */
    while ((nread = fread(buf, 1, sizeof(buf), output)) != 0) {
        fwrite(buf, 1, nread, stdout);
    }
    close(fildes[0]);
    fclose(output);

    /* wait for child to terminate */
    waitpid(pid, &status, 0);

    exit(0);

} else if (pid == 0) { /* child */
    dup2(fildes[1], STDOUT_FILENO); /* set pipe output equal to stdout */
    dup2(fildes[1], STDERR_FILENO);
    close(fildes[1]); /* no need this file descriptor : it is now stdout of the command below */
    execlp(cmd, cmd, NULL);
    /* if execlp is ok, this will never be reached */
    err(1, "execlp failed to run: %s", "ls");
}

Jouons avec les strings

Quelques fonctions pour gérer les chaînes de caractères. Ici on abuse de la fonction strlcat qui s'assure que l'on termine bien une chaîne avec le fameux '\0'. Cette dernière est présente chez OpenBSD et facilement recopiable depuis leurs sources.

strlcat.c

startswith

int
startswith(const char *str, const char *start)
{
        size_t str_len = strlen(str);
        size_t start_len = strlen(start);


        int ret = 0;
        if ((str_len >= start_len) &&
                (strncmp(str, start, start_len) == 0)) {
                ret = 1;
        }

        return ret;
}

endswith

int
endswith(const char *str, const char *end)
{
    size_t str_len = strlen(str);
    size_t end_len = strlen(end);

    int ret = 0;
    if ((str_len >= end_len) &&
        (strcmp(str + (str_len -end_len), end) == 0)) {
        ret = 1;
    }

    return ret;
}

(v)estrlcat

/* strlcat with error handling */
size_t
estrlcat(char *dst, const char *src, size_t dstsize)
{
    size_t size;
    if ((size = strlcat(dst, src, dstsize)) >= dstsize)
        err(1, "strlcat");

    return size;
}

/* variadic strlcat
 * a call must end with NULL
 * to append "bar", "zoo", yo" to foo, call:
 * vestrlcat(foo, sizeof(foo), "bar", "zoo", "yo", NULL);
 */
size_t
vestrlcat(char *dst, size_t dstsize, ...)
{
    size_t size = 0;
    char *s;
    va_list ap;

    va_start(ap, dstsize);

    while ((s = va_arg(ap, char *)) != NULL) {
        size += estrlcat(dst, s, dstsize);
    }
    va_end(ap);

    return size;
}

stralloc

Et ma favorite qui permet de fabriquer n'importe quelle chaîne de caractères. On y passe un pointeur (qui peut être NULL au départ. Tous les arguments sont assemblés pour former une longue chaîne, cette dernière étant allouée dynamiquement au besoin.

/* 
 * Make any string 
 * add in str every argument and realloc str if necessary
 * if str is NULL, it is allocated
 * argument list **must** end with NULL
 * return str
 * str should be dynamically allocated and freed later
 * example: stralloc(&foo, "hello", " there.", NULL);
 */

char *
stralloc(char **str, ...)
{
    size_t len = 0;    /* length of str at last*/
    char *s = NULL;
    va_list ap;
    va_list apcopy;

    va_copy(apcopy, ap);

    /* count required length */
    va_start(ap, str);
    while ((s = va_arg(ap, char *)) != NULL) {
        len += strlen(s);
    }
    va_end(ap);
    len++; /* \0 */

    if (*str != NULL) {
        len += strlen(*str);
        *str = ereallocarray(*str, len, sizeof(char));
    } else {
        *str = ecalloc(len, sizeof(char));
    }

    /* copy */
    va_start(apcopy, str);

    while ((s = va_arg(apcopy, char *)) != NULL) {
        estrlcat(*str, s, len);
    }
    va_end(apcopy);
    return(*str);
}

Je lui préfère une version qui ne nécessite aucune allocation, et en profite pour vous présenter "estrlcpy" et "estrlcat" qui font comme strlcpy et strlcat mais en gérant les erreurs :

size_t
estrlcpy(char *dst, const char *src, size_t dstsize)
{
    size_t n = 0;

    n = strlcpy(dst, src, dstsize);
    if (n >= dstsize) {
        err(1, "strlcpy failed for %s = %s", dst, src);
    }

    return n;
}

size_t
estrlcat(char *dst, const char *src, size_t dstsize)
{
    size_t size;
    if ((size = strlcat(dst, src, dstsize)) >= dstsize)
        err(1, "strlcat on %s + %s", dst, src);

    return size;
}

/* usage:
 * mkstr(str, sizeof(str), optarg, "/INBOX/", NULL);
 */
size_t
mkstr(char *str, size_t dstsize, ...)
{
    char *s = NULL;
    size_t size = 0;
    va_list ap;

    va_start(ap, dstsize);

    while ((s = va_arg(ap, char *)) != NULL) {
        if (size == 0) {
            size += estrlcpy(str, s, dstsize);
        } else {
            size += estrlcat(str, s, dstsize);
        }
    }
    va_end(ap);
    return(size);
}

trim

Comme son nom l'indique, elle vire les espaces et sauts de ligne en début et fin de chaîne. Il faut passer un pointeur vers la chaîne à modifier (et donc allouée dynamiquement).

/* return a trimmed copy of str */
/* trim(&str); */
void
trim(char **str)
{
    size_t begin = 0;
    size_t end = strlen(*str);


    while (isspace((*str)[begin])) {
        begin++;
    }

    while (isspace((*str)[end-1])) {
        end--;
    }
    for (size_t i = begin, j=0; i < end; i++, j++) {
        (*str)[j] = (*str)[i];
    }
    (*str)[end - begin] = '\0';
}

(v)esnprintf

Cette fonction fait comme snprintf avec gestion d'erreur. Il faut avoir un tableau de char déjà prêt, par exemple char s[256]. Ce code est en partie tiré de celui utilisé souvent par suckless.org. Elle est très pratique car permet de définir le format des variables qu'une chaîne doit contenir. Par exemple :

esnprintf(s, sizeof(s), "%s%d%c", "blabla", 42, 'a');

static int
evsnprintf(char *str, size_t size, const char *format, va_list ap)
{
    int ret = vsnprintf(str, size, format, ap);

    if (ret < 0 || (size_t)ret >= size) {
        warn("%s", "vsnprintf: Output truncated");
    }

    return ret;
}

int
esnprintf(char *str, size_t size, const char *format, ...)
{
    va_list ap;
    int ret = 0;

    va_start(ap, format);
    ret = evsnprintf(str, size, format, ap);
    va_end(ap);

    return ret;
}

strrstr : rechercher le dernier morceau

strrstr() fait comme strstr, mais pointe vers la dernière occurence, pas la première :

char *   strrstr(const char *, const char *);

char *
strrstr(const char *big, const char *little)
{
    char *p = NULL;
    char *ret = NULL;

    if ((p = strstr(big, little)) == NULL) {
        return NULL;
    }

    /* loop until last occurence */
    while ((p = strstr(p, little)) != NULL) {
        ret = p;
        p++;
    }

    return ret;
}

efopen, efclose, evfprintf : gestion des fichiers

On ouvre et on ferme les fichiers en gérant les erreurs

FILE *
efopen(const char *path, const char *mode)
{
    FILE *f = NULL;
    if ((f = fopen(path, mode)) == NULL)
        err(1, "%s: %s", "Fail to open", path);

    return f;
}

int
efclose(FILE *stream)
{
    int ret = 0;
    ret = fclose(stream);
    if (ferror(stream)) {
        err(1,"closefile");
    }
    return ret;
}

On écrit dans un fichier autant de choses que l'on veut en une fois. C'est comme fprint mais avec une liste d'arguments variable.

int
evfprintf(FILE *stream, const char *format, va_list ap)
{
    int ret;

    ret = vfprintf(stream, format, ap);
    if (ret < 0)
        err(1,"evfprintf:");

    return ret;
}

int
efprintf(FILE *stream, const char *format, ...)
{
    va_list ap;
    int ret;

    va_start(ap, format);
    ret = evfprintf(stream, format, ap);
    va_end(ap);

    return ret;
}

OpenBSD unveil et pledge

Deux macros pratiques et un exemple pour éviter des erreurs de compilation sur un autre système:

#define PLEDGEORDIE(prom) if (pledge(prom, NULL) == -1) { err(1, "pledge"); }
#define UNVEILORDIE(path,perms) if (unveil(path, perms) == -1) { err(1, "unveil"); }

Exemple:

#ifdef __OpenBSD__
    UNVEILORDIE(argv[1], "rwc");
    PLEDGEORDIE("stdio rpath cpath wpath exec proc");
#endif

Retirer le "\n" de fin de ligne

line[strcspn(line, "\r\n")] = '\0'; /* remove newline */

de la bonne façon d'utiliser strsep

strsep modifie la chaîne, et donc fait perdre la référence du pointeur correspondant. Il faut donc en garder une trace, ce qui donnerait (merci stacko) :

char *r = strdup(str);

char *tofree = r;
char *tok;
while ((tok = strsep(&r, " ")) != NULL) {
    puts(tok);
}

free(tofree);

récupérer les substrings d'une regex (regex.h)

#include <regex.h>
#include <sys/types.h>

#define LEN(X)               (sizeof X / sizeof X[0])

#define SE_MAX 4 /* max subexpression, in nmatch. This is the number of () + 1 */

int 
main(int argc, char *argv[])
{
    regex_t greg;   /* compiled gemini regex */
    size_t nmatch = SE_MAX;
    regmatch_t match[SE_MAX];
    int ret = 0;

    char buf[BUFSIZ] = {'\0'};

    char *gemini_regex = "^gemini://([^/]*)/?([^\?]*)[\?]?(.*)?$";
    char *lines[] = {
        "gemini://si3t.ch",
        "gemini://si3t.ch/subdir/index.gmi?query?weird",
        "gemini://si3t.ch/subdir?query",
        "gemini://si3t.ch?query",
        "gemini://si3t.ch//?query",
    };

    if ((ret = regcomp(&greg, gemini_regex, REG_EXTENDED)) != 0) {
        regerror(ret, &greg, buf, sizeof(buf));
        regfree(&greg);
        err(1, "%s", buf);
    }

    for (size_t l = 0; l < LEN(lines); l++) {
        printf("[%s]\n", lines[l]);

        if ((ret = regexec(&greg, lines[l], nmatch, match, 0)) != 0) {
            regerror(ret, &greg, buf, sizeof(buf));
            if (ret == REG_NOMATCH)
                warnx("No match :(");
            else {
                regfree(&greg);
                errx(1, "%s", buf);
            }
        }

        for (int i = 1; i <= greg.re_nsub; i++) {
            if (match[i].rm_so != match[i].rm_eo)
                size_t len = match[i].rm_eo - match[i].rm_so;
                memcpy(buf, lines[l] + match[i].rm_so, len);
                puts(buf);
            }
        }
    }

    regfree(&greg);
    return 0;
}

Dictionnaire (liste chaînée)

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

typedef struct Dict {
    char *key;
    char *val;
    struct Dict *next;
} Dict;


void * emalloc(size_t);
Dict * dict_new(void);
void dict_free(Dict *);
char * dict_get_key(Dict *, const char *);
void dict_print(Dict *);
void dict_append(Dict *, const char *, const char *);
void dict_push(Dict **, const char *, const char *);



void *
emalloc(size_t size)
{
    void *p;

    p = malloc(size);
    if (p == NULL)
        err(1, "malloc");

    return p;
}

void
dict_free(Dict *d)
{
    Dict *nextitm;


    while (d != NULL) {
        nextitm = d->next;
        free(d->key);
        free(d->val);
        free(d);
        d = nextitm;
    }
    free(d);
}

void
dict_print(Dict *d)
{
    Dict *cur = d;

    while (cur->next != NULL) {
        printf("%s: %s\n", cur->key, cur->val);
        cur = cur->next;
    }
}

char *
dict_get_key(Dict *d, const char *key)
{
    Dict *cur = d;

    while (cur->next != NULL) {
        if ((strcmp(cur->key, key)) == 0) {
            return cur->val;
        }
        cur = cur->next;
    }


    return NULL;
}

void
dict_append(Dict *d, const char *key, const char *val)
{

    Dict *cur = d;

    while (cur->next != NULL) {
        cur = cur->next;
    }

    cur->next = dict_new();
    cur->key = strdup(key);
    cur->val = strdup(val);
}

void
dict_push(Dict **d, const char *key, const char *val)
{

    Dict *new = dict_new();

    new->key = strdup(key);
    new->val = strdup(val);
    new->next = *d;

    *d = new;
}

Dict *
dict_new(void)
{
    Dict *d = emalloc(sizeof(Dict));
    d->key = NULL;
    d->val = NULL;
    d->next = NULL;
    return d;
}

int 
main(void)
{
    Dict *d = dict_new();
    dict_append(d, "batman", "un super héros");
    dict_append(d, "chat", "envahisseur d'internet");
    dict_push(&d, "abricot", "fruit placé au début");
    dict_print(d);
    dict_free(d);
    return 0;
}

Listes et queues (queue.h)

Beaucoup plus simple que de réimplémenter des listes chaînées.

Voici un exemple. Le reste, c'est dans ''man queue''. Il n'y a pas de code de vérification d'erreur :!:.

J'utilise les conventions suivantes :

Les structures qui seront dans les listes ont un nom commençant pas une majuscule

Les entête de liste portent le nom de la structure suivi de "_head".

Le nom des entrées porte le nom de la structure en minuscule.

<!-- -->
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>

struct Item {
    int id;
    char path[PATH_MAX];
    char title[256];    /* 256 is more than enough */
    SIMPLEQ_ENTRY(Item) item;
};
SIMPLEQ_HEAD(Item_head, Item);

int 
main(int argc, char *argv[])
{

    struct Item_head itemh;
    struct Item *i1, *ip;

    SIMPLEQ_INIT(&itemh);

    i1 = malloc(sizeof(struct Item));

    strlcpy(i1->title, "coucou", sizeof(i1->title));

    SIMPLEQ_INSERT_TAIL(&itemh, i1, item);
    SIMPLEQ_FOREACH(ip, &itemh, item) {
        puts(ip->title);
    }

    free(i1);


    return 0;
}