Comment concaténer des chaînes const / literal en C?


346

Je travaille en C et je dois concaténer quelques choses.

En ce moment, j'ai ceci:

message = strcat("TEXT ", var);

message2 = strcat(strcat("TEXT ", foo), strcat(" TEXT ", bar));

Maintenant, si vous avez de l'expérience en C, je suis sûr que vous vous rendez compte que cela vous donne un problème de segmentation lorsque vous essayez de l'exécuter. Alors, comment puis-je contourner cela?


6
Je voudrais vous suggérer d'utiliser strlcat au lieu de strcat! gratisoft.us/todd/papers/strlcpy.html
activout.se

3
Je voudrais répéter cette suggestion. Strcat entraîne une vulnérabilité aux exploits de dépassement de tampon. Quelqu'un peut donner à votre programme des données qui lui font exécuter du code arbitraire.
Brian

Réponses:


388

En C, les "chaînes" ne sont que des chartableaux simples . Par conséquent, vous ne pouvez pas les concaténer directement avec d'autres "chaînes".

Vous pouvez utiliser la strcatfonction, qui ajoute la chaîne pointée par srcà la fin de la chaîne pointée par dest:

char *strcat(char *dest, const char *src);

Voici un exemple de cplusplus.com :

char str[80];
strcpy(str, "these ");
strcat(str, "strings ");
strcat(str, "are ");
strcat(str, "concatenated.");

Pour le premier paramètre, vous devez fournir le tampon de destination lui-même. Le tampon de destination doit être un tampon de tableau de caractères. Par exemple:char buffer[1024];

Assurez-vous que le premier paramètre a suffisamment d'espace pour stocker ce que vous essayez de copier dedans. Si vous en disposez, il est plus sûr d'utiliser des fonctions telles que: strcpy_set strcat_soù vous devez explicitement spécifier la taille du tampon de destination.

Remarque : Un littéral de chaîne ne peut pas être utilisé comme tampon, car il s'agit d'une constante. Ainsi, vous devez toujours allouer un tableau de caractères pour le tampon.

La valeur de retour de strcatpeut simplement être ignorée, elle renvoie simplement le même pointeur que celui qui a été transmis comme premier argument. Il est là pour votre commodité et vous permet de chaîner les appels en une seule ligne de code:

strcat(strcat(str, foo), bar);

Ainsi, votre problème pourrait être résolu comme suit:

char *foo = "foo";
char *bar = "bar";
char str[80];
strcpy(str, "TEXT ");
strcat(str, foo);
strcat(str, bar);

66
Pourriez-vous mettre "Soyez très prudent que ..." en gras, s'il vous plaît? Cela ne peut pas être assez souligné. L'utilisation abusive de strcat, strcpy et sprintf est au cœur des logiciels instables / non sécurisés.
socle

12
Avertissement: tel qu'il est écrit, ce code laissera un trou géant et béant dans votre code pour les exploits de dépassement de tampon.
Brian

11
Il n'y a aucun exploit de dépassement de tampon possible dans l'exemple ci-dessus. Et oui, je suis d'accord en général, je n'utiliserais pas l'exemple ci-dessus pour des longueurs de chaîne indéterminées de foo et bar.
Brian R. Bondy

13
@psihodelia: N'oubliez pas non plus que les cuillères sont bien meilleures que les fourchettes! alors assurez-vous de toujours utiliser une cuillère!
Brian R. Bondy

20
Pour seconder @dolmen, Joel Spolsky a écrit un article assez élaboré sur la question. Devrait être une lecture obligatoire. ;-)
peter.slizik

247

Évitez d'utiliser strcaten code C. Le moyen le plus propre et, surtout, le plus sûr est d'utiliser snprintf:

char buf[256];
snprintf(buf, sizeof buf, "%s%s%s%s", str1, str2, str3, str4);

Certains commentateurs ont soulevé un problème selon lequel le nombre d'arguments peut ne pas correspondre à la chaîne de format et le code sera toujours compilé, mais la plupart des compilateurs émettent déjà un avertissement si tel est le cas.


3
Checkers, il parlait des parenthèses autour de "buf" de l'argument sizeof. ils ne sont pas requis si l'argument est une expression. Mais je ne comprends pas pourquoi vous êtes déçu. je pense que votre réponse est la meilleure de toutes, même si c'est c99. (peut-être à cause de cela, ils ne sont pas d'accord! lamers!) +1
Johannes Schaub - litb

4
sizeof () ne fonctionne ici que pour char buf [...]. PAS pour char * buf = malloc (...). Il n'y a pas beaucoup de différences entre les tableaux et les pointeurs, mais c'est l'un d'entre eux!
Mr.Ree

2
En outre, il essaie d'effectuer une concaténation. La concaténation en utilisant snprintf()est un GRAND non non.
Leonardo Herrera

5
@MrRee: Les différences entre les pointeurs et les tableaux sont vastes et complètes! C'est dans la façon dont vous les utilisez qui ne diffère pas toujours. De plus, les pointeurs et l'allocation dynamique sont vraiment des concepts orthogonaux.
Courses de légèreté en orbite

34
Une de mes bêtes noires est des gens comme @unwind qui insistent sur la distinction inutile entre sizeof(x)et sizeof x. La notation entre parenthèses fonctionne toujours et la notation non entre parenthèses ne fonctionne que parfois, utilisez donc toujours la notation entre parenthèses; c'est une règle simple à retenir et sûre. Cela entre dans un argument religieux - j'ai été impliqué dans des discussions avec ceux qui s'y opposent auparavant - mais la simplicité de `` toujours utiliser des parenthèses '' l'emporte sur tout mérite de ne pas les utiliser (IMNSHO, bien sûr). Ceci est présenté pour l'équilibre.
Jonathan Leffler

24

Mes amis , utilisez str n cpy (), str n cat () ou s n printf ().
Le dépassement de votre espace tampon mettra à la poubelle tout ce qui suit en mémoire!
(Et n'oubliez pas de laisser de l'espace pour le caractère '\ 0' nul final!)


3
Non seulement vous devez vous souvenir de laisser de l'espace pour le caractère NULL, mais vous devez vous rappeler d' ajouter le caractère NULL. strncpy et strncat ne le font pas pour vous.
Graeme Perrow

Euh? strncpy () et strncat () ajoutent bien sûr le caractère de fin. En fait, ils en ajoutent trop. Au moins tant qu'il reste de l'espace dans le tampon, ce qui est un énorme piège avec ces appels. Non recommandé.
détendre

3
@unwind, je pense que le point de Graeme est que si le tampon est trop petit, strncpy ou strncat n'ajoutera pas la terminaison '\ 0'.
quinmars

2
snprintf est bon, strncpy / strncat est la pire recommandation possible, strlcpy / strlcat est bien meilleur.
Robert Gamble

9
Ne pas utiliser strncpy(). Ce n'est pas une version "plus sûre" de strcpy(). Le tableau de caractères cible peut être inutilement complété avec des '\0'caractères supplémentaires , ou pire, il peut être laissé sans terminaison (c'est-à-dire pas une chaîne). (Il a été conçu pour être utilisé avec une structure de données qui est rarement utilisée, un tableau de caractères rembourré à la fin avec zéro ou plusieurs '\0'caractères.)
Keith Thompson

22

Les chaînes peuvent également être concaténées au moment de la compilation.

#define SCHEMA "test"
#define TABLE  "data"

const char *table = SCHEMA "." TABLE ; // note no + or . or anything
const char *qry =               // include comments in a string
    " SELECT * "                // get all fields
    " FROM " SCHEMA "." TABLE   /* the table */
    " WHERE x = 1 "             /* the filter */ 
                ;

15

Malloc et realloc sont également utiles si vous ne savez pas à l'avance combien de chaînes sont concaténées.

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

void example(const char *header, const char **words, size_t num_words)
{
    size_t message_len = strlen(header) + 1; /* + 1 for terminating NULL */
    char *message = (char*) malloc(message_len);
    strncat(message, header, message_len);

    for(int i = 0; i < num_words; ++i)
    {
       message_len += 1 + strlen(words[i]); /* 1 + for separator ';' */
       message = (char*) realloc(message, message_len);
       strncat(strncat(message, ";", message_len), words[i], message_len);
    }

    puts(message);

    free(message);
}

Cela se terminera dans une boucle sans fin lorsque num_words>INT_MAX, vous devriez peut-être utiliser size_tpouri
12431234123412341234123

5

N'oubliez pas d'initialiser le tampon de sortie. Le premier argument de strcat doit être une chaîne terminée par null avec suffisamment d'espace supplémentaire alloué pour la chaîne résultante:

char out[1024] = ""; // must be initialized
strcat( out, null_terminated_string ); 
// null_terminated_string has less than 1023 chars

4

Comme les gens l'ont souligné, la gestion des chaînes s'est beaucoup améliorée. Vous souhaiterez donc peut-être apprendre à utiliser la bibliothèque de chaînes C ++ au lieu des chaînes de style C. Cependant voici une solution en C pur

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

void appendToHello(const char *s) {
    const char *const hello = "hello ";

    const size_t sLength     = strlen(s);
    const size_t helloLength = strlen(hello);
    const size_t totalLength = sLength + helloLength;

    char *const strBuf = malloc(totalLength + 1);
    if (strBuf == NULL) {
        fprintf(stderr, "malloc failed\n");
        exit(EXIT_FAILURE);
    }

    strcpy(strBuf, hello);
    strcpy(strBuf + helloLength, s);

    puts(strBuf);

    free(strBuf);

}

int main (void) {
    appendToHello("blah blah");
    return 0;
}

Je ne suis pas sûr que ce soit correct / sûr mais pour le moment je n'ai pas pu trouver une meilleure façon de le faire dans ANSI C.


<string.h>est de style C ++. Tu veux "string.h". Vous calculez également strlen(s1)deux fois, ce qui n'est pas nécessaire. s3devrait être totalLenght+1long.
Mooing Duck

4
@MooingDuck: "string.h"c'est un non-sens.
sbi

Je n'ai pas utilisé de cordes de style C depuis un moment. N'hésitez pas à publier une version fixe.
Nils

4
@MooingDuck: C'est incorrect. #include <string.h>est correct C. Utilisez des équerres pour les en-têtes standard et système (y compris <string.h>), des guillemets pour les en-têtes qui font partie de votre programme. ( #include "string.h"cela fonctionnera si vous n'avez pas votre propre fichier d'en-tête sous ce nom, mais utilisez <string.h>quand même.)
Keith Thompson

Notez que cela dépend des fonctionnalités spécifiques à C99: mélange des déclarations et des instructions, et des tableaux de longueur variable (VLA). Notez également que les VLA ne fournissent aucun mécanisme pour détecter ou gérer les échecs d'allocation; s'il n'y a pas assez de place pour allouer un VLA, le comportement de votre programme n'est pas défini.
Keith Thompson

4

C'est un comportement indéfini d'essayer de modifier les littéraux de chaîne, ce qui ressemble à quelque chose comme:

strcat ("Hello, ", name);

va tenter de faire. Il essaiera de clouer la namechaîne jusqu'à la fin du littéral de chaîne "Hello, ", ce qui n'est pas bien défini.

Essayez quelque chose comme ça. Il réalise ce que vous semblez essayer de faire:

char message[1000];
strcpy (message, "TEXT ");
strcat (message, var);

Cela crée une zone tampon qui peut être modifiée, puis y copie à la fois le littéral de chaîne et tout autre texte. Faites juste attention aux débordements de tampon. Si vous contrôlez les données d'entrée (ou vérifiez-les au préalable), c'est bien d'utiliser des tampons de longueur fixe comme moi.

Sinon, vous devez utiliser des stratégies d'atténuation telles que l'allocation de suffisamment de mémoire à partir du tas pour vous assurer que vous pouvez le gérer. En d'autres termes, quelque chose comme:

const static char TEXT[] = "TEXT ";

// Make *sure* you have enough space.

char *message = malloc (sizeof(TEXT) + strlen(var) + 1);
if (message == NULL)
     handleOutOfMemoryIntelligently();
strcpy (message, TEXT);
strcat (message, var);

// Need to free message at some point after you're done with it.

4
Que se passe-t-il si var / foo / bar a plus de 1000 caractères? > :)
Geo

1
Ensuite, vous obtiendrez un débordement de tampon, que vous pouvez ajouter du code pour vérifier au préalable (par exemple, avec strlen). Mais le but d'un extrait de code est de montrer comment quelque chose fonctionne sans le polluer avec trop de code supplémentaire. Sinon, je vérifierais les longueurs, si var / foo / bar était nul, etc.
paxdiablo

7
@paxdiablo: Mais vous ne l'avez même pas mentionné, dans une réponse à une question où il semblerait qu'il faille le mentionner. Cela rend votre réponse dangereuse . Vous n'avez pas non plus expliqué pourquoi ce code est meilleur que le code original de l'OP, sauf pour le mythe selon lequel "il obtient le même résultat que votre original" (alors quel serait l'intérêt? L'original a été cassé !), Donc la réponse est également incomplète .
Courses de légèreté en orbite

Nous espérons avoir répondu à vos préoccupations, @PreferenceBean, mais en moins de temps que l'idéal :-) Faites-moi savoir si vous avez toujours un problème avec la réponse, et je vais l'améliorer davantage.
paxdiablo

3

Le premier argument de strcat () doit pouvoir contenir suffisamment d'espace pour la chaîne concaténée. Allouez donc un tampon avec suffisamment d'espace pour recevoir le résultat.

char bigEnough[64] = "";

strcat(bigEnough, "TEXT");
strcat(bigEnough, foo);

/* and so on */

strcat () concatène le deuxième argument avec le premier argument et stocke le résultat dans le premier argument, le caractère retourné * est simplement ce premier argument, et uniquement pour votre commodité.

Vous n'obtenez pas une chaîne nouvellement allouée avec le premier et le deuxième argument concaténés, ce que je suppose que vous attendiez en fonction de votre code.


3

La meilleure façon de le faire sans avoir une taille de tampon limitée est d'utiliser asprintf ()

char* concat(const char* str1, const char* str2)
{
    char* result;
    asprintf(&result, "%s%s", str1, str2);
    return result;
}

2
Vous devriez revenir char *, non const char *. La valeur de retour devra être transmise à free.
Per Johansson

Ce asprintfn'est malheureusement qu'une extension GNU.
Calmarius

3

Si vous avez de l'expérience en C, vous remarquerez que les chaînes ne sont que des tableaux de caractères où le dernier caractère est un caractère nul.

Maintenant, c'est assez gênant car vous devez trouver le dernier caractère pour ajouter quelque chose. strcatfera cela pour vous.

Donc, strcat recherche dans le premier argument un caractère nul. Ensuite, il remplacera cela par le contenu du deuxième argument (jusqu'à ce que cela se termine par un null).

Passons maintenant en revue votre code:

message = strcat("TEXT " + var);

Ici, vous ajoutez quelque chose au pointeur sur le texte "TEXT" (le type de "TEXT" est const char *. A pointer.).

Cela ne fonctionnera généralement pas. La modification du tableau "TEXT" ne fonctionnera pas non plus car il est généralement placé dans un segment constant.

message2 = strcat(strcat("TEXT ", foo), strcat(" TEXT ", bar));

Cela pourrait mieux fonctionner, sauf que vous essayez à nouveau de modifier des textes statiques. strcat n'alloue pas de nouvelle mémoire pour le résultat.

Je proposerais de faire quelque chose comme ceci à la place:

sprintf(message2, "TEXT %s TEXT %s", foo, bar);

Lisez la documentation de sprintfpour vérifier ses options.

Et maintenant un point important:

Assurez-vous que le tampon a suffisamment d'espace pour contenir le texte ET le caractère nul. Il existe quelques fonctions qui peuvent vous aider, par exemple strncat et des versions spéciales de printf qui allouent le tampon pour vous. Ne pas garantir la taille de la mémoire tampon entraînera une corruption de la mémoire et des bogues exploitables à distance.


Le type de "TEXT"est char[5], non const char* . Il se désintègre char*dans la plupart des contextes. Pour des raisons de compatibilité descendante, les littéraux de chaîne ne le sont pas const, mais tenter de les modifier entraîne un comportement non défini. (En C ++, les littéraux de chaîne sont const.)
Keith Thompson

2

Vous pouvez écrire votre propre fonction qui fait la même chose que strcat()mais cela ne change rien:

#define MAX_STRING_LENGTH 1000
char *strcat_const(const char *str1,const char *str2){
    static char buffer[MAX_STRING_LENGTH];
    strncpy(buffer,str1,MAX_STRING_LENGTH);
    if(strlen(str1) < MAX_STRING_LENGTH){
        strncat(buffer,str2,MAX_STRING_LENGTH - strlen(buffer));
    }
    buffer[MAX_STRING_LENGTH - 1] = '\0';
    return buffer;
}

int main(int argc,char *argv[]){
    printf("%s",strcat_const("Hello ","world"));    //Prints "Hello world"
    return 0;
}

Si les deux chaînes contiennent plus de 1 000 caractères, la chaîne sera coupée à 1 000 caractères. Vous pouvez modifier la valeur de MAX_STRING_LENGTHselon vos besoins.


Je prévois un débordement de tampon, je vous vois alloué strlen(str1) + strlen(str2), mais vous écrivez des strlen(str1) + strlen(str2) + 1caractères. Pouvez-vous vraiment écrire votre propre fonction?
Liviu

Hou la la! Vous ne libérez jamais la mémoire, méchant, méchant! return buffer; free(buffer);
Liviu

BTW, sizeof(char) == 1(D'ailleurs, il y a d'autres erreurs plus subtiles ...) Pouvez-vous voir maintenant pourquoi vous n'avez pas à écrire votre propre fonction?
Liviu

@Liviu Je libère de la mémoire sur la ligne free(buffer);.
Donald Duck

1
free(buffer);après return buffer;n'est jamais exécuté, voyez-le dans un débogueur;) Je vois maintenant: oui, vous devez libérer la mémoire dans la mainfonction
Liviu

1

En supposant que vous ayez un char [fixed_size] plutôt qu'un char *, vous pouvez utiliser une seule macro créative pour tout faire en même temps avec un <<cout<<likeordre ("plutôt% s le disjoint% s \ n", "plutôt que", "printf format de style "). Si vous travaillez avec des systèmes embarqués, cette méthode vous permettra également de laisser de côté malloc et la grande *printffamille de fonctions comme snprintf()(cela empêche aussi dietlibc de se plaindre de * printf)

#include <unistd.h> //for the write example
//note: you should check if offset==sizeof(buf) after use
#define strcpyALL(buf, offset, ...) do{ \
    char *bp=(char*)(buf+offset); /*so we can add to the end of a string*/ \
    const char *s, \
    *a[] = { __VA_ARGS__,NULL}, \
    **ss=a; \
    while((s=*ss++)) \
         while((*s)&&(++offset<(int)sizeof(buf))) \
            *bp++=*s++; \
    if (offset!=sizeof(buf))*bp=0; \
}while(0)

char buf[256];
int len=0;

strcpyALL(buf,len,
    "The config file is in:\n\t",getenv("HOME"),"/.config/",argv[0],"/config.rc\n"
);
if (len<sizeof(buf))
    write(1,buf,len); //outputs our message to stdout
else
    write(2,"error\n",6);

//but we can keep adding on because we kept track of the length
//this allows printf-like buffering to minimize number of syscalls to write
//set len back to 0 if you don't want this behavior
strcpyALL(buf,len,"Thanks for using ",argv[0],"!\n");
if (len<sizeof(buf))
    write(1,buf,len); //outputs both messages
else
    write(2,"error\n",6);
  • Remarque 1, vous n'utilisez généralement pas argv [0] comme ceci - juste un exemple
  • Remarque 2, vous pouvez utiliser n'importe quelle fonction qui génère un char *, y compris des fonctions non standard comme itoa () pour convertir des entiers en types chaîne.
  • Remarque 3, si vous utilisez déjà printf n'importe où dans votre programme, il n'y a aucune raison de ne pas utiliser snprintf (), car le code compilé serait plus grand (mais aligné et beaucoup plus rapide)

1
int main()
{
    char input[100];
    gets(input);

    char str[101];
    strcpy(str, " ");
    strcat(str, input);

    char *p = str;

    while(*p) {
       if(*p == ' ' && isalpha(*(p+1)) != 0)
           printf("%c",*(p+1));
       p++;
    }

    return 0;
}

1

Vous essayez de copier une chaîne dans une adresse allouée statiquement. Vous devez cat dans un tampon.

Plus précisément:

...couper...

destination

Pointer to the destination array, which should contain a C string, and be large enough to contain the concatenated resulting string.

...couper...

http://www.cplusplus.com/reference/clibrary/cstring/strcat.html

Il y a aussi un exemple ici.


0

C'était ma solution

#include <stdlib.h>
#include <stdarg.h>

char *strconcat(int num_args, ...) {
    int strsize = 0;
    va_list ap;
    va_start(ap, num_args);
    for (int i = 0; i < num_args; i++) 
        strsize += strlen(va_arg(ap, char*));

    char *res = malloc(strsize+1);
    strsize = 0;
    va_start(ap, num_args);
    for (int i = 0; i < num_args; i++) {
        char *s = va_arg(ap, char*);
        strcpy(res+strsize, s);
        strsize += strlen(s);
    }
    va_end(ap);
    res[strsize] = '\0';

    return res;
}

mais vous devez spécifier le nombre de chaînes que vous allez concaténer

char *str = strconcat(3, "testing ", "this ", "thing");

0

Essayez quelque chose de similaire à ceci:

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

int main(int argc, const char * argv[])
{
  // Insert code here...
  char firstname[100], secondname[100];
  printf("Enter First Name: ");
  fgets(firstname, 100, stdin);
  printf("Enter Second Name: ");
  fgets(secondname,100,stdin);
  firstname[strlen(firstname)-1]= '\0';
  printf("fullname is %s %s", firstname, secondname);

  return 0;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.