OVH Cloud OVH Cloud

Impression de me compliquer ... (strcat/sprintf)

49 réponses
Avatar
Kinou
Bonsoir,

J'ai un petit problème avec strcat/sprintf notamment en C.

En fait je "parse" un char* caractere par caractere, et je concatene ces
caracteres dans une autre chaine (chaineDest) de caractere char*.
Le probleme est que ma chaine chaineDest, si au depart je lui mets une
taille de 20, et que j'ai besoin ensuite de lui concatener plus de 20
caracteres, comment je fais ?

La solution que j'aurais tendance à utiliser est que quand je vois que
la taille devient trop courte, je sauvegarde ma chaine de caractere
(avec strcpy) chaineDest, je lui realloue la taille qu'il faut avec un
malloc, et je recopie ensuite dans l'autre sens pour remettre le contenu
de ma chaine sauvegardé dans la nouvelle chaine de taille plus grande.

Mais ca me parait abérant de faire ça pour toute concaténation en C ...
J'aime croire que le C n'est pas si barbare qu'on le dit, mais que c'est
plutot parce que je suis plus que juste en C ...

Merci par avance pour vos réponses.

--
Cordialement,
Vincent

10 réponses

1 2 3 4 5
Avatar
gadget
Regarde du coté de la fonction realloc. C'est typiquement sont type
d'utilisation.

--
Gadget
(5710)
Heureux qui, dans ses vers, sait d'une voix légère,
Passer du grave au doux, du plaisant au sévère.
-+- Nicolas Boileau, Art Poétique, chant 1er -+-
Avatar
Emmanuel Delahaye
Kinou wrote on 14/11/04 :
<...> et que j'ai besoin ensuite de lui concatener plus de 20 caracteres,
comment je fais ?


malloc() / realloc().

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

"C is a sharp tool"

Avatar
Charlie Gordon
"gadget" wrote in message
news:
Regarde du coté de la fonction realloc. C'est typiquement sont type
d'utilisation.


Mauvais conseil.
L'OP est visiblement un débutant en C, realloc() est bien top piégeuse pour être
une solution adéquate à son problème.

Je n'ai pas compris pourquoi on doit "sauvegarder" chaineDest avant de "lui"
reallouer la taille supplémentaire. Poster le code nous permettrait une analyse
en ligne et serait riche d'enseignements pour l'OP. Ce sujet est d'ailleurs
récurrent sur ce forum.

J'ajouterais un élément de réponse : la plupart des concaténations qui semblent
efficaces dans d'autres langages (Basic, C++, PHP, Perl...) déclenchent en fait
quasi systématiquement une allocation mémoire pour chaque opération
intermédiaire. En C, c'est le programmeur qui est en charge. Dans un programme
qui fait des concatenations nombreuses et consécutives sur la meme destination,
on peut choisir de réallouer le buffer destination d'une taille plus grande
quand c'est nécessaire, pour éviter d'avoir à le faire pour chaque opération,
quitte à réduire la taille finale au besoin par une opération adéquate. On peut
en particulier avoir recours à des heuristiques sophistiquées pour déterminer de
combien surallouer au fur et à mesure des besoins : progression géométrique,
fibonacci, exponentielle...

Programmer ce genre de choses en C est donc beaucoup plus laborieux, et peut
donc conduire à une grande variété d'implémentations, certaines lentes, d'autres
rapides mais dispendieuses, et de nombreuses avec des erreurs plus ou moins
graves.

Chqrlie.

Avatar
Targeur fou
Kinou wrote:
Bonsoir,


Bonjour,


J'ai un petit problème avec strcat/sprintf notamment en C.

En fait je "parse" un char* caractere par caractere, et je concatene ces
caracteres dans une autre chaine (chaineDest) de caractere char*.
Le probleme est que ma chaine chaineDest, si au depart je lui mets une
taille de 20, et que j'ai besoin ensuite de lui concatener plus de 20
caracteres, comment je fais ?


M'est d'avis que c'est une question de conception. Combien de caractères
correspondant à ton critère vas-tu récupérer ? La meilleure solution
AMA, surtout pour un parsing caractere par caractere est de bien
dimensionner sa chaine destination dès le départ (Qst de conception)
plutôt que de jouer avec les fonction de gestion de mémoire.

Exple (append a faire en fonction du parsing):

#include <stdio.h>

char * appendChar(char * s, size_t lng, char c)
{
if (s != NULL)
{
size_t pos = strlen(s);
if (pos < lng-1)
{
s[pos++] = c;
s[pos] = '';
}
}
return s;
}
int main(void)
{
/* Chaine destination a bien dimensionner, 100 ici (99 car. max) */
char dst_arr[100];
dst_arr[0] = '';
/* Les append a faire en fonction du parsing */
/* Traitement a mettre dans une fonction par exemple */
appendChar(dst_arr,100,'a');
appendChar(dst_arr,100,'b');
appendChar(dst_arr,100,'c');
/* etc ...*/
puts(dst_arr);
return 0;
}


La solution que j'aurais tendance à utiliser est que quand je vois que
la taille devient trop courte, je sauvegarde ma chaine de caractere
(avec strcpy) chaineDest, je lui realloue la taille qu'il faut avec un
malloc, et je recopie ensuite dans l'autre sens pour remettre le contenu
de ma chaine sauvegardé dans la nouvelle chaine de taille plus grande.


Voir realloc().
C'est une solution, mais que je préfère réallouer des blocs conséquents,
je ne m'amuserais pas a utiliser une telle solution pour rajouter un
caractère. Avec realloc(), pas de strcpy a faire (le contenu ne change
pas (peut etre tronque si taille demandée plus petite)), tu gardes le
bloc précédemment alloué, soit l'agrandissement marche, soit il ne
marche pas auqel cas tu gardes le pointeur sur le bloc précédemment alloué.

Exple :

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
char * dst_s, * dst_s_sav;
char * part1_s = "Il y a une ";
char * part2_s = "greve a l'aeroport de Brest et ca m'emmerde beaucoup";

/* Allocation dst_s de 20 (chaine de 19 car. max) */
dst_s = calloc(20, sizeof*dst_s);
if (dst_s)
{
/* On popule une premiere fois dst_s*/
strncat(dst_s, part1_s, 19);
/* On affiche */
puts("CHAINE ORIGINALE");
puts(dst_s);
/* On souhaite un peu de place en plus */
/* On passe a une taille de 50, soit 49 car. max */
/* On utilise pour cela une variable pointeur temporaire*/
dst_s_sav = realloc(dst_s, 50);
if (dst_s_sav)
{
size_t lng;
/* Reallocation reussie, on recupere le nouveau bloc */
dst_s = dst_s_sav;
/* ATTENTION, ne pas oublier de retrancher le nombre de
caractères effectivement ecrits dans le buffer original */
lng = (strlen(part1_s)<19) ? strlen(part1_s) : 19;
strncat(dst_s, part2_s, 50-lng);
puts("CHAINE AGRANDIE");
puts(dst_s);
}
else
{
puts("Pas pu agrandir dst_s...");
}
free(dst_s);
}
else
{
perror("Allocation de dst_s non reussie");
}

return 0;
}

Mais ca me parait abérant de faire ça pour toute concaténation en C ...
J'aime croire que le C n'est pas si barbare qu'on le dit, mais que c'est
plutot parce que je suis plus que juste en C ...


Bah, j'en reviens à la première question, quid du nombre max de
caractères correspondant au critère dans la chaine parsée ?
Utiliser realloc() est deja bien plus souple que de faire un malloc() du
nouveau, un strcpy() puis un free() du precedent mais est-ce vraiment
viable dans ton contexte, sachant que realloc demande deja quelques
precautions.

Regis

Avatar
Charlie Gordon
Exple (append a faire en fonction du parsing):

#include <stdio.h>

char * appendChar(char * s, size_t lng, char c)
{
if (s != NULL)
{
size_t pos = strlen(s);
if (pos < lng-1)


les unsigned ont encore frappé !
ce test se vautre si lng vaut 0
il faut mieux écrire pos + 1 < lng
ou utiliser des ssize_t ou des int pour les tailles de tableau.
qui plus est lng est mal choisi comme nom pour la taille du tableau, c'est trop
ambigu, on pourrait confondre avec la longueur de la chaine qui s'y trouve.

{
s[pos++] = c;
s[pos] = '';
}
}
return s;
}
int main(void)
{
/* Chaine destination a bien dimensionner, 100 ici (99 car. max) */
char dst_arr[100];
dst_arr[0] = '';
/* Les append a faire en fonction du parsing */
/* Traitement a mettre dans une fonction par exemple */


On préférera utiliser sizeof(dst_arr), voire countof(dst_arr), plutot que la
taille en dur. avec
#define coutof(array) ((ssize_t)((sizeof(array)/sizeof(*(array))))

appendChar(dst_arr,100,'a');
appendChar(dst_arr,100,'b');
appendChar(dst_arr,100,'c');
/* etc ...*/
puts(dst_arr);
return 0;
}


La solution que j'aurais tendance à utiliser est que quand je vois que
la taille devient trop courte, je sauvegarde ma chaine de caractere
(avec strcpy) chaineDest, je lui realloue la taille qu'il faut avec un
malloc, et je recopie ensuite dans l'autre sens pour remettre le contenu
de ma chaine sauvegardé dans la nouvelle chaine de taille plus grande.


Voir realloc().
C'est une solution, mais que je préfère réallouer des blocs conséquents,
je ne m'amuserais pas a utiliser une telle solution pour rajouter un
caractère. Avec realloc(), pas de strcpy a faire (le contenu ne change
pas (peut etre tronque si taille demandée plus petite)), tu gardes le
bloc précédemment alloué, soit l'agrandissement marche, soit il ne
marche pas auqel cas tu gardes le pointeur sur le bloc précédemment alloué.

Exple :

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
char * dst_s, * dst_s_sav;
char * part1_s = "Il y a une ";
char * part2_s = "greve a l'aeroport de Brest et ca m'emmerde beaucoup";

/* Allocation dst_s de 20 (chaine de 19 car. max) */
dst_s = calloc(20, sizeof*dst_s);
if (dst_s)
{
/* On popule une premiere fois dst_s*/
strncat(dst_s, part1_s, 19);
/* On affiche */
puts("CHAINE ORIGINALE");
puts(dst_s);
/* On souhaite un peu de place en plus */
/* On passe a une taille de 50, soit 49 car. max */
/* On utilise pour cela une variable pointeur temporaire*/
dst_s_sav = realloc(dst_s, 50);


quel drole de nom pour cette variable temporaire : dst_s_sav.

if (dst_s_sav)
{
size_t lng;
/* Reallocation reussie, on recupere le nouveau bloc */
dst_s = dst_s_sav;
/* ATTENTION, ne pas oublier de retrancher le nombre de
caractères effectivement ecrits dans le buffer original */
lng = (strlen(part1_s)<19) ? strlen(part1_s) : 19;


En fait c'est tout simplement : lng = strlen(dst_s);

strncat(dst_s, part2_s, 50-lng);


pas de bol, c'est 49-lng. Et encore on suppose que lng < 49, sinon ca fera un
debordement de tableau.
strncat() est un faux ami.

puts("CHAINE AGRANDIE");
puts(dst_s);
}
else
{
puts("Pas pu agrandir dst_s...");
}
free(dst_s);
}
else
{
perror("Allocation de dst_s non reussie");
}

return 0;
}

Mais ca me parait abérant de faire ça pour toute concaténation en C ...
J'aime croire que le C n'est pas si barbare qu'on le dit, mais que c'est
plutot parce que je suis plus que juste en C ...


Bah, j'en reviens à la première question, quid du nombre max de
caractères correspondant au critère dans la chaine parsée ?
Utiliser realloc() est deja bien plus souple que de faire un malloc() du
nouveau, un strcpy() puis un free() du precedent mais est-ce vraiment
viable dans ton contexte, sachant que realloc demande deja quelques
precautions.


tout à fait d'accord : realloc() est peu recommandable, en particulier pour les
débutants.
strncat() est piégeuse aussi. Tu l'as utilisée ici une première fois pour
éviter strncpy() qui est effectivement encore pire. Et la deuxieme fois la
taille était fausse. Tu aurais voulu des fonctions de copie et de concatenation
avec controle de débordement. Elles n'existent pas dans la libc. J'ai proposé
des solutions dans un thread récent.

Chqrlie.

PS: strncpy(), strncat(), strtok(), gets()... just say no.


Avatar
DINH Viêt Hoà

PS: strncpy(), strncat(), strtok(), gets()... just say no.


qu'utilises-tu quoi à la place de strncpy(), strncat() ?
du snprintf() ?
strncat(), je ne lui vois vraiment aucun remplaçant valable.

en quoi se trompe-t-on dans les tailles pour strncpy() si on utilise :

{
char * source;
char dest[XXX];

/* ... */

strncpy(dest, source, sizeof(dest));
dest[sizeof(dest) - 1] = '';
}

strtok() et gets(), il y a bien strtok_r() et fgets(),
quoique strtok_r() n'est pas forcément C99, voire pas du tout.

--
DINH V. Hoa,

"Le monde n'attend que toi ! " -- FiLH

Avatar
Charlie Gordon
"DINH Viêt Hoà" wrote in message
news:

PS: strncpy(), strncat(), strtok(), gets()... just say no.


qu'utilises-tu quoi à la place de strncpy(), strncat() ?
du snprintf() ?
strncat(), je ne lui vois vraiment aucun remplaçant valable.


Tu as suivi le fil ou ce sujet a été largement étudié.
Il faut écrire les fonctions dont la sémantique corresponde réellement aux
besoin.
strncpy() et strncat() ne font pas l'affaire.

en quoi se trompe-t-on dans les tailles pour strncpy() si on utilise :

{
char * source;
char dest[XXX];

/* ... */

strncpy(dest, source, sizeof(dest));
dest[sizeof(dest) - 1] = '';
}


D'abord strncpy() fait plus que recopier la source, elle initialise aussi le
reste de dest à 0, ce qui est rarement nécessaire.
Ensuite j'ai vu trop souvent strncpy() mal utilisée, avec des bugs lourds à la
clé.
Si tu veux copier la chaine source dans un buffer dest en tronquant une source
trop longue mais en forçant à dest un '' final, on peut le faire en utilisant
strncpy(), strncat(), snprintf(), memcpy() ... mais il faut rajouter du code
pour traiter les cas où ces fonctions ne font pas ce que l'on veut. Ce besoin
étant assez répandu, il me semble préférable d'écrire une fonction qui réalise
correctement l'opération et de l'utiliser partout.

strtok() et gets(), il y a bien strtok_r() et fgets(),
quoique strtok_r() n'est pas forcément C99, voire pas du tout.


gets() est indéfendable, il faut bien sûr utiliser fgets() ou une autre fonction
qu'on écrira à titre d'exercice, et qui pourra par exemple supprimer le 'n',
signaler le débordement ou consommer le reste de la ligne, voire allouer le
résultat.

strtok utilise une variable globale qui rend son utilisation imbriquée
impossible.
Elle est inadaptée pour scanner des enregistrements avec des champs vides.
Elle est souvent utilisée avec une chaine de séparateurs réduite à un seul
caractère.
La réimplémenter sans les inconvénients n'est pas bien difficile.

Le programmeur qui ne sait pas écrire ces fonctions de base ferait mieux de
changer de langage.

Chqrlie.


Avatar
Emmanuel Delahaye
DINH Viêt Hoà wrote on 14/11/04 :
quoique strtok_r() n'est pas forcément C99, voire pas du tout.


POSIX.

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

"C is a sharp tool"

Avatar
Jean-Marc Bourguet
Kinou writes:

En fait je "parse" un char* caractere par caractere, et je
concatene ces caracteres dans une autre chaine
(chaineDest) de caractere char*.


Si tu dois recopier tous les caractères d'un intervalle, la
solution classique est d'attendre d'avoir atteind la fin de
l'intervalle pour faire la copie, comme ça tu connais la
taille nécessaire. Ou même de retourner une paire debut de
l'intervalle, fin de l'intervalle et de ne faire une copie
que si c'est nécessaire.

Le probleme est que ma chaine chaineDest, si au depart je
lui mets une taille de 20, et que j'ai besoin ensuite de
lui concatener plus de 20 caracteres, comment je fais ?

La solution que j'aurais tendance à utiliser est que quand
je vois que la taille devient trop courte, je sauvegarde
ma chaine de caractere (avec strcpy) chaineDest, je lui
realloue la taille qu'il faut avec un malloc, et je
recopie ensuite dans l'autre sens pour remettre le contenu
de ma chaine sauvegardé dans la nouvelle chaine de taille
plus grande.


Pourquoi ne commences-tu pas par allouer avec la nouvelle
taille, ça copierait une fois de moins. Et naturellement,
si tu as beaucoup de concaténation à faire, tu peux allouer
plus.

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Kinou
Charlie Gordon wrote:
"gadget" wrote in message
news:

Regarde du coté de la fonction realloc. C'est typiquement sont type
d'utilisation.



Mauvais conseil.
L'OP est visiblement un débutant en C, realloc() est bien top piégeuse pour être
une solution adéquate à son problème.


En effet je débute, j'avais en premier lieu utilisé realloc, mais son
utilisation m'a été déconseillée par mon formateur. Je cherche donc une
alternative.


Je n'ai pas compris pourquoi on doit "sauvegarder" chaineDest avant de "lui"
reallouer la taille supplémentaire. Poster le code nous permettrait une analyse
en ligne et serait riche d'enseignements pour l'OP. Ce sujet est d'ailleurs
récurrent sur ce forum.



En effet après réflexion je n'ai pas besoin de sauvegarder, mais je dois
au moins refaire la copie, comme je le fais dans le code si dessous. Ce
code intervient lorsque je me rends compte que la place dans line
devient trop petite.

char* line,tempLine;

tempLine = (char *)malloc(((nbBuffer - 1) * BUFFER_SIZE) * sizeof(char)
+ len * (sizeof(char)));
tempLine[0]='';
strcpy(tempLine,line);

free(line);
line = tempLine;
tempLine=NULL;

J'ajouterais un élément de réponse : la plupart des concaténations qui semblent
efficaces dans d'autres langages (Basic, C++, PHP, Perl...) déclenchent en fait
quasi systématiquement une allocation mémoire pour chaque opération
intermédiaire. En C, c'est le programmeur qui est en charge. Dans un programme
qui fait des concatenations nombreuses et consécutives sur la meme destination,
on peut choisir de réallouer le buffer destination d'une taille plus grande
quand c'est nécessaire, pour éviter d'avoir à le faire pour chaque opération,
quitte à réduire la taille finale au besoin par une opération adéquate. On peut
en particulier avoir recours à des heuristiques sophistiquées pour déterminer de
combien surallouer au fur et à mesure des besoins : progression géométrique,
fibonacci, exponentielle...

Programmer ce genre de choses en C est donc beaucoup plus laborieux, et peut
donc conduire à une grande variété d'implémentations, certaines lentes, d'autres
rapides mais dispendieuses, et de nombreuses avec des erreurs plus ou moins
graves.



C'est ce qui est assez déconcertant en C au début. On nous demande de
gérer ce qui l'était automatiquement (plus au moins efficacement) dans
les autres langages.

Chqrlie.




Merci.

--
Cordialement,
Vincent


1 2 3 4 5