Petit programme en C, avez-vous des remarques etc. ?
Aucune réponse
Francois Lafont
Bonjour à tous,
Autant le dire tout de suite, je suis vraiment totalement
inexpérimenté en langage C (pour ne pas dire plus). Ceci,
étant dit, j'ai codé un petit programme et j'aimerais bien
avoir, si c'est possible, vos suggestions et critiques. Je
mets le programme en fin de message.
C'est un programme qui se lance en ligne de commandes de cette
manière :
Le programme va alors lancer une requête http à l'adresse
http://localhost/cgi/machin.pl en utilisant la lib cURL
(ici, il s'agit d'un script Perl en localhost mais ça pourrait
être autre chose). La valeur 10 correspond à un timeout en
secondes (au delà le programme abandonne la requête), et le
reste des arguments xxx, yyy, zzz (il doit y en avoir au moins
1, mais ce n'est pas forcément 3 arguments, le nombre d'arguments
dépend de la page qui est appelée) est passé en variable POST
à la page http qui est appelée. Les variables POST seront
définies sous cette forme :
token1=xxx
token2=yyy
token3=zzz
Ensuite, les pages qui seront appelées avec ce programme
devront fournir une sortie de la forme :
- une première avec juste le nombre 0, 1, 2 ou 3;
- et des lignes suivantes avec ce qu'on veut, peu importe.
Par exemple :
2
CRITICAL blabla blabla.
Blibli blibli.
est une sortie correcte pour la page web. Ensuite, le
programme, qui aura mis cette sortie dans un buffer,
affichera uniquement les lignes autres que la ligne 1
et devra retourner comme exit code la valeur indiquée
dans la ligne 1 de la sortie de la page web. Si je
reprends l'exemple précédent, le programme devra donc
dans ce cas là afficher en sortie :
CRITICAL blabla blabla.
Blibli blibli.
tout en retournant la valeur 2 comme exit code.
Enfin, si la sortie de la page web appelée est trop
longue, alors le programme devra tronquer cette
sortie suivant une taille limite qui est indiquée
en tant que macro dans le programme.
Voilà pour ce que le programme est censé faire.
Pour l'écrire, je suis parti d'exemples que l'on
trouve ici ou là sur le web à propos de la lib cURL.
Par exemple, un point en particulier sur lequel
j'aimerais bien des avis (même si je suis vraiment
preneur de toute remarque à propos du code), c'est
l'usage du "cast". J'ai souvent entendu dire que
c'était à éviter et qu'on pouvait en général s'en
passer. Or dans le code, le cast est pas mal utilisé.
Peut-être que je m'y suis mal pris... Je n'ai pas vu
comment faire autrement par exemple au niveau d'une
fonction qui attend en argument des pointeurs de type
void*. En effet, dans le code on indique que l'on
souhaite écrire la sortie de la requête cURL dans un
buffer en indiquant le nom d'une fonction write_data
qui doit avoir cette signature :
où :
- la variable buffer un pointeur vers un buffer qui
contient un bout de la sortie de la requête cURL,
- size*nmemb correspond à la taille de ce buffer,
- userp est un pointeur vers le buffer dans lequel
on souhaite écrire la sortie de cURL (c'est ce
buffer là que l'on récupère dans la fonction main).
Dans la fonction main, userp correspond à la variable
wr_buf qui est un tableau de "char" mais il doit être
casté en void* pour être passé à la fonction write_data,
sachant que, dans le corps de la fonction write_data, je
m'empresse de faire le cast « inverse » (ie je caste
userp en un pointeur de type char*). Voilà par exemple
un point sur lequel je m'interroge. Mais encore une
fois, toutes vos remarques (sur la forme comme sur le
fond) m'intéressent.
Au départ, je pensais que ce programme allait réellement
me servir mais finalement il est probable que je ne l'utilise
pas. Ceci étant, en écrivant ce programme, je me suis pris
au jeu en quelques sortes et j'ai tenté de l'achever tant
bien que mal.
Merci d'avance pour toutes vos remarques et critiques.
François Lafont
if (argc < 4) {
printf("Sorry, bad syntax. You must apply at least 3 arguments.\n");
printf("%s <timeout> <url> <args>...\n", argv[0]);
return UNKNOWN;
}
if (!isPositiveInteger(argv[1])) {
printf("Sorry, bad syntax. The first argument must be a positive");
printf(" integer (it's a timeout in seconds).\n");
return UNKNOWN;
}
int timeout = atoi(argv[1]);
char *url = argv[2];
// First step, init curl.
CURL *curl;
curl = curl_easy_init();
if (!curl) {
printf("Sorry, couldn't init curl.\n");
return UNKNOWN;
}
// Construction of the post variable, a string with this form:
// token1=<urlencoded data1>&token2=<urlencoded data2>&...
char post[MAX_POST_LENGTH] = { 0 };
int token_num = 1;
char *urlencoded_str = NULL;
int i = 0;
for (i = 3; i < argc; i++) {
if (token_num > 999) {
printf
("Sorry, the limit number (999) of POST variables is exceeded.\n");
curl_easy_cleanup(curl);
return UNKNOWN;
}
// 10 is the max length of the string "token<num>=&".
// The maximum is reached with "token999=&".
int temp_size = 10 + strlen(urlencoded_str) + 1;
char temp[temp_size];
//memset(temp, 0, temp_size*sizeof(char));
sprintf(temp, "token%d=%s&", token_num, urlencoded_str);
if (strlen(post) + strlen(temp) + 1 < MAX_POST_LENGTH) {
strcat(post, temp);
}
else {
printf("Sorry, the max POST size is exceeded.\n");
curl_easy_cleanup(curl);
return UNKNOWN;
}
curl_free(urlencoded_str);
token_num++;
}
// Remove the last character "&".
post[strlen(post) - 1] = 0;
//printf("C: POST [%s]\n", post);
// Tell curl that we'll receive data to the function write_data
// which will write the data in wr_buf.
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) wr_buf);
// Allow curl to perform the action.
CURLcode ret;
ret = curl_easy_perform(curl);
if (ret) {
curl_easy_cleanup(curl);
printf("Sorry, exit value of curl is %d.", ret);
switch (ret) {
case CURLE_COULDNT_RESOLVE_HOST:
printf(" Could not resolve the host address.\n");
break;
case CURLE_OPERATION_TIMEDOUT:
printf(" Operation timeout.\n");
break;
int return_value;
if (!strncmp(wr_buf, "0\n", 2)) {
return_value = OK;
}
else if (!strncmp(wr_buf, "1\n", 2)) {
return_value = WARNING;
}
else if (!strncmp(wr_buf, "2\n", 2)) {
return_value = CRITICAL;
}
else if (!strncmp(wr_buf, "3\n", 2)) {
return_value = UNKNOWN;
}
else {
printf("Unexpected output of the plugin, return value not");
printf(" displayed or not in {0, 1, 2, 3}.\n");
return UNKNOWN;
}
printf("%s", wr_buf + 2);
return return_value;
}
// Write data callback function (called within the context
// of curl_easy_perform).
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp) {
// It is assumed that the type of userp is char*.
char *wr_buf = (char *) userp;
static int wr_index = 0;
int segsize = size * nmemb;
// Check to see if this data exceeds the size of our buffer. If so,
// it's possible to return O to indicate a problem to curl.
// But here, we just stop the function without error (ie, we return
// segsize) and our buffer will be troncated.
if (wr_index + segsize > MAX_BUF) {
if (MAX_BUF - wr_index > 0) {
memcpy((void *) &wr_buf[wr_index], buffer,
(size_t) (MAX_BUF - wr_index));
}
wr_index = MAX_BUF + 1; // wr_buf will be not written anymore.
return segsize;
}
// Copy the data from the curl buffer into our buffer.
memcpy((void *) &wr_buf[wr_index], buffer, (size_t) segsize);
// Update the write index.
wr_index += segsize;
// Return the number of bytes received, indicating to curl that
// all is okay.
return segsize;
}
int isPositiveInteger(const char s[]) {
if (s == NULL || *s == '\0') {
return 0;
}
int i;
for (i = 0; i < strlen(s); i++) {
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57.
if (s[i] < 48 || s[i] > 57) {
return 0;
}
}
A partir du moment où ce tableau est une chaîne valide terminée par , toute opération de type strcat(), etc. garde cet invariant.
Ok, effectivement, tu as raison.
Même chose ici, en l'état je ne crois pas que je puisse me passer de cette initialisation.
Il suffirait (à priori en lisant le code rapidement), dans write_data(), après le memcpy(), de coller un .
Oui, là aussi tu as raison.
A ce sujet, write_data() prend un "userp" opaque, qui permet de ne pas avoir d'horribles variables statiques. Plutôt que de passer "juste" le buffer, on peut passer une structure avec plus d'infos:
... et utiliser wr->index en place de 'wr_index', wr->capa en place de 'MAX_BUF_IN_BYTES', et wr->buf en place de ''wr_buf
Utiliser une structure semble effectivement beaucoup plus classe. ;) J'essayerai d'écrire cela proprement... une fois de retour chez moi. Car là il faut que je me pose sur mon bureau avec papier + crayon etc. chose que je ne peux pas faire actuellement.
Mais le assert n'est-il pas consommateur de ressources ? Car je suppose qu'il sera testé à chaque appel de la fonction, non ?
Non, cela ne coûte rien. Éventuellement on se pose la question dans une boucle interne de copie de bitmap, par exemple, mais dans une fonction qui est appelée dans le cadre de transferts réseau, un test de taille, c'est totalement transparent.
Ok je comprends. En revanche je ne sais pas si le assert est adapté dans mon cas. En effet, si la sortie de curl est trop grande et dépasse la taille de mon buffer, je ne veux pas qu'il y ait d'erreur et je veux simplement que la sortie soit tronquée.
Or, la fonction write_data doit toujours s'exécuter sans erreur et renvoyer segsize sinon curl n'est pas content. Du coup, dans le cas d'un dépassement du buffer, la fonction write_data ne doit rien faire, à part un "return segsize". Du coup, je ne crois pas qu'un assert soit le bienvenu dans cette fonction.
Préférence personnelle également: je compile les assert() y compris en "release". Sinon ça sert à rien, un assert.
Ok, je vois. Merci encore pour toutes ces explications.
-- François Lafont
Bonjour,
Le 17/07/2014 08:16, Xavier Roche a écrit :
Mais un:
post[0] = ' ';
suffit à priori, non ?
A partir du moment où ce tableau est une chaîne valide terminée par , toute opération de type strcat(), etc. garde cet invariant.
Ok, effectivement, tu as raison.
Même chose ici, en l'état je ne crois pas que je puisse
me passer de cette initialisation.
Il suffirait (à priori en lisant le code rapidement), dans write_data(), après le memcpy(), de coller un .
Oui, là aussi tu as raison.
A ce sujet, write_data() prend un "userp" opaque, qui permet de ne pas avoir d'horribles variables statiques. Plutôt que de passer "juste" le buffer, on peut passer une structure avec plus d'infos:
... et utiliser wr->index en place de 'wr_index', wr->capa en place de 'MAX_BUF_IN_BYTES', et wr->buf en place de ''wr_buf
Utiliser une structure semble effectivement beaucoup plus
classe. ;) J'essayerai d'écrire cela proprement... une fois
de retour chez moi. Car là il faut que je me pose sur mon
bureau avec papier + crayon etc. chose que je ne peux pas
faire actuellement.
Mais le assert n'est-il pas consommateur de ressources ?
Car je suppose qu'il sera testé à chaque appel de la
fonction, non ?
Non, cela ne coûte rien. Éventuellement on se pose la question dans une boucle interne de copie de bitmap, par exemple, mais dans une fonction qui est appelée dans le cadre de transferts réseau, un test de taille, c'est totalement transparent.
Ok je comprends. En revanche je ne sais pas si le assert
est adapté dans mon cas. En effet, si la sortie de curl
est trop grande et dépasse la taille de mon buffer, je ne
veux pas qu'il y ait d'erreur et je veux simplement que
la sortie soit tronquée.
Or, la fonction write_data doit toujours s'exécuter
sans erreur et renvoyer segsize sinon curl n'est pas
content. Du coup, dans le cas d'un dépassement du
buffer, la fonction write_data ne doit rien faire,
à part un "return segsize". Du coup, je ne crois pas
qu'un assert soit le bienvenu dans cette fonction.
Préférence personnelle également: je compile les assert() y compris en "release". Sinon ça sert à rien, un assert.
A partir du moment où ce tableau est une chaîne valide terminée par , toute opération de type strcat(), etc. garde cet invariant.
Ok, effectivement, tu as raison.
Même chose ici, en l'état je ne crois pas que je puisse me passer de cette initialisation.
Il suffirait (à priori en lisant le code rapidement), dans write_data(), après le memcpy(), de coller un .
Oui, là aussi tu as raison.
A ce sujet, write_data() prend un "userp" opaque, qui permet de ne pas avoir d'horribles variables statiques. Plutôt que de passer "juste" le buffer, on peut passer une structure avec plus d'infos:
... et utiliser wr->index en place de 'wr_index', wr->capa en place de 'MAX_BUF_IN_BYTES', et wr->buf en place de ''wr_buf
Utiliser une structure semble effectivement beaucoup plus classe. ;) J'essayerai d'écrire cela proprement... une fois de retour chez moi. Car là il faut que je me pose sur mon bureau avec papier + crayon etc. chose que je ne peux pas faire actuellement.
Mais le assert n'est-il pas consommateur de ressources ? Car je suppose qu'il sera testé à chaque appel de la fonction, non ?
Non, cela ne coûte rien. Éventuellement on se pose la question dans une boucle interne de copie de bitmap, par exemple, mais dans une fonction qui est appelée dans le cadre de transferts réseau, un test de taille, c'est totalement transparent.
Ok je comprends. En revanche je ne sais pas si le assert est adapté dans mon cas. En effet, si la sortie de curl est trop grande et dépasse la taille de mon buffer, je ne veux pas qu'il y ait d'erreur et je veux simplement que la sortie soit tronquée.
Or, la fonction write_data doit toujours s'exécuter sans erreur et renvoyer segsize sinon curl n'est pas content. Du coup, dans le cas d'un dépassement du buffer, la fonction write_data ne doit rien faire, à part un "return segsize". Du coup, je ne crois pas qu'un assert soit le bienvenu dans cette fonction.
Préférence personnelle également: je compile les assert() y compris en "release". Sinon ça sert à rien, un assert.
Ok, je vois. Merci encore pour toutes ces explications.
-- François Lafont
Xavier Roche
On 07/17/2014 01:42 PM, Francois Lafont wrote:
Ok je comprends. En revanche je ne sais pas si le assert est adapté dans mon cas.
Le assert était là pour protéger le memcpy, rien d'autre (donc il est juste avant)
En gros le assert permet de faire crasher "proprement" un programme en cas de problème, ce qui est considéré comme plus désirable par rapport à: - un crash "sale" (déréférencement de pointeur NULL par exemple, qui est difficile à tracer, ou écrasement de pile) - une corruption silencieuse (qui pourrait se manifester longtemps après) - un invariant qui n'est pas respecté (et qui ferait entrer le programme dans un état bizarre)
[ Le assert n'est PAS là pour vérifier le retour d'erreur d'une fonction par contre (enfin en règle générale - il est tolérable de assert'er un retour non nul de malloc/realloc, selon les cas) ]
Donc ici le assert sert uniquement à vérifier que le memcpy ne pas pas écrabouiller quelque chose qui ne t'appartient pas :)
Si je comprends bien ces 2 instructions, tu définis *ta* propre fonction assert, c'est bien ça ?
Oui, comme ça on sait précisément ce que ça fait (NDEBUG a tendance a être défini assez facilement sur des builds "de production", désactivant la macro)
[ En théorie on devrait utiliser un autre nom du coup, mais je chipotte ]
Si je me contente d'un simple : #include <assert.h> ça ne suffit pas ?
Oui, mais faire attention à ne pas définir NDEBUG du coup (ce qui désactiverait cette macro)
On 07/17/2014 01:42 PM, Francois Lafont wrote:
Ok je comprends. En revanche je ne sais pas si le assert
est adapté dans mon cas.
Le assert était là pour protéger le memcpy, rien d'autre (donc il est
juste avant)
En gros le assert permet de faire crasher "proprement" un programme en
cas de problème, ce qui est considéré comme plus désirable par rapport à:
- un crash "sale" (déréférencement de pointeur NULL par exemple, qui est
difficile à tracer, ou écrasement de pile)
- une corruption silencieuse (qui pourrait se manifester longtemps après)
- un invariant qui n'est pas respecté (et qui ferait entrer le programme
dans un état bizarre)
[ Le assert n'est PAS là pour vérifier le retour d'erreur d'une fonction
par contre (enfin en règle générale - il est tolérable de assert'er un
retour non nul de malloc/realloc, selon les cas) ]
Donc ici le assert sert uniquement à vérifier que le memcpy ne pas pas
écrabouiller quelque chose qui ne t'appartient pas :)
Si je comprends bien ces 2 instructions, tu définis *ta*
propre fonction assert, c'est bien ça ?
Oui, comme ça on sait précisément ce que ça fait (NDEBUG a tendance a
être défini assez facilement sur des builds "de production", désactivant
la macro)
[ En théorie on devrait utiliser un autre nom du coup, mais je chipotte ]
Si je me contente d'un simple :
#include <assert.h>
ça ne suffit pas ?
Oui, mais faire attention à ne pas définir NDEBUG du coup (ce qui
désactiverait cette macro)
Ok je comprends. En revanche je ne sais pas si le assert est adapté dans mon cas.
Le assert était là pour protéger le memcpy, rien d'autre (donc il est juste avant)
En gros le assert permet de faire crasher "proprement" un programme en cas de problème, ce qui est considéré comme plus désirable par rapport à: - un crash "sale" (déréférencement de pointeur NULL par exemple, qui est difficile à tracer, ou écrasement de pile) - une corruption silencieuse (qui pourrait se manifester longtemps après) - un invariant qui n'est pas respecté (et qui ferait entrer le programme dans un état bizarre)
[ Le assert n'est PAS là pour vérifier le retour d'erreur d'une fonction par contre (enfin en règle générale - il est tolérable de assert'er un retour non nul de malloc/realloc, selon les cas) ]
Donc ici le assert sert uniquement à vérifier que le memcpy ne pas pas écrabouiller quelque chose qui ne t'appartient pas :)
Si je comprends bien ces 2 instructions, tu définis *ta* propre fonction assert, c'est bien ça ?
Oui, comme ça on sait précisément ce que ça fait (NDEBUG a tendance a être défini assez facilement sur des builds "de production", désactivant la macro)
[ En théorie on devrait utiliser un autre nom du coup, mais je chipotte ]
Si je me contente d'un simple : #include <assert.h> ça ne suffit pas ?
Oui, mais faire attention à ne pas définir NDEBUG du coup (ce qui désactiverait cette macro)
Lucas Levrel
Le 17 juillet 2014, Francois Lafont a écrit :
[ ou, avec GCC, pour indiquer que la condition est en général vraie, mais là je chipote vraiment un peu:
Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test est la plus probable, pour qu'il optimise le code produit en ce sens ; le code de la branche « vrai » sera préférentiellement à la suite du test, le code de la branche « faux » sera ailleurs, ce qui pénalise éventuellement la performance s'il doit être exécuté (cache miss de la mémoire contenant le code).
-- LL Ἕν οἶδα ὅτι οὐδὲν οἶδα (Σωκράτης)
Le 17 juillet 2014, Francois Lafont a écrit :
[ ou, avec GCC, pour indiquer que la condition est en général vraie, mais là je chipote vraiment un peu:
Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test est
la plus probable, pour qu'il optimise le code produit en ce sens ; le code
de la branche « vrai » sera préférentiellement à la suite du test, le code
de la branche « faux » sera ailleurs, ce qui pénalise éventuellement la
performance s'il doit être exécuté (cache miss de la mémoire contenant le
code).
Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test est la plus probable, pour qu'il optimise le code produit en ce sens ; le code de la branche « vrai » sera préférentiellement à la suite du test, le code de la branche « faux » sera ailleurs, ce qui pénalise éventuellement la performance s'il doit être exécuté (cache miss de la mémoire contenant le code).
Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test est la plus probable, pour qu'il optimise le code produit en ce sens ; le code de la branche « vrai » sera préférentiellement à la suite du test, le code de la branche « faux » sera ailleurs, ce qui pénalise éventuellement la performance s'il doit être exécuté (cache miss de la mémoire contenant le code).
Oui pardon, j'avais zappé la question.
Il y a typiquement deux manière de compiler un test du type:
if (expression) { bloc_instruction_1; } else { bloc_instruction_2; }
(1) Version "équilibrée":
tester expression si faux goto label_2 bloc_instruction_1 goto label_fin
label_2: bloc_instruction_2
label_fin: ... suite du code
On peut éventuellement faire une version "si vrai" pour d'obscures raisons de prédiction de branche, mais cela reviens au même: on a dans tous les cas un "goto" (jump conditionnel)
(2) Version "déséquilibrée"
tester expression si faux goto label_2 bloc_instruction_1
label_fin: ... suite du code
... fin du code
label_2: bloc_instruction_2
goto label_fin
Là dans un cas on a aucun branchement, et dans l'autre on en a deux, avec en prime un "gap" important (donc le processeur doit recharger un autre segment de cache)
Explications très approximatives, mais c'est pour donner l'idée.
Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test
est la plus probable, pour qu'il optimise le code produit en ce sens ;
le code de la branche « vrai » sera préférentiellement à la suite du
test, le code de la branche « faux » sera ailleurs, ce qui pénalise
éventuellement la performance s'il doit être exécuté (cache miss de la
mémoire contenant le code).
Oui pardon, j'avais zappé la question.
Il y a typiquement deux manière de compiler un test du type:
if (expression) {
bloc_instruction_1;
} else {
bloc_instruction_2;
}
(1) Version "équilibrée":
tester expression
si faux goto label_2
bloc_instruction_1
goto label_fin
label_2:
bloc_instruction_2
label_fin:
... suite du code
On peut éventuellement faire une version "si vrai" pour d'obscures
raisons de prédiction de branche, mais cela reviens au même: on a dans
tous les cas un "goto" (jump conditionnel)
(2) Version "déséquilibrée"
tester expression
si faux goto label_2
bloc_instruction_1
label_fin:
... suite du code
... fin du code
label_2:
bloc_instruction_2
goto label_fin
Là dans un cas on a aucun branchement, et dans l'autre on en a deux,
avec en prime un "gap" important (donc le processeur doit recharger un
autre segment de cache)
Explications très approximatives, mais c'est pour donner l'idée.
Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test est la plus probable, pour qu'il optimise le code produit en ce sens ; le code de la branche « vrai » sera préférentiellement à la suite du test, le code de la branche « faux » sera ailleurs, ce qui pénalise éventuellement la performance s'il doit être exécuté (cache miss de la mémoire contenant le code).
Oui pardon, j'avais zappé la question.
Il y a typiquement deux manière de compiler un test du type:
if (expression) { bloc_instruction_1; } else { bloc_instruction_2; }
(1) Version "équilibrée":
tester expression si faux goto label_2 bloc_instruction_1 goto label_fin
label_2: bloc_instruction_2
label_fin: ... suite du code
On peut éventuellement faire une version "si vrai" pour d'obscures raisons de prédiction de branche, mais cela reviens au même: on a dans tous les cas un "goto" (jump conditionnel)
(2) Version "déséquilibrée"
tester expression si faux goto label_2 bloc_instruction_1
label_fin: ... suite du code
... fin du code
label_2: bloc_instruction_2
goto label_fin
Là dans un cas on a aucun branchement, et dans l'autre on en a deux, avec en prime un "gap" important (donc le processeur doit recharger un autre segment de cache)
Explications très approximatives, mais c'est pour donner l'idée.
Antoine Leca
Le 17/07/2014 01:00, Marc Espie écrivit :
In article <lq6oh3$u40$, Olivier Miakinen <om+ wrote:
Le 16/07/2014 22:18, Benoit Izac a écrit :
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57. if (s[i] < 48 || s[i] > 57) {
Avec ce genre de commentaires en contexte, je ne pense pas que la compatibilité avec les chiffres indiens ou javanais soit vraiment le souci premier de l'OP ; mais négligeons ce détail.
Ou, un peu plus clair: if (s[i] < '0' || s[i] > '9') {
Ou encore (plus portable ?) :
if (! isdigit(s[i])) {
Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être, cela dit, un quart de poil de chouia de microseconde plus rapide ?
Ah si, c'est plus portable.
En l'occurrence et dans le cas _très_ particulier de isdigit(), non. La norme C oblige les chiffres de 0 à 9 à avoir des codets consécutifs, donc '9'-'0' vaut toujours 9 (même si '0' vaut 240 ou 0x660.) Et je ne pense pas que quelqu'un s'est amusé à porter un compilateur C (non ANSI) sur un encodage suffisament tordu pour que cette condition ne soit pas respectée. Ensuite, la norme oblige aussi à ce que isdigit() (et iswdigit()) ne testent _que_ les chiffres '0' à '9' ; donc même s'il est plausible de concevoir une implémentation qui testerait tous les caractères utilisés comme chiffres décimaux (par exemple U+0660 à U+0669, U+0966 à U+096F, etc.), ce ne serait pas conforme à la norme. Et en l'occurrence, je pense que l'utilisation de isdigit() dans une telle implémentation serait _moins_ portable, car normalement les programmes ne sont pas prévus pour traiter le cas où il existe plusieurs jeux de chiffres ; donc une telle implémentation est improbable.
Cela étant précisé, je suis d'accord sur le fond de ta remarque, l'utilisation des fonctions isxxx() est un gage de portabilité. Et cela incite les programmeurs à penser (comme ce devrait être acquis, mais la pratique démontre le contraire) que le codage des caractères est une fonction arbitraire entre signifiants et nombres, qu'il est abusif d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser des propriétés de l'un à l'autre (ici, l'opération < appliquée à des caractères, cela n'a pas de sens en général.)
Typiquement, en EBCDIC, les chiffres sont tout en haut. Suis pas sur que ca donne des choses tres coherentes avec des char signes (ce qui je te l'accorde serait un peu loufoque dans ce codage).
En EBCDIC, les char sont toujours non signés et sur 8 bits, sinon cela ne marche pas.
Note que par exemple, pour les lettres, les codes ne sont pas contigus...
Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte» entre N et O... ;-)
Antoine
Le 17/07/2014 01:00, Marc Espie écrivit :
In article <lq6oh3$u40$1@cabale.usenet-fr.net>,
Olivier Miakinen <om+news@miakinen.net> wrote:
Le 16/07/2014 22:18, Benoit Izac a écrit :
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57.
if (s[i] < 48 || s[i] > 57) {
Avec ce genre de commentaires en contexte, je ne pense pas que la
compatibilité avec les chiffres indiens ou javanais soit vraiment le
souci premier de l'OP ; mais négligeons ce détail.
Ou, un peu plus clair:
if (s[i] < '0' || s[i] > '9') {
Ou encore (plus portable ?) :
if (! isdigit(s[i])) {
Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être,
cela dit, un quart de poil de chouia de microseconde plus rapide ?
Ah si, c'est plus portable.
En l'occurrence et dans le cas _très_ particulier de isdigit(), non.
La norme C oblige les chiffres de 0 à 9 à avoir des codets consécutifs,
donc '9'-'0' vaut toujours 9 (même si '0' vaut 240 ou 0x660.) Et je ne
pense pas que quelqu'un s'est amusé à porter un compilateur C (non ANSI)
sur un encodage suffisament tordu pour que cette condition ne soit pas
respectée.
Ensuite, la norme oblige aussi à ce que isdigit() (et iswdigit()) ne
testent _que_ les chiffres '0' à '9' ; donc même s'il est plausible de
concevoir une implémentation qui testerait tous les caractères utilisés
comme chiffres décimaux (par exemple U+0660 à U+0669, U+0966 à U+096F,
etc.), ce ne serait pas conforme à la norme. Et en l'occurrence, je
pense que l'utilisation de isdigit() dans une telle implémentation
serait _moins_ portable, car normalement les programmes ne sont pas
prévus pour traiter le cas où il existe plusieurs jeux de chiffres ;
donc une telle implémentation est improbable.
Cela étant précisé, je suis d'accord sur le fond de ta remarque,
l'utilisation des fonctions isxxx() est un gage de portabilité.
Et cela incite les programmeurs à penser (comme ce devrait être acquis,
mais la pratique démontre le contraire) que le codage des caractères est
une fonction arbitraire entre signifiants et nombres, qu'il est abusif
d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser
des propriétés de l'un à l'autre (ici, l'opération < appliquée à des
caractères, cela n'a pas de sens en général.)
Typiquement, en EBCDIC, les chiffres sont tout en haut. Suis
pas sur que ca donne des choses tres coherentes avec des char signes
(ce qui je te l'accorde serait un peu loufoque dans ce codage).
En EBCDIC, les char sont toujours non signés et sur 8 bits, sinon cela
ne marche pas.
Note que par exemple, pour les lettres, les codes ne sont pas contigus...
Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre
de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte»
entre N et O... ;-)
In article <lq6oh3$u40$, Olivier Miakinen <om+ wrote:
Le 16/07/2014 22:18, Benoit Izac a écrit :
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57. if (s[i] < 48 || s[i] > 57) {
Avec ce genre de commentaires en contexte, je ne pense pas que la compatibilité avec les chiffres indiens ou javanais soit vraiment le souci premier de l'OP ; mais négligeons ce détail.
Ou, un peu plus clair: if (s[i] < '0' || s[i] > '9') {
Ou encore (plus portable ?) :
if (! isdigit(s[i])) {
Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être, cela dit, un quart de poil de chouia de microseconde plus rapide ?
Ah si, c'est plus portable.
En l'occurrence et dans le cas _très_ particulier de isdigit(), non. La norme C oblige les chiffres de 0 à 9 à avoir des codets consécutifs, donc '9'-'0' vaut toujours 9 (même si '0' vaut 240 ou 0x660.) Et je ne pense pas que quelqu'un s'est amusé à porter un compilateur C (non ANSI) sur un encodage suffisament tordu pour que cette condition ne soit pas respectée. Ensuite, la norme oblige aussi à ce que isdigit() (et iswdigit()) ne testent _que_ les chiffres '0' à '9' ; donc même s'il est plausible de concevoir une implémentation qui testerait tous les caractères utilisés comme chiffres décimaux (par exemple U+0660 à U+0669, U+0966 à U+096F, etc.), ce ne serait pas conforme à la norme. Et en l'occurrence, je pense que l'utilisation de isdigit() dans une telle implémentation serait _moins_ portable, car normalement les programmes ne sont pas prévus pour traiter le cas où il existe plusieurs jeux de chiffres ; donc une telle implémentation est improbable.
Cela étant précisé, je suis d'accord sur le fond de ta remarque, l'utilisation des fonctions isxxx() est un gage de portabilité. Et cela incite les programmeurs à penser (comme ce devrait être acquis, mais la pratique démontre le contraire) que le codage des caractères est une fonction arbitraire entre signifiants et nombres, qu'il est abusif d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser des propriétés de l'un à l'autre (ici, l'opération < appliquée à des caractères, cela n'a pas de sens en général.)
Typiquement, en EBCDIC, les chiffres sont tout en haut. Suis pas sur que ca donne des choses tres coherentes avec des char signes (ce qui je te l'accorde serait un peu loufoque dans ce codage).
En EBCDIC, les char sont toujours non signés et sur 8 bits, sinon cela ne marche pas.
Note que par exemple, pour les lettres, les codes ne sont pas contigus...
Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte» entre N et O... ;-)
Antoine
Antoine Leca
Le 17/07/2014 12:05, Olivier Miakinen a écrit :
Puisqu'on fait dans l'exotique, voici un cas où comparer la valeur avec '0' et '9' pourrait être préférable à isdigit() : ce serait un système dans lequel isdigit() retourne vrai lorsque la propriété Unicode 'Nombre, chiffre décimal' est vraie (p{Nd}) : <http://www.fileformat.info/info/unicode/category/Nd/list.htm>.
Non conforme, et de plus inutilisable dans la pratique : il y a beaucoup trop de code C qui utilise isdigit() et implicitement suppose que la variable examinée contient une valeur entre '0' et '0'+9
En dehors d'Unicode, il existe des codages (thaï et lao, par exemple) où il y a deux jeux de chiffres décimaux ; mais les données informatiques manipulées dans ces codages utilisent toujours un seul jeu de chiffres, qui peut être l'un ou l'autre, ce qui en C implique une recompilation du programme (en changeant la valeur de '0', un peu comme on peut parfois changer les char de signé à non signé). Utiliser un mélange des deux jeux n'a pas de sens, inutile donc de compliquer les implémentations.
Antoine
Le 17/07/2014 12:05, Olivier Miakinen a écrit :
Puisqu'on fait dans l'exotique, voici un cas où comparer la
valeur avec '0' et '9' pourrait être préférable à isdigit() :
ce serait un système dans lequel isdigit() retourne vrai
lorsque la propriété Unicode 'Nombre, chiffre décimal' est
vraie (p{Nd}) :
<http://www.fileformat.info/info/unicode/category/Nd/list.htm>.
Non conforme, et de plus inutilisable dans la pratique : il y a beaucoup
trop de code C qui utilise isdigit() et implicitement suppose que la
variable examinée contient une valeur entre '0' et '0'+9
En dehors d'Unicode, il existe des codages (thaï et lao, par exemple) où
il y a deux jeux de chiffres décimaux ; mais les données informatiques
manipulées dans ces codages utilisent toujours un seul jeu de chiffres,
qui peut être l'un ou l'autre, ce qui en C implique une recompilation du
programme (en changeant la valeur de '0', un peu comme on peut parfois
changer les char de signé à non signé). Utiliser un mélange des deux
jeux n'a pas de sens, inutile donc de compliquer les implémentations.
Puisqu'on fait dans l'exotique, voici un cas où comparer la valeur avec '0' et '9' pourrait être préférable à isdigit() : ce serait un système dans lequel isdigit() retourne vrai lorsque la propriété Unicode 'Nombre, chiffre décimal' est vraie (p{Nd}) : <http://www.fileformat.info/info/unicode/category/Nd/list.htm>.
Non conforme, et de plus inutilisable dans la pratique : il y a beaucoup trop de code C qui utilise isdigit() et implicitement suppose que la variable examinée contient une valeur entre '0' et '0'+9
En dehors d'Unicode, il existe des codages (thaï et lao, par exemple) où il y a deux jeux de chiffres décimaux ; mais les données informatiques manipulées dans ces codages utilisent toujours un seul jeu de chiffres, qui peut être l'un ou l'autre, ce qui en C implique une recompilation du programme (en changeant la valeur de '0', un peu comme on peut parfois changer les char de signé à non signé). Utiliser un mélange des deux jeux n'a pas de sens, inutile donc de compliquer les implémentations.
Antoine
Olivier Miakinen
Le 21/07/2014 12:47, Antoine Leca répondait à Marc Espie :
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57. if (s[i] < 48 || s[i] > 57) {
Avec ce genre de commentaires en contexte, je ne pense pas que la compatibilité avec les chiffres indiens ou javanais soit vraiment le souci premier de l'OP ;
... pas plus que la compatibilité avec EBCDIC.
mais négligeons ce détail.
Idem.
Ou, un peu plus clair: if (s[i] < '0' || s[i] > '9') {
Ou encore (plus portable ?) :
if (! isdigit(s[i])) {
Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être, cela dit, un quart de poil de chouia de microseconde plus rapide ?
Ah si, c'est plus portable.
En l'occurrence et dans le cas _très_ particulier de isdigit(), non.
Absolument.
[...]
Cela étant précisé, je suis d'accord sur le fond de ta remarque, l'utilisation des fonctions isxxx() est un gage de portabilité. Et cela incite les programmeurs à penser (comme ce devrait être acquis, mais la pratique démontre le contraire) que le codage des caractères est une fonction arbitraire entre signifiants et nombres, qu'il est abusif d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser des propriétés de l'un à l'autre (ici, l'opération < appliquée à des caractères, cela n'a pas de sens en général.)
Je précise que, malgré mon apparente contradiction, je suis d'accord aussi avec le fond à propos de l'utilisation des fonctions isxxx().
Je voulais juste rappeler qu'il est important, quand on programme, de comprendre ce que font les fonctions que l'on utilise. En particulier, depuis le temps que les ordinateurs savent gérer des jeux de caractères autres que US-ASCII (7 bits) ou EBCDIC, il ne me semble pas sage de supposer, sans l'avoir vérifié, que tout fonctionnera comme si les seuls chiffres étaient '0' à '9', les seules lettres 'A' à 'Z' et 'a' à 'z'.
[...]
Note que par exemple, pour les lettres, les codes ne sont pas contigus...
Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte» entre N et O... ;-)
Oui. Et d'une langue à une autre, l'ordre alphabétique ne sera pas forcément le même.
Le 21/07/2014 12:47, Antoine Leca répondait à Marc Espie :
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57.
if (s[i] < 48 || s[i] > 57) {
Avec ce genre de commentaires en contexte, je ne pense pas que la
compatibilité avec les chiffres indiens ou javanais soit vraiment le
souci premier de l'OP ;
... pas plus que la compatibilité avec EBCDIC.
mais négligeons ce détail.
Idem.
Ou, un peu plus clair:
if (s[i] < '0' || s[i] > '9') {
Ou encore (plus portable ?) :
if (! isdigit(s[i])) {
Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être,
cela dit, un quart de poil de chouia de microseconde plus rapide ?
Ah si, c'est plus portable.
En l'occurrence et dans le cas _très_ particulier de isdigit(), non.
Absolument.
[...]
Cela étant précisé, je suis d'accord sur le fond de ta remarque,
l'utilisation des fonctions isxxx() est un gage de portabilité.
Et cela incite les programmeurs à penser (comme ce devrait être acquis,
mais la pratique démontre le contraire) que le codage des caractères est
une fonction arbitraire entre signifiants et nombres, qu'il est abusif
d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser
des propriétés de l'un à l'autre (ici, l'opération < appliquée à des
caractères, cela n'a pas de sens en général.)
Je précise que, malgré mon apparente contradiction, je suis d'accord
aussi avec le fond à propos de l'utilisation des fonctions isxxx().
Je voulais juste rappeler qu'il est important, quand on programme, de
comprendre ce que font les fonctions que l'on utilise. En particulier,
depuis le temps que les ordinateurs savent gérer des jeux de caractères
autres que US-ASCII (7 bits) ou EBCDIC, il ne me semble pas sage de
supposer, sans l'avoir vérifié, que tout fonctionnera comme si les
seuls chiffres étaient '0' à '9', les seules lettres 'A' à 'Z' et 'a'
à 'z'.
[...]
Note que par exemple, pour les lettres, les codes ne sont pas contigus...
Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre
de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte»
entre N et O... ;-)
Oui. Et d'une langue à une autre, l'ordre alphabétique ne sera pas
forcément le même.
Le 21/07/2014 12:47, Antoine Leca répondait à Marc Espie :
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57. if (s[i] < 48 || s[i] > 57) {
Avec ce genre de commentaires en contexte, je ne pense pas que la compatibilité avec les chiffres indiens ou javanais soit vraiment le souci premier de l'OP ;
... pas plus que la compatibilité avec EBCDIC.
mais négligeons ce détail.
Idem.
Ou, un peu plus clair: if (s[i] < '0' || s[i] > '9') {
Ou encore (plus portable ?) :
if (! isdigit(s[i])) {
Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être, cela dit, un quart de poil de chouia de microseconde plus rapide ?
Ah si, c'est plus portable.
En l'occurrence et dans le cas _très_ particulier de isdigit(), non.
Absolument.
[...]
Cela étant précisé, je suis d'accord sur le fond de ta remarque, l'utilisation des fonctions isxxx() est un gage de portabilité. Et cela incite les programmeurs à penser (comme ce devrait être acquis, mais la pratique démontre le contraire) que le codage des caractères est une fonction arbitraire entre signifiants et nombres, qu'il est abusif d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser des propriétés de l'un à l'autre (ici, l'opération < appliquée à des caractères, cela n'a pas de sens en général.)
Je précise que, malgré mon apparente contradiction, je suis d'accord aussi avec le fond à propos de l'utilisation des fonctions isxxx().
Je voulais juste rappeler qu'il est important, quand on programme, de comprendre ce que font les fonctions que l'on utilise. En particulier, depuis le temps que les ordinateurs savent gérer des jeux de caractères autres que US-ASCII (7 bits) ou EBCDIC, il ne me semble pas sage de supposer, sans l'avoir vérifié, que tout fonctionnera comme si les seuls chiffres étaient '0' à '9', les seules lettres 'A' à 'Z' et 'a' à 'z'.
[...]
Note que par exemple, pour les lettres, les codes ne sont pas contigus...
Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte» entre N et O... ;-)
Oui. Et d'une langue à une autre, l'ordre alphabétique ne sera pas forcément le même.
Francois Lafont
Bonsoir à tous,
J'ai tenté de modifier le programme afin que l'écriture dans le buffer passe par une structure comme me l'avait conseillé Xavier. J'ai laissé tomber le coup des « assert » car dans la fonction write_data, il y a déjà plein de tests if pour éviter les écritures en dehors du buffer, du coup ça me semblait faire totalement double emploi (mais j'ai peut-être tort).
J'ai aussi fait en sorte que la sortie du programme se termine pas un n si ce n'est pas déjà le cas du buffer récupéré par curl (modification faite à la fin du code source).
Je laisse le code ci-dessous. Si jamais vous avez des remarques, je suis toujours preneur bien sûr. Quoi qu'il en soit, l'exercice a été très instructif pour moi et donc merci à tous pour toute l'aide déjà apportée dans ce fil.
int is_filled ( Buffer * buffer ) { if ( buffer->index >= buffer->capa - 1 ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return 1; } else { return 0; } }
// If this function does not return segsize, it will signal an error // condition to the library. This will cause the transfer to get aborted // and the libcurl function used will return CURLE_WRITE_ERROR. size_t write_data ( void *buffer, size_t size, size_t nmemb, void *userp ) { // The size (in bytes) of the received buffer. int segsize = size * nmemb;
// In this function, userp refers to a Buffer structure. Buffer *userbuf = userp;
if ( userbuf->index >= userbuf->capa - 1 ) { // userbuf is already filled. return 0; }
// Get truncated_segsize. int truncated_segsize = segsize;
if ( truncated_segsize > 0 ) { memcpy ( &( userbuf->wr_buf )[userbuf->index], buffer, truncated_segsize ); userbuf->index += truncated_segsize; // After each change, userbuf->wr_buf must be a valid string. ( userbuf->wr_buf )[userbuf->index] = ' '; return truncated_segsize; } else { userbuf->index = userbuf->capa - 1; return 0; } }
int isPositiveInteger ( const char s[] ) { if ( s == NULL || *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
int main ( int argc, char *argv[] ) { if ( argc < 4 ) { printf ( "Sorry, bad syntax. You must apply at least 3 arguments.n" ); printf ( "%s <timeout> <url> <args>...n", argv[0] ); return UNKNOWN; }
if ( !isPositiveInteger ( argv[1] ) ) { printf ( "Sorry, bad syntax. The first argument must be a positive" ); printf ( " integer (it is a timeout in seconds).n" ); return UNKNOWN; }
if ( !curl ) { printf ( "Sorry, could not init curl.n" ); return UNKNOWN; }
// Construction of the post variable, a string with this form: // token1=<urlencoded data1>&token2=<urlencoded data2>&... char post[MAX_POST_IN_BYTES];
post[0] = ' ';
int token_num = 1; char *urlencoded_str = NULL; int i = 0;
for ( i = 3; i < argc; i++ ) { if ( token_num > 999 ) { printf ( "Sorry, the limit number (999) of POST variables is exceeded.n" ); curl_easy_cleanup ( curl ); return UNKNOWN; }
// 10 is the max length of the string "token<num>=&". // The maximum is reached with "token999=&". int temp_size = 10 + strlen ( urlencoded_str ) + 1; char temp[temp_size];
// Allow curl to perform the action. CURLcode ret;
ret = curl_easy_perform ( curl );
if ( ret ) { if ( ret != CURLE_WRITE_ERROR || !is_filled ( &buf ) ) { curl_easy_cleanup ( curl ); printf ( "Sorry, exit value of curl_easy_perform is %d.", ret );
switch ( ret ) { case CURLE_COULDNT_RESOLVE_HOST: printf ( " Could not resolve the host address.n" ); break;
case CURLE_OPERATION_TIMEDOUT: printf ( " Operation timeout.n" ); break;
J'ai tenté de modifier le programme afin que l'écriture
dans le buffer passe par une structure comme me l'avait
conseillé Xavier. J'ai laissé tomber le coup des « assert »
car dans la fonction write_data, il y a déjà plein de
tests if pour éviter les écritures en dehors du buffer,
du coup ça me semblait faire totalement double emploi
(mais j'ai peut-être tort).
J'ai aussi fait en sorte que la sortie du programme
se termine pas un n si ce n'est pas déjà le cas du
buffer récupéré par curl (modification faite à la fin
du code source).
Je laisse le code ci-dessous. Si jamais vous avez des
remarques, je suis toujours preneur bien sûr. Quoi qu'il
en soit, l'exercice a été très instructif pour moi et
donc merci à tous pour toute l'aide déjà apportée dans
ce fil.
int is_filled ( Buffer * buffer )
{
if ( buffer->index >= buffer->capa - 1 )
{
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ' '
// and can be used.
return 1;
}
else
{
return 0;
}
}
// If this function does not return segsize, it will signal an error
// condition to the library. This will cause the transfer to get aborted
// and the libcurl function used will return CURLE_WRITE_ERROR.
size_t write_data ( void *buffer, size_t size, size_t nmemb, void *userp )
{
// The size (in bytes) of the received buffer.
int segsize = size * nmemb;
// In this function, userp refers to a Buffer structure.
Buffer *userbuf = userp;
if ( userbuf->index >= userbuf->capa - 1 )
{
// userbuf is already filled.
return 0;
}
// Get truncated_segsize.
int truncated_segsize = segsize;
if ( truncated_segsize > 0 )
{
memcpy ( &( userbuf->wr_buf )[userbuf->index], buffer,
truncated_segsize );
userbuf->index += truncated_segsize;
// After each change, userbuf->wr_buf must be a valid string.
( userbuf->wr_buf )[userbuf->index] = ' ';
return truncated_segsize;
}
else
{
userbuf->index = userbuf->capa - 1;
return 0;
}
}
int isPositiveInteger ( const char s[] )
{
if ( s == NULL || *s == ' ' )
{
return 0;
}
size_t i;
for ( i = 0; s[i] != ' '; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}
int main ( int argc, char *argv[] )
{
if ( argc < 4 )
{
printf ( "Sorry, bad syntax. You must apply at least 3 arguments.n" );
printf ( "%s <timeout> <url> <args>...n", argv[0] );
return UNKNOWN;
}
if ( !isPositiveInteger ( argv[1] ) )
{
printf ( "Sorry, bad syntax. The first argument must be a positive" );
printf ( " integer (it is a timeout in seconds).n" );
return UNKNOWN;
}
if ( !curl )
{
printf ( "Sorry, could not init curl.n" );
return UNKNOWN;
}
// Construction of the post variable, a string with this form:
// token1=<urlencoded data1>&token2=<urlencoded data2>&...
char post[MAX_POST_IN_BYTES];
post[0] = ' ';
int token_num = 1;
char *urlencoded_str = NULL;
int i = 0;
for ( i = 3; i < argc; i++ )
{
if ( token_num > 999 )
{
printf
( "Sorry, the limit number (999) of POST variables is exceeded.n" );
curl_easy_cleanup ( curl );
return UNKNOWN;
}
// 10 is the max length of the string "token<num>=&".
// The maximum is reached with "token999=&".
int temp_size = 10 + strlen ( urlencoded_str ) + 1;
char temp[temp_size];
// Allow curl to perform the action.
CURLcode ret;
ret = curl_easy_perform ( curl );
if ( ret )
{
if ( ret != CURLE_WRITE_ERROR || !is_filled ( &buf ) )
{
curl_easy_cleanup ( curl );
printf ( "Sorry, exit value of curl_easy_perform is %d.", ret );
switch ( ret )
{
case CURLE_COULDNT_RESOLVE_HOST:
printf ( " Could not resolve the host address.n" );
break;
case CURLE_OPERATION_TIMEDOUT:
printf ( " Operation timeout.n" );
break;
J'ai tenté de modifier le programme afin que l'écriture dans le buffer passe par une structure comme me l'avait conseillé Xavier. J'ai laissé tomber le coup des « assert » car dans la fonction write_data, il y a déjà plein de tests if pour éviter les écritures en dehors du buffer, du coup ça me semblait faire totalement double emploi (mais j'ai peut-être tort).
J'ai aussi fait en sorte que la sortie du programme se termine pas un n si ce n'est pas déjà le cas du buffer récupéré par curl (modification faite à la fin du code source).
Je laisse le code ci-dessous. Si jamais vous avez des remarques, je suis toujours preneur bien sûr. Quoi qu'il en soit, l'exercice a été très instructif pour moi et donc merci à tous pour toute l'aide déjà apportée dans ce fil.
int is_filled ( Buffer * buffer ) { if ( buffer->index >= buffer->capa - 1 ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return 1; } else { return 0; } }
// If this function does not return segsize, it will signal an error // condition to the library. This will cause the transfer to get aborted // and the libcurl function used will return CURLE_WRITE_ERROR. size_t write_data ( void *buffer, size_t size, size_t nmemb, void *userp ) { // The size (in bytes) of the received buffer. int segsize = size * nmemb;
// In this function, userp refers to a Buffer structure. Buffer *userbuf = userp;
if ( userbuf->index >= userbuf->capa - 1 ) { // userbuf is already filled. return 0; }
// Get truncated_segsize. int truncated_segsize = segsize;
if ( truncated_segsize > 0 ) { memcpy ( &( userbuf->wr_buf )[userbuf->index], buffer, truncated_segsize ); userbuf->index += truncated_segsize; // After each change, userbuf->wr_buf must be a valid string. ( userbuf->wr_buf )[userbuf->index] = ' '; return truncated_segsize; } else { userbuf->index = userbuf->capa - 1; return 0; } }
int isPositiveInteger ( const char s[] ) { if ( s == NULL || *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
int main ( int argc, char *argv[] ) { if ( argc < 4 ) { printf ( "Sorry, bad syntax. You must apply at least 3 arguments.n" ); printf ( "%s <timeout> <url> <args>...n", argv[0] ); return UNKNOWN; }
if ( !isPositiveInteger ( argv[1] ) ) { printf ( "Sorry, bad syntax. The first argument must be a positive" ); printf ( " integer (it is a timeout in seconds).n" ); return UNKNOWN; }
if ( !curl ) { printf ( "Sorry, could not init curl.n" ); return UNKNOWN; }
// Construction of the post variable, a string with this form: // token1=<urlencoded data1>&token2=<urlencoded data2>&... char post[MAX_POST_IN_BYTES];
post[0] = ' ';
int token_num = 1; char *urlencoded_str = NULL; int i = 0;
for ( i = 3; i < argc; i++ ) { if ( token_num > 999 ) { printf ( "Sorry, the limit number (999) of POST variables is exceeded.n" ); curl_easy_cleanup ( curl ); return UNKNOWN; }
// 10 is the max length of the string "token<num>=&". // The maximum is reached with "token999=&". int temp_size = 10 + strlen ( urlencoded_str ) + 1; char temp[temp_size];
// Allow curl to perform the action. CURLcode ret;
ret = curl_easy_perform ( curl );
if ( ret ) { if ( ret != CURLE_WRITE_ERROR || !is_filled ( &buf ) ) { curl_easy_cleanup ( curl ); printf ( "Sorry, exit value of curl_easy_perform is %d.", ret );
switch ( ret ) { case CURLE_COULDNT_RESOLVE_HOST: printf ( " Could not resolve the host address.n" ); break;
case CURLE_OPERATION_TIMEDOUT: printf ( " Operation timeout.n" ); break;
Juste histoire de pinailler: buffer->capa = sizeof(buffer->wr_buf); puisque la taille du tableau est déjà définir dans la structure.
int is_filled ( Buffer * buffer ) { if ( buffer->index >= buffer->capa - 1 ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return 1; } else { return 0; } }
Tout d'abord is_filled n'a pas besoin d'être visible par d'autres fichiers C, donc on peut coller la fonction en "static", ce qui est plus propre (cela ne change strictement rien sinon) car cela limite la visiblilité
Sinon j'aurais collé quelque chose de plus compact (et finalement plus lisible):
static int is_filled ( Buffer * buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
Voir (soyons fous):
static int is_filled ( const Buffer *const buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
// In this function, userp refers to a Buffer structure. Buffer *userbuf = userp;
Buffer *const userbuf = userp;
int truncated_segsize = segsize;
const int truncated_segsize = segsize;
(oui je sais, je pinaille!)
int isPositiveInteger ( const char s[] )
static int isPositiveInteger ...
{ if ( s == NULL || *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
Pinaillage: j'aurais considéré NULL comme une erreur de programmation (l'entrée n'est pas supposée être nulle, et signale un souci de logique de code) et supprimé le test au début du coup
assert(s != NULL); for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return i != 0; }
Bon, pinaillage pinaillage :)
On 07/22/2014 02:32 AM, Francois Lafont wrote:
buffer->capa = MAX_BUF_IN_BYTES;
Juste histoire de pinailler:
buffer->capa = sizeof(buffer->wr_buf);
puisque la taille du tableau est déjà définir dans la structure.
int is_filled ( Buffer * buffer )
{
if ( buffer->index >= buffer->capa - 1 )
{
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ' '
// and can be used.
return 1;
}
else
{
return 0;
}
}
Tout d'abord is_filled n'a pas besoin d'être visible par d'autres
fichiers C, donc on peut coller la fonction en "static", ce qui est plus
propre (cela ne change strictement rien sinon) car cela limite la
visiblilité
Sinon j'aurais collé quelque chose de plus compact (et finalement plus
lisible):
static int is_filled ( Buffer * buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ' '
// and can be used.
return buffer->index >= buffer->capa - 1;
}
Voir (soyons fous):
static int is_filled ( const Buffer *const buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ' '
// and can be used.
return buffer->index >= buffer->capa - 1;
}
// In this function, userp refers to a Buffer structure.
Buffer *userbuf = userp;
Buffer *const userbuf = userp;
int truncated_segsize = segsize;
const int truncated_segsize = segsize;
(oui je sais, je pinaille!)
int isPositiveInteger ( const char s[] )
static int isPositiveInteger ...
{
if ( s == NULL || *s == ' ' )
{
return 0;
}
size_t i;
for ( i = 0; s[i] != ' '; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}
Pinaillage: j'aurais considéré NULL comme une erreur de programmation
(l'entrée n'est pas supposée être nulle, et signale un souci de logique
de code) et supprimé le test au début du coup
assert(s != NULL);
for ( i = 0; s[i] != ' '; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return i != 0;
}
Juste histoire de pinailler: buffer->capa = sizeof(buffer->wr_buf); puisque la taille du tableau est déjà définir dans la structure.
int is_filled ( Buffer * buffer ) { if ( buffer->index >= buffer->capa - 1 ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return 1; } else { return 0; } }
Tout d'abord is_filled n'a pas besoin d'être visible par d'autres fichiers C, donc on peut coller la fonction en "static", ce qui est plus propre (cela ne change strictement rien sinon) car cela limite la visiblilité
Sinon j'aurais collé quelque chose de plus compact (et finalement plus lisible):
static int is_filled ( Buffer * buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
Voir (soyons fous):
static int is_filled ( const Buffer *const buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
// In this function, userp refers to a Buffer structure. Buffer *userbuf = userp;
Buffer *const userbuf = userp;
int truncated_segsize = segsize;
const int truncated_segsize = segsize;
(oui je sais, je pinaille!)
int isPositiveInteger ( const char s[] )
static int isPositiveInteger ...
{ if ( s == NULL || *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
Pinaillage: j'aurais considéré NULL comme une erreur de programmation (l'entrée n'est pas supposée être nulle, et signale un souci de logique de code) et supprimé le test au début du coup
assert(s != NULL); for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return i != 0; }
Bon, pinaillage pinaillage :)
Francois Lafont
Bonjour,
Le 22/07/2014 08:08, Xavier Roche a écrit :
Juste histoire de pinailler: [...]
Pas de souci avec le pinaillage. Ton post m'a permis au passage d'apprendre la différence entre :
const int *p = &a; int *const p = &a; const int *const p = &a;
Je n'avais pas fait attention jusque là (explications trouvées assez rapidement sur le web).
buffer->capa = sizeof(buffer->wr_buf); puisque la taille du tableau est déjà définir dans la structure.
int is_filled ( Buffer * buffer ) { if ( buffer->index >= buffer->capa - 1 ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return 1; } else { return 0; } }
Tout d'abord is_filled n'a pas besoin d'être visible par d'autres fichiers C, donc on peut coller la fonction en "static", ce qui est plus propre (cela ne change strictement rien sinon) car cela limite la visiblilité
Sinon j'aurais collé quelque chose de plus compact (et finalement plus lisible):
static int is_filled ( Buffer * buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
Voir (soyons fous):
static int is_filled ( const Buffer *const buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
Ok, effectivement, quelque part c'est plus lisible.
// In this function, userp refers to a Buffer structure. Buffer *userbuf = userp;
Buffer *const userbuf = userp;
Ok pour tout ça.
int truncated_segsize = segsize;
const int truncated_segsize = segsize;
Celui-là n'est pas possible car la variable est (éventuellement) modifiée juste après. Tu étais sans doute emporté dans ta frénésie des « const ». ;)
int isPositiveInteger ( const char s[] )
static int isPositiveInteger ...
{ if ( s == NULL || *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
Pinaillage: j'aurais considéré NULL comme une erreur de programmation (l'entrée n'est pas supposée être nulle, et signale un souci de logique de code) et supprimé le test au début du coup
assert(s != NULL); for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return i != 0; }
Là aussi, je suis convaincu, du coup j'ai mis le assert. En revanche, j'avoue que j'ai du mal avec la siouxerie « return i != 0; ». J'ai bien compris que ça permettait de gérer le cas où « *s == ' ' » et que c'est plus concis mais je sais que dans 1 semaine j'arriverai plus à m'en rappeler (j'ai pas trop l'habitude du côté "tout expression" du langage C, même si ça permet des raccourcis). Du coup, je me suis contenté de ça :
static int isPositiveInteger ( const char s[] ) { assert ( s != NULL );
if ( *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
Merci encore pour ton aide Xavier.
Le code est ici : https://github.com/flaf/miscellaneous/blob/master/puppet/production/modules/apache_poller/files/sp_check.c (Un lien est peut-être préférable à un copier-coller...)
-- François Lafont
Bonjour,
Le 22/07/2014 08:08, Xavier Roche a écrit :
Juste histoire de pinailler: [...]
Pas de souci avec le pinaillage. Ton post m'a
permis au passage d'apprendre la différence entre :
const int *p = &a;
int *const p = &a;
const int *const p = &a;
Je n'avais pas fait attention jusque là (explications
trouvées assez rapidement sur le web).
buffer->capa = sizeof(buffer->wr_buf);
puisque la taille du tableau est déjà définir dans la structure.
int is_filled ( Buffer * buffer )
{
if ( buffer->index >= buffer->capa - 1 )
{
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ' '
// and can be used.
return 1;
}
else
{
return 0;
}
}
Tout d'abord is_filled n'a pas besoin d'être visible par d'autres fichiers C, donc on peut coller la fonction en "static", ce qui est plus propre (cela ne change strictement rien sinon) car cela limite la visiblilité
Sinon j'aurais collé quelque chose de plus compact (et finalement plus lisible):
static int is_filled ( Buffer * buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ' '
// and can be used.
return buffer->index >= buffer->capa - 1;
}
Voir (soyons fous):
static int is_filled ( const Buffer *const buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ' '
// and can be used.
return buffer->index >= buffer->capa - 1;
}
Ok, effectivement, quelque part c'est plus lisible.
// In this function, userp refers to a Buffer structure.
Buffer *userbuf = userp;
Buffer *const userbuf = userp;
Ok pour tout ça.
int truncated_segsize = segsize;
const int truncated_segsize = segsize;
Celui-là n'est pas possible car la variable est
(éventuellement) modifiée juste après. Tu étais
sans doute emporté dans ta frénésie des « const ». ;)
int isPositiveInteger ( const char s[] )
static int isPositiveInteger ...
{
if ( s == NULL || *s == ' ' )
{
return 0;
}
size_t i;
for ( i = 0; s[i] != ' '; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}
Pinaillage: j'aurais considéré NULL comme une erreur de programmation (l'entrée n'est pas supposée être nulle, et signale un souci de logique de code) et supprimé le test au début du coup
assert(s != NULL);
for ( i = 0; s[i] != ' '; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return i != 0;
}
Là aussi, je suis convaincu, du coup j'ai mis le assert.
En revanche, j'avoue que j'ai du mal avec la siouxerie
« return i != 0; ». J'ai bien compris que ça permettait
de gérer le cas où « *s == ' ' » et que c'est plus concis
mais je sais que dans 1 semaine j'arriverai plus à m'en
rappeler (j'ai pas trop l'habitude du côté "tout expression"
du langage C, même si ça permet des raccourcis). Du coup, je
me suis contenté de ça :
static int isPositiveInteger ( const char s[] )
{
assert ( s != NULL );
if ( *s == ' ' )
{
return 0;
}
size_t i;
for ( i = 0; s[i] != ' '; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}
Merci encore pour ton aide Xavier.
Le code est ici :
https://github.com/flaf/miscellaneous/blob/master/puppet/production/modules/apache_poller/files/sp_check.c
(Un lien est peut-être préférable à un copier-coller...)
Pas de souci avec le pinaillage. Ton post m'a permis au passage d'apprendre la différence entre :
const int *p = &a; int *const p = &a; const int *const p = &a;
Je n'avais pas fait attention jusque là (explications trouvées assez rapidement sur le web).
buffer->capa = sizeof(buffer->wr_buf); puisque la taille du tableau est déjà définir dans la structure.
int is_filled ( Buffer * buffer ) { if ( buffer->index >= buffer->capa - 1 ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return 1; } else { return 0; } }
Tout d'abord is_filled n'a pas besoin d'être visible par d'autres fichiers C, donc on peut coller la fonction en "static", ce qui est plus propre (cela ne change strictement rien sinon) car cela limite la visiblilité
Sinon j'aurais collé quelque chose de plus compact (et finalement plus lisible):
static int is_filled ( Buffer * buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
Voir (soyons fous):
static int is_filled ( const Buffer *const buffer ) { // Because (buffer->wr_buf)[buffer->cap - 1] must contain ' ' // and can be used. return buffer->index >= buffer->capa - 1; }
Ok, effectivement, quelque part c'est plus lisible.
// In this function, userp refers to a Buffer structure. Buffer *userbuf = userp;
Buffer *const userbuf = userp;
Ok pour tout ça.
int truncated_segsize = segsize;
const int truncated_segsize = segsize;
Celui-là n'est pas possible car la variable est (éventuellement) modifiée juste après. Tu étais sans doute emporté dans ta frénésie des « const ». ;)
int isPositiveInteger ( const char s[] )
static int isPositiveInteger ...
{ if ( s == NULL || *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
Pinaillage: j'aurais considéré NULL comme une erreur de programmation (l'entrée n'est pas supposée être nulle, et signale un souci de logique de code) et supprimé le test au début du coup
assert(s != NULL); for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return i != 0; }
Là aussi, je suis convaincu, du coup j'ai mis le assert. En revanche, j'avoue que j'ai du mal avec la siouxerie « return i != 0; ». J'ai bien compris que ça permettait de gérer le cas où « *s == ' ' » et que c'est plus concis mais je sais que dans 1 semaine j'arriverai plus à m'en rappeler (j'ai pas trop l'habitude du côté "tout expression" du langage C, même si ça permet des raccourcis). Du coup, je me suis contenté de ça :
static int isPositiveInteger ( const char s[] ) { assert ( s != NULL );
if ( *s == ' ' ) { return 0; }
size_t i;
for ( i = 0; s[i] != ' '; i++ ) { // Test if all the characters are digits. if ( !isdigit ( s[i] ) ) { return 0; } } return 1; }
Merci encore pour ton aide Xavier.
Le code est ici : https://github.com/flaf/miscellaneous/blob/master/puppet/production/modules/apache_poller/files/sp_check.c (Un lien est peut-être préférable à un copier-coller...)