Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Petit programme en C, avez-vous des remarques etc. ?

Aucune réponse
Avatar
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 :

./mon-programme 10 localhost/cgi/machin.pl xxx yyy zzz

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 :

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);

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

-------------------------------------------------------
/* With Debian Wheezy:
*
* sudo apt-get install libcurl4-openssl-dev
* gcc -lcurl -std=c99 -o curl-launcher.exe curl-launcher.c
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <curl/curl.h>

#define MAX_BUF 65536
#define MAX_POST_LENGTH 4096
#define OK 0
#define WARNING 1
#define CRITICAL 2
#define UNKNOWN 3

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);
int isPositiveInteger(const char s[]);

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'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;
}

//printf("C: token%d: [%s]\n", token_num, argv[i]);

urlencoded_str = curl_easy_escape(curl, argv[i], 0);

// 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);

char wr_buf[MAX_BUF + 1] = { 0 };

curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, 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;

default:
printf("\n");
break;

}

return UNKNOWN;
}

curl_easy_cleanup(curl);

/*
printf("----------------------------------\n");
printf("%s", wr_buf);
printf("----------------------------------\n");
*/

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;
}
}

return 1;
}
-------------------------------------------------------

10 réponses

3 4 5 6 7
Avatar
Francois Lafont
Le 22/07/2014 20:08, Olivier Miakinen a écrit :

le (my_abort_function(...), 0) fonctionne avec une fonction qui retourne
void, comme :
void my_abort_function(const char*, const char*, const char*);



Et donc, cela permet d'utiliser cette expression comme deuxième
paramètre d'un || puisque maintenant la valeur de retour est 0 et
non pas void.



Ah ok, je comprends maintenant. On utilise la séquence
« my_abort_function(...), 0 » parce que sans celle-ci,
on aurait :

(exp) || (my_abort_function(...))

qui n'est pas correct dans le cas où la fonction retourne
void, les expressions à gauche et à droite de || doivent
renvoyer une valeur (ce qui paraît logique dans un test).
Avec :

(exp) || (my_abort_function(...),0)

je suis sûr que c'est correct (les 2 expressions à gauche
et à droite de || ont une valeur).

Merci.

--
François Lafont
Avatar
Olivier Miakinen
Le 22/07/2014 23:39, Francois Lafont a écrit :

Ah ok, je comprends maintenant. On utilise la séquence
« my_abort_function(...), 0 » parce que sans celle-ci,
on aurait :

(exp) || (my_abort_function(...))

qui n'est pas correct dans le cas où la fonction retourne
void, les expressions à gauche et à droite de || doivent
renvoyer une valeur (ce qui paraît logique dans un test).



C'est exactement ça.

Avec :

(exp) || (my_abort_function(...),0)

je suis sûr que c'est correct (les 2 expressions à gauche
et à droite de || ont une valeur).



Voilà.
Avatar
espie
In article <53cee14c$0$2301$,
Samuel DEVULDER <samuel-dot-devulder-at-laposte-dot-net> wrote:
Le 22/07/2014 22:55, Erwan David a écrit :

Une architecture avec size_t sur 64 bits et int sur 32 ?



Je ne crois pas alors que size_t serait compatible avec le "unsigned
int" (entier non signé) que la norme indique.



Paragraphe de la norme ?

Les int sur 32 bits, les size_t sur 64 bits, ca arrive. C'est meme
extremement frequents.

Une archi LP64 va typiquement avoir int sur 32 bits et size_t sur 64 bits...
Avatar
Samuel DEVULDER
Le 23/07/2014 05:50, Marc Espie a écrit :

Paragraphe de la norme ?



Cf supra (<53cec6d9$0$2282$).

Le document parle d'entier non signé ce que je traduis par "unsigned
int" et indique en outre que SIZE_MAX vaut 65535 minimum (p259
paragraphe7.18.3).

Une archi LP64 va typiquement avoir int sur 32 bits et size_t sur 64


bits...

et en ILP64 int vaut 64bits. Les modèles de calculs il y en a des tas.
ILP64 est le plus homogène et a la réputation d'introduire très peu de
modifs dans le code lors du passage d'un modèle 32bits à un modèle 64bits.

Bon sinon en cas de doute utiliser "!!i", ou "(i?1:0)". Dans le fond les
pinaillages sur les taille de données sont importants mais je voulais
avant tout porter l'attention sur le code efficace (on démarrait avec
une serie de if() {return 1;}).

Si on parle d'optimisation, ce qui serait utile ici serait de pouvoir
indiquer au compilo que la seule chose qui est intéressante dans la
valeur de retour de la fonction isPositiveInteger() est le statut
nul/non-nul du résultat sans imposer précisément les valeurs 0 et 1.

Typiquement sur l'architecture cible le compilo pourrait optimiser de la
façon qui juge le mieux pour convertir un size_t en valeur entière
indiquant nul/non-nul. Il pourrait ainsi choisir de faire "(i>>32)|i" si
une instruction ASM faisait cela plus rapidement que le saut engendré
par "(i?1:0)".

a+

sam.
Avatar
Samuel DEVULDER
Le 22/07/2014 08:08, Xavier Roche a écrit :

const int segsize = size * nmemb;
Buffer *const userbuf = userp;
const int truncated_segsize = segsize;

(oui je sais, je pinaille!)



En fait plus de 90% des variables sont constantes dans une fonction C
typique. Mettre const est très utile pour éviter les bêtises, mais on a
alors très rapidement trop de const partout dans le code source.

Pour être plus lisible, on devrait presque dire que le const est
implicite et que pour déclarer une vraie variable il faut utiliser un
mot clef spécial, "nonconst", ou mieux "var".

Ca donnerait:

for(var i=0; s[i]; ++i) { ... }

Ah ben on dirait presque du javascript ou du C# pour le coup. Ca alors! :)

a+

sam.
Avatar
Olivier Miakinen
Le 23/07/2014 09:03, Samuel DEVULDER a écrit :

En fait plus de 90% des variables sont constantes dans une fonction C
typique.



Tu trouves ? Et donc, moins d'une variable sur dix serait véritablement
variable ? Permets-moi d'en douter.

[...]

for(var i=0; s[i]; ++i) { ... }

Ah ben on dirait presque du javascript ou du C# pour le coup. Ca alors! :)



Ah, c'était un troll ? Désolé, j'ai marché dedans. ;-)
Avatar
Samuel DEVULDER
Le 23/07/2014 09:13, Olivier Miakinen a écrit :
Le 23/07/2014 09:03, Samuel DEVULDER a écrit :

En fait plus de 90% des variables sont constantes dans une fonction C
typique.



Tu trouves ? Et donc, moins d'une variable sur dix serait véritablement
variable ? Permets-moi d'en douter.



Oui à la louche. Bon 90% est peut être trop élevé, mais disons 60% (cf
les modifs de Xavier Roche). En gros seules les variables de boucles et
2 ou 3 auxiliaires de calcul à coté sont vraiment des variables. Les
autres ne sont que des prêtes-noms à des raccourcis pour des expression
longues et complexe qu'on a pas envie de répéter 25 fois et qui servent
à rendre le code plus lisible. On indique que ces valeurs là ont une
importance car elles ont leur "variables" dédiées. Mais ces variablels
ne changent jamais de valeurs. Ce sont des constantes dans les faits.
Les choses qui varient réellement dans un programme sont les valeurs
pointées par un pointeur(!).... constant (cf cases de tableaux), ou les
champs des structures. Ceux là oui changent souvent, mais beaucoup de
variable "auto" sont souvent des const qui ont oubliés de l'être. C'est
ce que montre typiquement les corrections de Xavier auquel je répondais.

Ah, c'était un troll ? Désolé, j'ai marché dedans. ;-)



Désolé :| C'était pas destiné à troller, mais simplement à faire réagir.
Il y a des langages ou les vars sont exceptionnelles, et ils ne s'en
portent pas plus mal (et je ne parle pas des langages fonctionnels).

sam.
Avatar
espie
In article <53cf6a4c$0$2205$,
Samuel DEVULDER <samuel-dot-devulder-at-laposte-dot-net> wrote:
Il y a des langages ou les vars sont exceptionnelles, et ils ne s'en
portent pas plus mal (et je ne parle pas des langages fonctionnels).



Dans la forme interne moderne des compilos, il n'y a pas de variables non
const. :)

(static single assignment, http://en.wikipedia.org/wiki/Static_single_assignment_form)
Avatar
Antoine Leca
Le 22/07/2014 21:25, Samuel DEVULDER écrivit :
Le 22/07/2014 17:52, Antoine Leca a écrit :

Un compromis pourrait être

return i != 0 ? 1 : 0;



Question idiote (quoique), pourquoi pas plutôt

return i ? 1 : 0;



Pour être un poil plus clair sur le cas dégénéré que ce test traite (et
faire plus genre Pascal).


Quitte à faire du C, on aurait pu tout aussi bien aussi écrire pour un i
entier:
return !!i;



Oui. C'est certainement plus commun que la forme «plus genre Pascal»
return i != 0
Mais bon, je comprend aussi la réaction de François...

[...] mais surtout j'en connais qui aurait écrit directement
return i;
pour exactement la même sémantique (mais pas la même valeur bien entendu).



Oui oui oui ! Et beaucoup plus dans l'esprit de C !


J'ajoute que j'ai des doutes qu'en l'absence d'un type boolean formel,
un compilo sache optimiser l'opérateur ternaire pour obtenir la dernière
forme.



Je voulais dire le contraire. En général, les machines n'ont pas
d'opérations qui stocke 0 ou 1 en fonction du résultat d'un test, donc
le compilateur (au niveau des graphes) transforme l'expression
(i != 0)
en
(i != 0) ? 1 : 0
pour respecter la norme qui spécifie la valeur de l'opération !
Ensuite, un compilateur qui optimise globalement va peut-être intégrer
la fonction en inline (et obtenir exactement l'effet d'un type booléen
formel), et détecter que le résultat n'est utilisé que pour être testé;
mais ce genre de compilateur sait aussi réduire l'opérateur ternaire,
donc il n'y aura pas de différence entre les deux formes.
Avatar
Samuel DEVULDER
Le 23/07/2014 11:38, Antoine Leca a écrit :
Le 22/07/2014 21:25, Samuel DEVULDER écrivit :
Le 22/07/2014 17:52, Antoine Leca a écrit :

Un compromis pourrait être

return i != 0 ? 1 : 0;



Question idiote (quoique), pourquoi pas plutôt

return i ? 1 : 0;



Pour être un poil plus clair sur le cas dégénéré que ce test traite (et
faire plus genre Pascal).



A noter: certaine normes industrielles (MISRA 2004 http://goo.gl/30FKuB
par exemple) déconseillent fortement d'utiliser une expression
arithmétique (ici le i seul) là ou est attendu une valeur booléenne
(règle 12.6 pour le cas des && || et !, mais cela peut s'étendre à
l'opérateur ternaire aussi).

Cette valeur booléenne s'obtient depuis l'expression aritméthique en lui
ajoutant simplement un "!= 0". On retombe alors sur la 1ère forme qui
est du coup la plus canonique. Voilà une réponse que j'espère pertinente
à ma question idiote :)


(snip)

Ensuite, un compilateur qui optimise globalement va peut-être intégrer
la fonction en inline (et obtenir exactement l'effet d'un type booléen
formel), et détecter que le résultat n'est utilisé que pour être testé;
mais ce genre de compilateur sait aussi réduire l'opérateur ternaire,
donc il n'y aura pas de différence entre les deux formes.



Il n'y a pas de différences pour seulement certains compilo. Pour les
autres, le code produit sera nettement moins bon. Les compilos qui font
du lto ne sont pas non plus les plus courants dans certains milieux. Il
faut le temps que la technologie se diffuse.

Perso je préférerais un pragma dans la signature de la fonction plutôt
que de présumer que l'inline va faire le boulot car il y a souvent
pleins d'autres raisons qui l'empêchent de se réaliser:

* On veut encore pouvoir débugger le truc après
* La fonction est trop grosse pour pouvoir être inlinée
* La fonction n'est accessible que via une bibliothèque partagée
* etc.

avec un pragma indiquant que seule l'aspect nul/non nul est attendu en
sortie de fonction le compilo saura choisir la façon la plus efficace
même s'il ne fait pas de l'optim poussée.

a+

sam.
3 4 5 6 7