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

Problème bizarre de concatenation

19 réponses
Avatar
nico
Bonjour,

Soit le morceau de code suivant :

#include <stdio.h>
#include <string.h>

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

/* Blabla */

char* str;

if(argc >= 2)
{
int i;
str = argv[1];

for(i = 2; i < argc; ++i)
{
strcat(str, " ");
strcat(str, argv[i]);
}
}

printf("%s\n", str);
return 0;
}

pourquoi si j'apelle mon prog par :

./prog salut ceci est un test

ca m'affiche "salut est un test" ?

j'ai trouvé une autre méthode qui consiste a remplacer les deux strcat par :

sprintf(str, "%s %s", str, argv[i]);

et là ca fonctionne.

Quelle est la meilleure méthode (si toutefois la première marchait) ?


Merci.

--
nico

10 réponses

1 2
Avatar
Alain
On Mon, 20 Jun 2005 14:29:24 +0200
nico [nico] wrote:

[...]
nico> Quelle est la meilleure méthode (si toutefois la première marchait) ?

ni l'une ni l'autre, tu ne devrais pas toucher au contenu de argv,
mais plutot t'allouer un buffer et travailler dedans.

ici tu écrases le contenu de argv avec son propre contenu, ce qui n'est pas,
je pense, ce que tu veux vraiment faire.

--
Alain
Avatar
Antoine Leca
[ISO/CÉI 8859-1]

En <news:42b6b6a4$0$32719$, nico va escriure:
str = argv[1];

strcat(str, " ");
strcat(str, argv[i]);


Rapelle-moi, tu as alloué la mémoire pour pouvoir ajouter tes chaînes de
caractères ?

pourquoi si j'apelle mon prog par :

./prog salut ceci est un test

ca m'affiche "salut est un test" ?


Parce que le premier strcat() (avec " ") a écrit un '' à la place du 'c'
de ceci; le second a trouvé cette fin de chaîne, donc n'a rien ajouté; le
reste a copié sans recouvrement (enfin, en recouvrant successivement argv[2]
puis argv[3] etc.) donc le résultat paraît être correct.


j'ai trouvé une autre méthode qui consiste a remplacer les deux
strcat par :

sprintf(str, "%s %s", str, argv[i]);

et là ca fonctionne.


Non, cela semble fonctionner (chez toi).
Ici, le sprintf n'écrit pas le '' après l'espace, donc le problème
ci-dessus ne se produit plus. le sprintf ne fait rien d'autre que de faire
l'inverse de ce qu'a fait le shell auparavant, c'est-à-dire mettre des ' '
là où il y a des '' qui indique la fin des chaînes-arguments quand tu
entres dans le programme.


Quelle est la meilleure méthode (si toutefois la première marchait) ?


Lire strcpy() et malloc()/ARG_MAX.
Si tu as un programme qui utilise strcat() sans utiliser strcpy(), en
général il y a un bogue.

Sinon, une bonne méthode consiste à mettre un caractère « qui ne peut pas
arriver » (je mettais souvent '¤' avant les euros, comme il était au
clavier) tout à la fin de la zone où tu fais les strcat(); et à la fin du
programme on vérifie que le '¤' n'a pas été écrasé, et sinon on signale un
problème (assert par exemple). Ensuite on passe des GROS arguments au
programme, miam miam.
Dans ton cas, cette méthose permet de détecter tout de suite le problème: il
n'y a pas d'endroit pour mettre le '¤'...


Antoine

Avatar
Yves ROMAN
"nico" a écrit dans le message de news:
42b6b6a4$0$32719$
Bonjour,

Soit le morceau de code suivant :

#include <stdio.h>
#include <string.h>

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

/* Blabla */

char* str;

if(argc >= 2)
{
int i;
str = argv[1];

for(i = 2; i < argc; ++i)
{
strcat(str, " ");
strcat(str, argv[i]);
}
}

printf("%sn", str);
return 0;
}

pourquoi si j'apelle mon prog par :

./prog salut ceci est un test

ca m'affiche "salut est un test" ?



Tu ne devrait pas toucher la zone pointée par argv[i] car tu ne connais pas
sa taille disponible

Si tu affiches les adresses contenues dans argv[i], tu verras qu'il y a une
zone contigue qui contient:
salutceciestuntest
argv[1] pointe vers le s de salut
argv[2] pointe vers le c de ceci
...

Le 1er strcat(str," ")
transforme la zone en
salut eciestuntest
argv[2] pointe maintenant vers le derrière l'espace, et le strcat() ne
fait rien
Pour les autres ca fonctionne car il a un décalage du au fait que "ceci" n'a
pas été copié.

Par contre, maintenant argv[i] pointent vers un peu n'importe quoi...

j'ai trouvé une autre méthode qui consiste a remplacer les deux strcat par
:

sprintf(str, "%s %s", str, argv[i]);

et là ca fonctionne.



coup de bol !
le sprintf ne met pas le après le " ", donc argv[2] pointe toujours vers
"ceci"

Par contre, après ton traitement, argv[2] pointe vers "ceci est un test"

Tu as de la chance que les arguments du programme soient gérés comme ca.
Si argv[1] pointait vers une zone de 6 caractères, avec une zone inaccesible
derrière, ton programme aurait planté.

Il vaudrait mieux
- calculer la taille finale de la chaine avec une boucle de strlen()
- allouer str avec malloc(somme(strlen())+(argc-2)+1)
- puis faire la boucle des strcat()
- liberer str quand il est devenu inutile


Nota:
J'ai toujours des scrupules à utiliser
sprintf(str, "%s....", str,...);
(en supposant que str soit assez grand)

Avatar
nico
Salut,

D'abord merci pour la réponse.

Il vaudrait mieux
- calculer la taille finale de la chaine avec une boucle de strlen()
- allouer str avec malloc(somme(strlen())+(argc-2)+1)
- puis faire la boucle des strcat()
- liberer str quand il est devenu inutile


Je veux bien mais je le connais pas quand le lis de stdin, voici mon code
complet :

(je pensais que sprintf() s'occupait d'allouer la mémoire qui va bien :/ (ou
de reallouer dans mon cas) donc forcément je suppose que le code qui suit
est horrible)

j'aimerai savoir coment m'en sortir pour la lecture de stdin (pour la
lecture de argv j'ai compris, je vais claculer la taille à l'avance).

/* main.c */

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

void show_usage(char* prg);
void rot13(char * str);

const int BUFFER_SZ = 256;

int main(int argc, char ** argv)
{
if( argc == 2 && strcmp(argv[1], "--help") == 0 )
{
show_usage(argv[0]);
return 0;
}

char * str = calloc(1, sizeof(char));

if(argc == 1)
{ /* on lit l'entrée standard */
char buff[BUFFER_SZ];

if(fgets(buff, BUFFER_SZ, stdin) != NULL)
{
sprintf(str, "%s", buff);
while( fgets(buff, BUFFER_SZ, stdin) != NULL )
sprintf(str, "%s%s", str, buff);
}
else
{
fprintf(stderr, "%s: Error: Nothing to read from stdin",
argv[0]);
return 1;
}
}
else if(argc >= 2)
{ /* on concatène toutes les arguments */
int i;
sprintf(str, "%s", argv[1]);

for(i = 2; i < argc; ++i)
sprintf(str, "%s %s", str, argv[i]);

sprintf(str, "%sn", str);
}

rot13(str);

printf(str);

free(str);
return 0;
}

void rot13(char * str)
{ /* code/decode une chaine en rot13 */
int i = 0;

while( str[i] != 0 )
{
if( isalpha(str[i]) )
{
/*
renvoie la position de a indifferemment de la casse
la plupart du temps a— et Ae

toupper(str[i]) == str[i] détermine si str[i] est une
majuscule ou pas
*/
int a_pos = (toupper(str[i]) == str[i]) ? 'A' : 'a';

/* position de la lettre, a=1, b=2, ... */
int val = str[i] - a_pos + 1;

if( val <= 13 )
str[i] += 13;
else
str[i] -= 13;
}
++i;
}
}

void show_usage(char* prg)
{
printf("Usage: %s [--help] [string [string2 [string3 ...]]]n", prg);
printf("tstringtString to encode/decoden");
printf("t--helptShow helpn");
printf("If string argument is not provided, read string from standard
inputn");
}


/* fin main.c */

PS: je viens de C++ (que je pratique depuis un moment) donc pour les chaines
j'avais l'habitude de ne pas m'embeter beaucoup (un coup de std::string et
de std::string::c_str quand je voulais un char* pour les APIs et c'était
fini !)
--
nico

Avatar
Jean-Claude Arbaut
On 20/06/2005 16:17, nico wrote:

(je pensais que sprintf() s'occupait d'allouer la mémoire qui va bien :/ (ou
de reallouer dans mon cas) donc forcément je suppose que le code qui suit
est horrible)


Aïeoupsssss. Horrible est le mot juste ;-)

Avatar
nico
Jean-Claude Arbaut wrote:

On 20/06/2005 16:17, nico wrote:

(je pensais que sprintf() s'occupait d'allouer la mémoire qui va bien :/
(ou de reallouer dans mon cas) donc forcément je suppose que le code qui
suit est horrible)


Aïeoupsssss. Horrible est le mot juste ;-)


et comme ca c'est mieux ?



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

enum { SUCCESS = 0, ERROR = 1, MEM_ERROR = 2 };

void show_usage(char* prg);
void rot13(char * str);
void print_rot13(char * str);

const int BUFFER_SZ = 12;

int main(int argc, char ** argv)
{
if( argc == 2 && strcmp(argv[1], "--help") == 0 )
{
show_usage(argv[0]);
return SUCCESS;
}

if(argc == 1)
{ /* on lit l'entrée standard */

char buff[BUFFER_SZ];

while( fgets(buff, BUFFER_SZ, stdin) != NULL )
print_rot13(buff);

return SUCCESS;
}

if(argc >= 2)
{ /* on concatène toutes les arguments */

char * str;
int i;
int needed = 0;

for(i = 1; i < argc; ++i)
needed += strlen(argv[i]) + 1;

str = calloc(needed, 1);
if(str == NULL)
{
fprintf(stderr, "%s: Error: Unable to allocate memory",
argv[0]);
return MEM_ERROR;
}

strcpy(str, argv[1]);
for(i = 2; i < argc; ++i)
{
strcat(str, " ");
strcat(str, argv[i]);
}

print_rot13(str);
printf("n");
free(str);

return SUCCESS;
}

return ERROR;
}

void print_rot13(char * str)
{
rot13(str);
printf(str);
}

void rot13(char * str)
{ /* code/decode une chaine en rot13 */
int i = 0;

while( str[i] != 0 )
{
if( isalpha(str[i]) )
{
/*
renvoie la position de a indifferemment de la casse
la plupart du temps a— et Ae

toupper(str[i]) == str[i] détermine si str[i] est une
majuscule ou pas
*/
int a_pos = (toupper(str[i]) == str[i]) ? 'A' : 'a';

/* position de la lettre, a=1, b=2, ... */
int val = str[i] - a_pos + 1;

if( val <= 13 )
str[i] += 13;
else
str[i] -= 13;
}
++i;
}
}

void show_usage(char* prg)
{
printf("Usage: %s [--help] [string [string2 [string3 ...]]]n", prg);
printf("tstringtString to encode/decoden");
printf("t--helptShow helpn");
printf("If string argument is not provided, read string from standard
inputn");
}


--
nico


Avatar
Emmanuel Delahaye
nico wrote on 20/06/05 :
int main(int argc, char ** argv)

str = argv[1];

strcat(str, " ");


Comportement indéfini. Tu ne sais pas si l'argument est modifible, ni
si il peut être agrandi.

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

"Mal nommer les choses c'est ajouter du malheur au
monde." -- Albert Camus.

Avatar
Emmanuel Delahaye
Antoine Leca wrote on 20/06/05 :
Sinon, une bonne méthode consiste à mettre un caractère « qui ne peut pas
arriver » (je mettais souvent '¤' avant les euros, comme il était au
clavier) tout à la fin de la zone où tu fais les strcat(); et à la fin du
programme on vérifie que le '¤' n'a pas été écrasé, et sinon on signale un
problème (assert par exemple). Ensuite on passe des GROS arguments au
programme, miam miam.
Dans ton cas, cette méthose permet de détecter tout de suite le problème: il
n'y a pas d'endroit pour mettre le '¤'...


Je faisais ça aussi avec -1 ou je ne sais quel caractère bizarre
jusqu'à ce qu'un 'smart guy' de c.l.c me fasse remarqué que 0 était la
bonne valeur.

Depuis :

/* controles */
#define LIM_STR(s) (s)[sizeof (s) - 1]=0
#define CHK_STR(s) ASSERT((s)[sizeof (s) - 1]==0)

#ifndef NDEBUG
#define LIM_PTR(p,l) (p)[(l) -1]=0
#else
#define LIM_PTR(p,l)
#endif
#define CHK_PTR(p,l) ASSERT((p)[(l) -1]==0)

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

"There are 10 types of people in the world today;
those that understand binary, and those that dont."

Avatar
nico
Antoine Leca wrote:

Sinon, une bonne méthode consiste à mettre un caractère « qui ne peut pas
arriver » (je mettais souvent '?' avant les euros, comme il était au
clavier) tout à la fin de la zone où tu fais les strcat(); et à la fin du
programme on vérifie que le '?' n'a pas été écrasé, et sinon on signale un
problème (assert par exemple). Ensuite on passe des GROS arguments au
programme, miam miam.
Dans ton cas, cette méthose permet de détecter tout de suite le problème:
il n'y a pas d'endroit pour mettre le '?'...


ok. à la fin de la zona allouée tu veux dire ?

par exemple :

char * str = malloc(nb);
str[nb - 1] = '?';

--
nico

Avatar
nico
nico wrote:
const int BUFFER_SZ = 12;


c'était pour des tests, à la base c'était 256.

--
nico

1 2