# Pourquoi le langage C?
Je n'ai jamais eu de formation en programmation/développement/code.
Enfin presque, j'ai eu droit à un module d'algorithmique en première
année de fac de sciences. Mais j'ai toujours aimé programmer. Pour moi,
c'est comme une partie d'Age of empire/Rimworld/échecs/
Smallworld/go/... : il faut anticiper, s'adapter et chercher le
meilleur moyen d'arriver à ses fins. Quand je dis "meilleur moyen", ça
signifie à la fois rapide et élégant.
J'ai appris énormément grâce à l'esprit du logiciel libre, en lisant ce
que d'autres ont bien voulu partager sur le web. Aussi j'ai pu
découvrir des scripts shell (si on peut parler de programmation), puis
le python, le PHP, le javascript et le go. En passant, j'ai appris le C
via le site du zéro (pour ceux qui s'en souviennent). Cependant je n'ai
pas approfondi : grave erreur.
Pendant la période de confinement de 2020, j'en ai profité pour m'y
remettre et réécrire certains projets en C. J'ai donc de nouveau lu du
code, notamment celui de
HTML suckless.org
mais aussi en partie celui d'
HTML OpenBSD
Cette expérience a été une aventure exaltante qui me fait dire
aujourd'hui : j'adore le C.
Certes, je ne suis pas un expert, loin de là. Cependant, je suis
convaincu qu'il s'agit d'un des meilleurs langages existants. Voilà
pourquoi...
## Pas de compromis
La syntaxe en C demande de la rigueur. Certains la désapprouve. Je
trouve au contraire qu'elle permet d'écrire du code plus lisible et
moins souvent source d'erreur.
Une erreur, et le compilateur ne voudra pas vous créer un binaire. Pas
de pitié. Oui, mais en même temps, ça évite de mauvaises surprises. Au
passage, clang sous OpenBSD donne de bons conseils :)
## C'est léger
La bibliothèque standard est à la fois complète et restreinte.
Autrement dit, il y a tout ce qu'il faut dedans, mais il faut parfois
assembler quelques pièces entre elles pour parvenir à ses fins.
## Ça force à mieux programmer
Les exigences du langage poussent à se poser davantage de questions, à
bien réfléchir à son projet et comment y parvenir. Ça force à mieux
programmer.
## C'est sur-documenté
C'est un langage qui date. Il y a énormément de ressources et de
conseils sur le web pour apprendre.
## C'est rapide
Comparé aux autres langage que j'ai pu appréhender, la différence est
flagrante.
## C'est beau
Argument complètement subjectif : je trouve le code C beau.
# Tout ce que j'aurais aimé qu'on me dise quand j'ai appris le C
Je code comme certains colorient un carreau sur 2, gribouillent des
petits dessins rigolos ou font des arabesques pour s'occuper pendant
les réunions. Je ne pense pas être bon à ça, mais j'aime beaucoup
m'occuper l'esprit en écrivant du C. C'est un peu comme jouer à
résoudre une énigme en s'amusant avec les règles du jeu.
J'ai appris le C il y a longtemps, en lisant le tutoriel du "site du
zero" (j'ai dit, c'était il y a longtemps). Je m'y suis remis
récemment, et avec du recul, sans être un expert, je m'aperçois que
j'aurais bien aimé savoir certaines choses à l'époque. Voici donc tout
ce que j'aurais aimé lire la première fois que j'ai appris le C.
## Macros utiles
Quand on déclare une chaîne de caractères, on ne sait jamais trop
quelle doit être sa longueur. Il existe en fait des limites déjà bien
définies pour nous :
* `PATH_MAX` : La taille maximale d'un chemin vers un fichier (#include
limits.h)
* `FILENAME_MAX` : La taille maximale d'un nom de fichie
* `BUFSIZ` : Taille d'un buffer, souvent utiliser pour traiter du texte.
(`#include stdio.h`)
## Lecture
Aller regarder dans les fichiers `/usr/include` peut parfois aider à
mieux comprendre. Mais par dessus tout, il faut lire les pages man.
J'ignore si c'est spécifique à OpenBSD, mais les sections (3) regorgent
d'explication, d'exemples, et de suggestions de fonctions associées
dont on ignore peut-être l'existence. Par exemple, `man strncat` propose
de lire des infos sur `strlcpy` pour la compléter/remplacer. Cette
dernière nous montre carrément des exemples.
## ternaire
Bon, c'est pas original, mais je n'avais jamais vraiment compris avant:
```
(condition) ? expression_si_vrai : expression_si_faux ;
```
## Les chaînes de caractères
En C, il y a le type "char". Les chaînes de caractères ne sont pas un
type. En C, il n'y a que des tableaux de "char", terminés par un NUL :
`\0`. C'est idiot, mais en fait très important à garder en tête pour
travailler sur du texte. La confusion est facile car on mélange les
expressions avec des " ou des '. En bref :
* `'a'` : c'est le char a
* `"a"` : c'est la "chaîne" constituée de 'a' puis '\0'.
On ne peut pas "additionner" ou modifier un tableau de char aussi
"facilement" que dans d'autres langages. Pourtant, dans `string.h`, on
peut trouver tout ce qu'il faut pour gérer les chaînes de caractères.
Il vaut mieux se garder une bibliothèque personnelle de fonctions
gérant les chaînes, et les réemployer au besoin.
Si une manipulation sur des chaînes de caractères nécessite d'allouer
de la mémoire avec malloc, alors il existe certainement une solution
plus simple. Ce n'est pas toujours vrai ceci dit ^^.
Utiliser la fonction `strdup()` permet de copier une chaîne de
caractères. Il faut penser à appeler `free()` ensuite avec cette
fonction.
La fonction `strsep()` modifie la chaîne qui lui est donnée. Il faut donc
parfois utiliser `strdup()` avant, car il faut donner une chaîne
modifiable et non constante. Autrement dit:
```
char *str = "coucou"; /* n'est pas modifiable */
char *str = strdup("coucou"); /* modifiable */
```
`strchr()` et `strstr()` sont très utiles pour récupérer l'emplacement
d'un caractère ou d'une chaîne de caractères. On peut directement
intervenir à partir de ce point ensuite.
Par exemple
```
char *pos = NULL;
char *str = "Respirer de la compote fait tousser";
pos = strstr(str, "compote");
puts(str); /* affiche "compote fait tousser" /*
```
Toujours initialiser une chaîne avec des 0 : ainsi, on est sûr qu'elle
est toujours terminée correctement (par un `\0`).
```
char str[BUFSIZ] = {'\0'};
char *str2 = calloc(BUFSIZ, sizeof(char));
```
Pour "vider" (remettre à zéro) une chaine de caractère (reset) :
```
bzero(s, sizeof(s));
```
Pour modifier une chaîne de caratères à l'aide d'une fonction, cette
dernière doit prendre en argument la taille de la chaîne. En effet,
elle ne reçoit en réalité qu'un pointeur vers un tableau de char, et un
`sizeof()` ne retournerait que la taille du pointeur. Exemple :
```
void
modif_str(char *str, ssize_t strsiz)
{
strlcpy(str, "coucou", strsiz);
}
char test[256] = {'\0'};
modif_str(test, sizeof(test));
```
## Fichiers
Il vaut mieux utiliser les fonctions `fread()`/`fwrite()` plutôt que
`read()`/`write()` car elles sont plus efficaces (utilisation d'un cache).
```
size_t nread = 0;
FILE *fd = NULL;
char *buffer[BUFSIZ];
if ((fd = fopen(fp, "r")) == NULL) { goto err; }
while ((nread = fread(buffer, 1, sizeof(buffer), fd)) != 0)
fwrite(buffer, 1, nread, stdout);
fclose(fd);
if (ferror(fd)) {
err(1,"closefile");
}
```
Pour lister le contenu répertoire: `scandir()`
```
#include <sys/types.h>
#include <dirent.h>
int n = 0;
struct dirent **namelist;
if ((n = scandir(path, &namelist, NULL, alphasort)) < 0) {
err(1, "Can't scan %s", path);
} else {
for(int j = 0; j < n; j++) {
if (!strcmp(namelist[j]->d_name, ".")) {
continue;
}
if (namelist[j]->d_type == DT_DIR) {
printf("%s\n", namelist[j]->d_name);
}
free(namelist[j]);
}
free(namelist);
}
```
## Gestion des erreurs
```
#include <err.h>
#include <errno.h>
```
Pour afficher une erreur et quitter : `err(1, "erreur :%s", blabla);`
Pour afficher un avertissement et quitter :
`warn("attention :%s", blabla);`
## Compiler
Dans la plupart des cours en C, on n'apprend pas à compiler. Pour un
simple fichier "main.c", il suffit d'un:
```
make main
```
Qui est en fait un raccourci pour:
```
cc -o main main.c
```
Pour compiler en utilisant une bibliothèque, il faut utiliser les
options `-l` `-L`, et `-I`. Le `-I` permet de dire où se trouvent les
entêtes (.h), `-L` indique où se trouvent les bibliothèques et `-l`
indique quelle bibilothèque utiliser.
C'est à partir de ce moment qu'un fichier Makefile devient intéressant.
Voici le modèle que j'utilise. Il est assez strict au niveau de la
gestion des erreurs, ça me force à écrire du code moins sensible:
```
NAME = thename
VERSION = 0.1
PREFIX?=/usr/local/
INCS = -I/usr/local/include
LIBS = -L/usr/local/lib -lCHANGEME -lOTHERLIB
LDFLAGS = ${LIBS}
CPPFLAGS = -DVERSION=\"${VERSION}\"
CFLAGS += -pedantic -Wall -Wextra -Wmissing-prototypes \
-Werror -Wshadow -Wstrict-overflow -fno-strict-aliasing \
-Wstrict-prototypes -Wwrite-strings \
${CPPFLAGS} \
${INCS} \
-Os
.SUFFIXES: .c .o
SRC != find . -type f -name \*.c
H != find . -type f -name \*.h
OBJ = ${SRC:.c=.o}
.c.o:
${CC} ${CFLAGS} -c $<
all: ${NAME}
${OBJ}: ${H}
clean:
rm -f ${NAME} *.core *.o
${NAME}: ${OBJ}
${CC} -o $@ ${OBJ} ${CFLAGS} ${LDFLAGS}
```
Bien sûr, il faudra changer "NAME", les "CHANGEME" après les `-l` et
pourquoi pas lire `man make` :)
Si on veut compiler un binaire pour un chroot, il faut ajouter l'option
`-static`.
## Modestie
Parfois, on lit du code très difficile à comprendre, avec des
opérations sur pointeurs pour gérer des chaînes de caractère par
exemple.
Il faut rester parfois modeste, et arrêter de vouloir un code
surpuissant meilleur que les autres. Il n'y a pas de code parfait.
Par exemple, c'est parfois tout aussi efficace de lire un fichier
caractère après caractères plutôt que de chercher à le découper en
morceaux à la recherche d'un motif particulier. Le C est très rapide :)
## Ressources
HTML Excellent site pour apprendre les bases du C
HTML Learn C the Hard Way de Zed A. Shaw
HTML Des remarques intéressantes pour écrire du C récent
HTML Notions pour présenter du code propre
HTML Idem, à la suckless
HTML Des exemples de code très instructifs
HTML Beej's Guide to C Programming
## Snippets C
Cette partie 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.
### Lire un fichier ligne après lignes
`getline()`, et hop :) Le man présente même un exemple.
Ceci dit, c'est souvent aussi rapide d'utiliser `fgets()` ou `fgetc()`.
### Initialiser une structure
Le mieux reste de le faire à la déclaration :
```
struct thing localThing = {0};
```
```
memset(&structure, 0, sizeof(struct structure_type));
```
### Utiliser fork() et éviter les zombies
En appelant un signal qui matche tous les processus enfants. Attention,
cela ne fait pas bon ménage avec `kqueue()`.
```
#include <signal.h>
static void sigchld(int unused);
void
sigchld(int unused)
{
(void)unused; // avoid unused variable warning
while (waitpid(WAIT_ANY, NULL, WNOHANG) > 0);
}
int
main(int argc, char *argv[])
/* ... */
if (signal(SIGCHLD, sigchld) == SIG_ERR)
err(1, "can't install SIGCHLD handler:");
/* ... */
```
Ou avec un `wait`/`waitpid` bien placé :
```
const char *cmd[] = { "/usr/bin/top", "-s", "0.5", NULL };
pid_t pid = fork();
if (pid == -1) {
err(1, "fork");
} else if (pid == 0) {
/* child */
execv(cmd[0], (char **)cmd);
err(1, "execv");
} else {
/* parent */
wait(NULL); // blocking
/* or :
waitpid(pid, NULL, WNOHANG); // non-blocking
*/
}
```
### 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 `pope`n 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ès 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");
}
```
### 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 :
```
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;
}
```
### 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 par 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;
}
```