Come posso concatenare stringhe const / letterali in C?


346

Sto lavorando in C e devo concatenare alcune cose.

In questo momento ho questo:

message = strcat("TEXT ", var);

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

Ora, se hai esperienza in C, sono sicuro che ti rendi conto che questo ti dà un errore di segmentazione quando provi a eseguirlo. Quindi, come posso aggirare questo?


6
Vorrei suggerire di usare strlcat invece di strcat! gratisoft.us/todd/papers/strlcpy.html
activout.se

3
Vorrei ribadire questo suggerimento. Strcat causa vulnerabilità agli exploit di overflow del buffer. Qualcuno può fornire al tuo programma dati che lo fanno eseguire codice arbitrario.
Brian,

Risposte:


386

In C, le "stringhe" sono semplici charmatrici. Pertanto, non è possibile concatenarli direttamente con altre "stringhe".

È possibile utilizzare la strcatfunzione, che aggiunge la stringa puntata da srcalla fine della stringa puntata da dest:

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

Ecco un esempio da cplusplus.com :

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

Per il primo parametro, è necessario fornire il buffer di destinazione stesso. Il buffer di destinazione deve essere un buffer di array di caratteri. Per esempio:char buffer[1024];

Assicurati che il primo parametro abbia spazio sufficiente per memorizzare ciò che stai cercando di copiare. Se disponibile, è più sicuro utilizzare funzioni come: strcpy_se strcat_sdove è necessario specificare esplicitamente la dimensione del buffer di destinazione.

Nota : un letterale stringa non può essere utilizzato come buffer, poiché è una costante. Pertanto, è sempre necessario allocare un array di caratteri per il buffer.

Il valore di ritorno di strcatpuò semplicemente essere ignorato, restituisce semplicemente lo stesso puntatore che è stato passato come primo argomento. È lì per comodità e ti consente di concatenare le chiamate in una riga di codice:

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

Quindi il tuo problema potrebbe essere risolto come segue:

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

66
Metteresti "Stai molto attento che ..." in grassetto, per favore? Questo non può essere sottolineato abbastanza. L'uso improprio di strcat, strcpy e sprintf sono il cuore del software instabile / insicuro.
zoccolo

12
Avvertenza: come scritto, questo codice lascerà un enorme buco nel tuo codice per gli exploit buffer overflow.
Brian,

11
Nell'esempio precedente non è possibile sfruttare exploit di overflow del buffer. E sì, sono d'accordo in generale, non userei l'esempio sopra per lunghezze di corda indeterminate di foo e bar.
Brian R. Bondy,

13
@psihodelia: Inoltre, non dimenticare che i cucchiai sono molto meglio delle forchette! quindi assicurati di usare sempre un cucchiaio!
Brian R. Bondy,

20
Al secondo @dolmen, Joel Spolsky ha scritto un articolo abbastanza elaborato sull'argomento . Dovrebbe essere una lettura obbligatoria. ;-)
peter.slizik

247

Evitare l'uso strcatnel codice C. Il modo più pulito e, soprattutto, il più sicuro è usare snprintf:

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

Alcuni commentatori hanno sollevato il problema che il numero di argomenti potrebbe non corrispondere alla stringa di formato e che il codice verrà comunque compilato, ma la maggior parte dei compilatori emette già un avviso se questo è il caso.


3
Dama, stava parlando delle parentesi attorno a "buf" dell'argomento sizeof. non sono richiesti se l'argomento è un'espressione. Ma non capisco perché sei sottoposto a downgrade. penso che la tua risposta sia la migliore di tutte, anche se è c99. (forse per questo non sono d'accordo! lamers!) +1
Johannes Schaub - litb

4
sizeof () funziona qui solo per char buf [...]. NON per char * buf = malloc (...). Non ci sono molte differenze tra array e puntatori, ma questo è uno di questi!
Mr.Ree,

2
Inoltre, sta cercando di eseguire la concatenazione. Concatenare l'uso snprintf()è un GRANDE no no.
Leonardo Herrera,

5
@MrRee: le differenze tra puntatori e array sono ampie e complete! È nel modo in cui li usi che non differisce sempre. Inoltre, i puntatori e l'allocazione dinamica sono concetti veramente ortogonali.
Razze di leggerezza in orbita

34
Uno dei miei animali domestici è gente come @unwind che insiste sulla inutile distinzione tra sizeof(x)e sizeof x. La notazione tra parentesi funziona sempre e la notazione senza parentesi funziona solo a volte, quindi usa sempre la notazione tra parentesi; è una regola semplice da ricordare ed è sicura. Questo entra in una discussione religiosa - sono stato coinvolto in discussioni con coloro che obiettano prima - ma la semplicità di "usa sempre le parentesi" supera qualsiasi merito a non usarle (IMNSHO, ovviamente). Questo è presentato per un equilibrio.
Jonathan Leffler

24

La gente, l'uso str n CPY (), str n gatto (), o s n printf ().
Il superamento dello spazio del buffer eliminerà tutto ciò che segue in memoria!
(E ricorda di lasciare spazio al carattere null '\ 0' finale!)


3
Non solo dovresti ricordare di lasciare spazio per il personaggio NULL, ma devi anche ricordare di aggiungere il carattere NULL. strncpy e strncat non lo fanno per te.
Graeme Perrow,

Uh? strncpy () e strncat () aggiungono sicuramente il carattere finale. In effetti, ne aggiungono troppi. Almeno finché c'è spazio nel buffer, che è una trappola enorme con queste chiamate. Non consigliato.
Rilassati il

3
@unwind, penso che il punto di Graeme sia che se il buffer è troppo piccolo, strncpy o strncat non aggiungeranno la terminazione '\ 0'.
Quinmars,

2
snprintf è buono, strncpy / strncat è la peggior raccomandazione possibile, strlcpy / strlcat è molto meglio.
Robert Gamble,

9
Non usare strncpy(). E ' non è una versione "più sicuro" di strcpy(). L'array di caratteri di destinazione può essere inutilmente riempito con '\0'caratteri extra o, peggio ancora, può essere lasciato non terminato (cioè, non una stringa). (È stato progettato per l'uso con una struttura di dati che viene raramente utilizzata più, un array di caratteri riempito fino alla fine con zero o più '\0'caratteri.)
Keith Thompson,

22

Le stringhe possono anche essere concatenate al momento della compilazione.

#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

Anche malloc e realloc sono utili se non si conosce in anticipo quante stringhe vengono concatenate.

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

Questo finirà in un ciclo infinito quando num_words>INT_MAX, forse dovresti usare size_tperi
12431234123412341234123

5

Non dimenticare di inizializzare il buffer di output. Il primo argomento di strcat deve essere una stringa terminata nulla con spazio aggiuntivo allocato per la stringa risultante:

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

4

Come sottolineato dalla gente, la gestione delle stringhe è migliorata molto. Quindi potresti voler imparare come usare la libreria di stringhe C ++ invece di stringhe in stile C. Tuttavia, ecco una soluzione in C puro

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

Non sono sicuro che sia corretto / sicuro, ma in questo momento non sono riuscito a trovare un modo migliore per farlo in ANSI C.


<string.h>è in stile C ++. Tu vuoi "string.h". Si calcola anche strlen(s1)due volte, che non è necessario. s3dovrebbe essere totalLenght+1lungo.
Mooing Duck il

4
@MooingDuck: "string.h"è una sciocchezza.
sabato

Non uso stringhe di tipo C da un po '. Sentiti libero di pubblicare una versione fissa.
Nils,

4
@MooingDuck: non è corretto. #include <string.h>è corretto C. Utilizzare le parentesi angolari per le intestazioni standard e di sistema (incluso <string.h>), le virgolette per le intestazioni che fanno parte del programma. ( #include "string.h"funzionerà se non hai il tuo file di intestazione con quel nome, ma lo usi <string.h>comunque.)
Keith Thompson,

Si noti che ciò dipende dalle caratteristiche specifiche di C99: miscelazione di dichiarazioni e dichiarazioni e array a lunghezza variabile (VLA). Si noti inoltre che i VLA non forniscono alcun meccanismo per rilevare o gestire errori di allocazione; se non c'è abbastanza spazio per allocare un VLA, il comportamento del programma non è definito.
Keith Thompson,

4

È un comportamento indefinito tentare di modificare i valori letterali delle stringhe, che è qualcosa di simile a:

strcat ("Hello, ", name);

proverò a fare. Tenterà di virare sulla namestringa fino alla fine della stringa letterale "Hello, ", che non è ben definita.

Prova qualcosa di questo. Raggiunge ciò che sembra stia cercando di fare:

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

Questo crea una zona di buffer che viene permesso di essere modificato e quindi le copie sia letterale corda e altro testo ad esso. Fai solo attenzione agli overflow del buffer. Se controlli i dati di input (o li controlli in anticipo), va bene usare buffer a lunghezza fissa come ho fatto io.

Altrimenti, è necessario utilizzare strategie di mitigazione come l'allocazione di memoria sufficiente dall'heap per assicurarsi di poterlo gestire. In altre parole, qualcosa del tipo:

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
Cosa succede se var / foo / bar ha più di 1000 caratteri? > :)
Geo

1
Quindi otterrai un overflow del buffer, che puoi aggiungere codice per verificare in anticipo (diciamo, con strlen). Ma lo scopo di uno snippet di codice è mostrare come funziona qualcosa senza inquinarlo con troppo codice aggiuntivo. Altrimenti controllerei le lunghezze, se var / foo / bar fosse nullo, ecc.
paxdiablo

7
@paxdiablo: Ma non l'hai nemmeno menzionato, in una risposta a una domanda in cui sembra che debba essere menzionato. Questo rende la tua risposta pericolosa . Inoltre non hai spiegato perché questo codice sia migliore del codice originale del PO, ad eccezione del mito che "raggiunge lo stesso risultato del tuo originale" (quindi quale sarebbe il punto? L'originale era rotto !), Quindi la risposta è anche incompleto .
Razze di leggerezza in orbita

Spero di aver risposto alle tue preoccupazioni, @PreferenceBean, anche se in modo meno tempestivo dell'ideale :-) Fammi sapere se hai ancora problemi con la risposta, e lo migliorerò ulteriormente.
paxdiablo,

3

Il primo argomento di strcat () deve essere in grado di contenere spazio sufficiente per la stringa concatenata. Quindi allocare un buffer con spazio sufficiente per ricevere il risultato.

char bigEnough[64] = "";

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

/* and so on */

strcat () concatenerà il secondo argomento con il primo argomento e memorizzerà il risultato nel primo argomento, il carattere restituito * è semplicemente questo primo argomento e solo per comodità dell'utente.

Non si ottiene una stringa appena allocata con il primo e il secondo argomento concatenati, che immagino che ti aspettavi in ​​base al tuo codice.


3

Il modo migliore per farlo senza avere una dimensione del buffer limitata è utilizzando asprintf ()

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

2
Dovresti tornare char *, no const char *. Il valore restituito dovrà essere passato a free.
Per Johansson,

Sfortunatamente asprintfè solo un'estensione GNU.
Calmarius,

3

Se hai esperienza in C noterai che le stringhe sono solo array di caratteri in cui l'ultimo carattere è un carattere nullo.

Ora questo è abbastanza scomodo in quanto devi trovare l'ultimo personaggio per aggiungere qualcosa. strcatlo farà per te.

Quindi strcat cerca nel primo argomento un carattere null. Quindi sostituirà questo con il contenuto del secondo argomento (fino a quando non termina con un valore nullo).

Ora passiamo attraverso il tuo codice:

message = strcat("TEXT " + var);

Qui stai aggiungendo qualcosa al puntatore al testo "TEXT" (il tipo di "TEXT" è const char *. Un puntatore.).

Di solito non funzionerà. Anche la modifica dell'array "TEXT" non funzionerà poiché di solito viene posizionata in un segmento costante.

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

Potrebbe funzionare meglio, tranne per il fatto che stai di nuovo provando a modificare testi statici. strcat non sta allocando nuova memoria per il risultato.

Proporrei invece a fare qualcosa del genere:

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

Leggi la documentazione di sprintfper verificare le sue opzioni.

E ora un punto importante:

Assicurarsi che il buffer disponga di spazio sufficiente per contenere il testo E il carattere null. Esistono un paio di funzioni che possono aiutarti, ad esempio strncat e versioni speciali di printf che allocano il buffer per te. Non garantire la dimensione del buffer porterà alla corruzione della memoria e ai bug sfruttabili da remoto.


Il tipo di "TEXT"è char[5], no const char* . Decadde char*nella maggior parte dei contesti. Per motivi di compatibilità con le versioni precedenti, i letterali stringa non lo sono const, ma il tentativo di modificarli comporta un comportamento indefinito. (In C ++, i letterali delle stringhe sono const.)
Keith Thompson,

2

Puoi scrivere la tua funzione che fa la stessa cosa strcat()ma che non cambia nulla:

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

Se entrambe le stringhe insieme contengono più di 1000 caratteri, la stringa verrà tagliata a 1000 caratteri. È possibile modificare il valore di MAX_STRING_LENGTHin base alle proprie esigenze.


Prevedo un overflow del buffer, ti vedo allocato strlen(str1) + strlen(str2), ma scrivi i strlen(str1) + strlen(str2) + 1caratteri. Quindi puoi davvero scrivere la tua funzione?
Liviu,

Wow! Non liberi mai il ricordo, cattivo, cattivo! return buffer; free(buffer);
Liviu,

A proposito, sizeof(char) == 1(Inoltre, ci sono altri errori più sottili ...) Riesci a capire ora perché non devi scrivere la tua funzione?
Liviu,

@Liviu Libero la memoria sulla linea free(buffer);.
Donald Duck,

1
free(buffer);dopo return buffer;non viene mai eseguito, vederlo in un debugger;) Vedo ora: sì, devi liberare la memoria nella mainfunzione
Liviu,

1

Supponendo che tu abbia un carattere [fixed_size] anziché un carattere *, puoi utilizzare una singola macro creativa per eseguire tutto in una volta con un <<cout<<likeordine ("piuttosto% s il% s \ n" disgiunto, "di", "printf formato stile "). Se stai lavorando con sistemi embedded, questo metodo ti permetterà anche di tralasciare malloc e la grande *printffamiglia di funzioni come snprintf()(Questo evita che dietlibc si lamenti anche di * 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);
  • Nota 1, in genere non useresti argv [0] in questo modo - solo un esempio
  • Nota 2, è possibile utilizzare qualsiasi funzione che genera un carattere *, comprese le funzioni non standard come itoa () per convertire numeri interi in tipi di stringa.
  • Nota 3, se stai già utilizzando printf in qualsiasi parte del tuo programma, non c'è motivo di non usare snprintf (), poiché il codice compilato sarebbe più grande (ma in linea e significativamente più veloce)

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

Stai tentando di copiare una stringa in un indirizzo allocato staticamente. Devi cat in un buffer.

In particolare:

... snip ...

destinazione

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

... snip ...

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

C'è un esempio anche qui.


0

Questa era la mia soluzione

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

ma devi specificare quante stringhe concatenerai

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

0

Prova qualcosa di simile a questo:

#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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.