Wie verkette ich konstante / literale Zeichenfolgen in C?

346

Ich arbeite in C und muss ein paar Dinge verketten.

Im Moment habe ich Folgendes:

message = strcat("TEXT ", var);

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

Wenn Sie Erfahrung mit C haben, werden Sie sicher feststellen, dass dies zu einem Segmentierungsfehler führt, wenn Sie versuchen, es auszuführen. Wie kann ich das umgehen?

The.Anti.9
quelle
6
Ich möchte vorschlagen, dass Sie strlcat anstelle von strcat verwenden! gratisoft.us/todd/papers/strlcpy.html
activout.se
3
Ich möchte diesen Vorschlag wiederholen. Strcat verursacht eine Sicherheitsanfälligkeit, um Überlauf-Exploits zu puffern. Jemand kann Ihren Programmdaten geben, die dazu führen, dass beliebiger Code ausgeführt wird.
Brian

Antworten:

386

In C sind "Strings" nur einfache charArrays. Daher können Sie sie nicht direkt mit anderen "Zeichenfolgen" verketten.

Sie können die strcatFunktion verwenden, mit der die Zeichenfolge, auf die von gezeigt wird, srcan das Ende der Zeichenfolge angehängt wird, auf die gezeigt wird von dest:

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

Hier ist ein Beispiel von cplusplus.com :

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

Für den ersten Parameter müssen Sie den Zielpuffer selbst angeben. Der Zielpuffer muss ein Char-Array-Puffer sein. Z.B:char buffer[1024];

Stellen Sie sicher, dass der erste Parameter genügend Speicherplatz hat, um zu speichern, was Sie in ihn kopieren möchten. Wenn zur Verfügung, ist es sicherer , Funktionen zu verwenden wie: strcpy_sund strcat_swo Sie müssen explizit die Größe des Zielpuffers angeben.

Hinweis : Ein Zeichenfolgenliteral kann nicht als Puffer verwendet werden, da es eine Konstante ist. Daher müssen Sie dem Puffer immer ein char-Array zuweisen.

Der Rückgabewert von strcatkann einfach ignoriert werden, er gibt lediglich den gleichen Zeiger zurück, der als erstes Argument übergeben wurde. Es dient der Bequemlichkeit und ermöglicht es Ihnen, die Anrufe in einer Codezeile zu verketten:

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

Ihr Problem könnte also wie folgt gelöst werden:

char *foo = "foo";
char *bar = "bar";
char str[80];
strcpy(str, "TEXT ");
strcat(str, foo);
strcat(str, bar);
Brian R. Bondy
quelle
66
Würden Sie bitte "Seien Sie sehr vorsichtig, dass ..." fett schreiben? Dies kann nicht genug betont werden. Der Missbrauch von strcat, strcpy und sprintf ist das Herzstück instabiler / unsicherer Software.
Sockel
12
Warnung: Wie geschrieben, hinterlässt dieser Code ein riesiges, klaffendes Loch in Ihrem Code für Pufferüberlauf-Exploits.
Brian
11
Im obigen Beispiel ist kein Pufferüberlauf-Exploit möglich. Und ja, ich stimme im Allgemeinen zu, dass ich das obige Beispiel nicht für unbestimmte Stringlängen von foo und bar verwenden würde.
Brian R. Bondy
13
@psihodelia: Vergiss auch nicht, dass Löffel viel besser sind als Gabeln! Verwenden Sie daher immer einen Löffel!
Brian R. Bondy
20
Joel Spolsky hat zum zweiten @dolmen einen ziemlich ausführlichen Artikel zu diesem Thema geschrieben. Sollte eine obligatorische Lektüre sein. ;-)
peter.slizik
247

Vermeiden Sie die Verwendung strcatin C-Code. Der sauberste und vor allem sicherste Weg ist snprintf:

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

Einige Kommentatoren haben ein Problem angesprochen, dass die Anzahl der Argumente möglicherweise nicht mit der Formatzeichenfolge übereinstimmt und der Code weiterhin kompiliert wird. Die meisten Compiler geben jedoch bereits eine Warnung aus, wenn dies der Fall ist.

Alex B.
quelle
3
Checkers, er sprach über die Klammern um "buf" der Größe des Arguments. Sie sind nicht erforderlich, wenn das Argument ein Ausdruck ist. Aber ich verstehe nicht, warum Sie abgelehnt werden. Ich denke, Ihre Antwort ist die beste von allen, obwohl es c99 ist. (Vielleicht sind sie deshalb anderer Meinung! Lamers!) +1
Johannes Schaub - Litb
4
sizeof () funktioniert hier nur für char buf [...]. NICHT für char * buf = malloc (...). Es gibt nicht viele Unterschiede zwischen Arrays und Zeigern, aber dies ist einer davon!
Mr.Ree
2
Außerdem versucht er, eine Verkettung durchzuführen. Die Verkettung mit snprintf()ist eine GROSSE Nr. Nr.
Leonardo Herrera
5
@ MrRee: Die Unterschiede zwischen Zeigern und Arrays sind groß und vollständig! Es ist, wie Sie verwenden sie , dass nicht immer unterscheiden. Auch Zeiger und dynamische Zuordnung sind wirklich orthogonale Konzepte.
Leichtigkeitsrennen im Orbit
34
Eine meiner Lieblingsbeschwerden sind Leute wie @unwind, die auf der sinnlosen Unterscheidung zwischen sizeof(x)und bestehen sizeof x. Die Notation in Klammern funktioniert immer und die Notation in Klammern funktioniert nur manchmal. Verwenden Sie daher immer die Notation in Klammern. Es ist eine einfache Regel, an die man sich erinnert und die sicher ist. Dies führt zu einem religiösen Streit - ich war in Diskussionen mit denjenigen verwickelt, die zuvor Einwände erhoben haben -, aber die Einfachheit, immer Klammern zu verwenden, überwiegt jeden Verdienst, sie nicht zu verwenden (IMNSHO natürlich). Dies wird zum Ausgleich dargestellt.
Jonathan Leffler
24

Leute, verwenden Sie str n cpy (), str n cat () oder s n printf ().
Wenn Sie Ihren Pufferplatz überschreiten, wird alles andere im Speicher gelöscht!
(Und denken Sie daran, Platz für das nachfolgende Nullzeichen '\ 0' zu lassen!)

Mr.Ree
quelle
3
Sie sollten nicht nur daran denken, Platz für das NULL-Zeichen zu lassen, sondern auch daran, das NULL-Zeichen hinzuzufügen . strncpy und strncat machen das nicht für dich.
Graeme Perrow
Äh? strncpy () und strncat () fügen sicher das abschließende Zeichen hinzu. Tatsächlich fügen sie zu viele hinzu. Zumindest solange noch Platz im Puffer ist, was bei diesen Aufrufen eine große Falle ist. Nicht empfohlen.
Entspannen Sie
3
@unwind, ich denke, der Punkt von Graeme ist, dass wenn der Puffer zu klein ist, strncpy oder strncat das abschließende '\ 0' nicht hinzufügen.
Quinmars
2
snprintf ist gut, strncpy / strncat ist die schlechteste Empfehlung, strlcpy / strlcat ist viel besser.
Robert Gamble
9
Nicht benutzen strncpy(). Es ist keine "sicherere" Version von strcpy(). Das Zielzeichenarray kann unnötig mit zusätzlichen '\0'Zeichen aufgefüllt werden , oder schlimmer noch, es kann nicht abgeschlossen sein (dh keine Zeichenfolge). (Es wurde für die Verwendung mit einer Datenstruktur entwickelt, die selten mehr verwendet wird, einem Zeichenarray, das bis zum Ende mit null oder mehr '\0'Zeichen aufgefüllt ist .)
Keith Thompson
22

Zeichenfolgen können auch zur Kompilierungszeit verkettet werden.

#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 */ 
                ;
dbagnara
quelle
15

Auch malloc und realloc sind nützlich, wenn Sie nicht im Voraus wissen, wie viele Zeichenfolgen verkettet werden.

#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);
}
Reed Hedges
quelle
Dies endet in einer Endlosschleife, wenn num_words>INT_MAXSie vielleicht size_tfüri
12431234123412341234123
5

Vergessen Sie nicht, den Ausgabepuffer zu initialisieren. Das erste Argument für strcat muss eine nullterminierte Zeichenfolge sein, der genügend zusätzlicher Speicherplatz für die resultierende Zeichenfolge zugewiesen ist:

char out[1024] = ""; // must be initialized
strcat( out, null_terminated_string ); 
// null_terminated_string has less than 1023 chars
David Rodríguez - Dribeas
quelle
4

Wie die Leute betonten, verbesserte sich das Saitenhandling erheblich. Vielleicht möchten Sie lernen, wie Sie die C ++ - Zeichenfolgenbibliothek anstelle von Zeichenfolgen im C-Stil verwenden. Hier ist jedoch eine Lösung in reinem C.

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

Ich bin nicht sicher, ob es richtig / sicher ist, aber im Moment könnte ich in ANSI C keinen besseren Weg finden, dies zu tun.

Nils
quelle
<string.h>ist C ++ - Stil. Du willst "string.h". Sie berechnen auch strlen(s1)zweimal, was nicht benötigt wird. s3sollte totalLenght+1lang sein.
Mooing Duck
4
@MooingDuck: "string.h"ist Unsinn.
sbi
Ich habe eine Weile keine Strings im C-Stil mehr verwendet. Fühlen Sie sich frei, eine feste Version zu posten.
Nils
4
@MooingDuck: Das ist falsch. #include <string.h>ist korrekt C. Verwenden Sie spitze Klammern für Standard- und System-Header (einschließlich <string.h>), Anführungszeichen für Header, die Teil Ihres Programms sind. (Funktioniert #include "string.h"zufällig, wenn Sie keine eigene Header-Datei mit diesem Namen haben, diese aber <string.h>trotzdem verwenden.)
Keith Thompson
Beachten Sie, dass dies von C99-spezifischen Funktionen abhängt: Mischen von Deklarationen und Anweisungen sowie Arrays variabler Länge (VLAs). Beachten Sie auch, dass VLAs keinen Mechanismus zum Erkennen oder Behandeln von Zuordnungsfehlern bieten. Wenn nicht genügend Platz zum Zuweisen einer VLA vorhanden ist, ist das Verhalten Ihres Programms undefiniert.
Keith Thompson
4

Es ist ein undefiniertes Verhalten, zu versuchen, Zeichenfolgenliterale zu ändern.

strcat ("Hello, ", name);

werde versuchen zu tun. Es wird versucht, die nameZeichenfolge bis zum Ende des Zeichenfolgenliteral anzuheften"Hello, " , das nicht genau definiert ist.

Probieren Sie etwas aus. Es erreicht das, was Sie zu versuchen scheinen:

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

Dies erzeugt einen Pufferbereich, der ist verändert und kopiert dann sowohl die Stringliteral und anderen Text ihm erlaubt sein. Seien Sie vorsichtig mit Pufferüberläufen. Wenn Sie die Eingabedaten steuern (oder vorher überprüfen), ist es in Ordnung, Puffer mit fester Länge wie ich zu verwenden.

Andernfalls sollten Sie Schadensbegrenzungsstrategien verwenden, z. B. die Zuweisung von genügend Speicher aus dem Heap, um sicherzustellen, dass Sie damit umgehen können. Mit anderen Worten, so etwas wie:

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.
paxdiablo
quelle
4
Was passiert, wenn var / foo / bar mehr als 1000 Zeichen hat? > :)
Geo
1
Dann erhalten Sie einen Pufferüberlauf, den Sie hinzufügen können, um vorher zu prüfen (z. B. mit strlen). Der Zweck eines Code-Snippets besteht jedoch darin, zu zeigen, wie etwas funktioniert, ohne es mit zu viel zusätzlichem Code zu verschmutzen. Andernfalls würde ich die Länge überprüfen, ob var / foo / bar null ist usw.
paxdiablo
7
@paxdiablo: Aber Sie haben es nicht einmal erwähnt, als Antwort auf eine Frage, bei der es anscheinend erwähnt werden muss. Das macht deine Antwort gefährlich . Sie haben auch nicht erklärt, warum dieser Code besser ist als der Originalcode des OP, mit Ausnahme des Mythos, dass er "das gleiche Ergebnis wie Ihr Original erzielt" (worum geht es dann? Das Original war kaputt !), Also die Antwort ist auch unvollständig .
Leichtigkeitsrennen im Orbit
Habe hoffentlich deine Bedenken angesprochen, @PreferenceBean, wenn auch weniger zeitnah als ideal :-) Lass es mich wissen, wenn du immer noch ein Problem mit der Antwort hast, und ich werde es weiter verbessern.
Paxdiablo
3

Das erste Argument von strcat () muss genügend Speicherplatz für die verkettete Zeichenfolge enthalten. Weisen Sie also einen Puffer mit genügend Speicherplatz zu, um das Ergebnis zu erhalten.

char bigEnough[64] = "";

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

/* and so on */

strcat () verkettet das zweite Argument mit dem ersten Argument und speichert das Ergebnis im ersten Argument. Das zurückgegebene Zeichen * ist einfach dieses erste Argument und nur zur Vereinfachung.

Sie erhalten keine neu zugewiesene Zeichenfolge mit dem ersten und zweiten verketteten Argument, was Sie aufgrund Ihres Codes wahrscheinlich erwartet hätten.

Pieter
quelle
3

Der beste Weg, dies ohne eine begrenzte Puffergröße zu tun, ist die Verwendung von asprintf ()

char* concat(const char* str1, const char* str2)
{
    char* result;
    asprintf(&result, "%s%s", str1, str2);
    return result;
}
Nico Cvitak
quelle
2
Du solltest zurückkehren char *, nicht const char *. Der Rückgabewert muss an übergeben werden free.
Per Johansson
Leider asprintfist nur eine GNU-Erweiterung.
Calmarius
3

Wenn Sie Erfahrung mit C haben, werden Sie feststellen, dass Zeichenfolgen nur Zeichenarrays sind, bei denen das letzte Zeichen ein Nullzeichen ist.

Das ist ziemlich unpraktisch, da Sie das letzte Zeichen finden müssen, um etwas anzuhängen. strcatwerde das für dich tun.

Strcat durchsucht also das erste Argument nach einem Nullzeichen. Dann wird dies durch den Inhalt des zweiten Arguments ersetzt (bis dies mit einer Null endet).

Lassen Sie uns nun Ihren Code durchgehen:

message = strcat("TEXT " + var);

Hier fügen Sie dem Zeiger etwas auf den Text "TEXT" hinzu (der Typ von "TEXT" ist const char *. Ein Zeiger.).

Das wird normalerweise nicht funktionieren. Auch das Ändern des "TEXT" -Arrays funktioniert nicht, da es normalerweise in einem konstanten Segment platziert wird.

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

Das könnte besser funktionieren, außer dass Sie erneut versuchen, statische Texte zu ändern. strcat weist dem Ergebnis keinen neuen Speicher zu.

Ich würde stattdessen vorschlagen, so etwas zu tun:

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

Lesen Sie die Dokumentation von sprintf, um die Optionen zu überprüfen.

Und jetzt ein wichtiger Punkt:

Stellen Sie sicher, dass im Puffer genügend Speicherplatz für den Text UND das Nullzeichen vorhanden ist. Es gibt einige Funktionen, die Ihnen helfen können, z. B. strncat und spezielle Versionen von printf, die den Puffer für Sie zuweisen. Wenn die Puffergröße nicht sichergestellt wird, führt dies zu Speicherbeschädigungen und aus der Ferne ausnutzbaren Fehlern.

Ralf
quelle
Die Art der "TEXT"ist char[5], nicht const char* . Es zerfällt char*in den meisten Kontexten. Aus Gründen der Abwärtskompatibilität sind Zeichenfolgenliterale nicht vorhanden const. Der Versuch, sie zu ändern, führt jedoch zu undefiniertem Verhalten. (In C ++ sind String-Literale const.)
Keith Thompson
2

Sie können Ihre eigene Funktion schreiben, die dasselbe tut wie strcat(), aber nichts ändert:

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

Wenn beide Zeichenfolgen zusammen mehr als 1000 Zeichen lang sind, wird die Zeichenfolge auf 1000 Zeichen gekürzt. Sie können den Wert von MAX_STRING_LENGTHan Ihre Bedürfnisse anpassen.

Donald Duck
quelle
Ich sehe einen Pufferüberlauf voraus. Ich sehe, dass Sie zugewiesen sind strlen(str1) + strlen(str2), aber Sie schreiben strlen(str1) + strlen(str2) + 1Zeichen. Können Sie also wirklich Ihre eigene Funktion schreiben?
Liviu
Beeindruckend! Sie befreien nie die Erinnerung, böse, böse! return buffer; free(buffer);
Liviu
Übrigens sizeof(char) == 1(außerdem gibt es noch andere subtilere Fehler ...) Können Sie jetzt sehen, warum Sie keine eigene Funktion schreiben müssen?
Liviu
@Liviu Ich mache den Speicher an der Leitung frei free(buffer);.
Donald Duck
1
free(buffer);nachdem return buffer;wird nie ausgeführt, sehen Sie es in einem Debugger;) Ich sehe jetzt: Ja, Sie müssen den Speicher in der mainFunktion
freigeben
1

Angenommen, Sie haben ein Zeichen [fixed_size] anstelle eines Zeichens *, können Sie ein einzelnes kreatives Makro verwenden, um alles auf einmal mit einer <<cout<<likeBestellung zu erledigen ("eher% s das disjunkte% s \ n", "als", "printf Stilformat "). Wenn Sie mit eingebetteten Systemen arbeiten, können Sie mit dieser Methode auch malloc und die große *printfFunktionsfamilie wie snprintf()(Dies verhindert, dass dietlibc sich auch über * printf beschwert) weglassen.

#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);
  • Hinweis 1: Normalerweise würden Sie argv [0] nicht so verwenden - nur ein Beispiel
  • Hinweis 2: Sie können jede Funktion verwenden, die ein Zeichen * ausgibt, einschließlich nicht standardmäßiger Funktionen wie itoa (), um Ganzzahlen in Zeichenfolgentypen zu konvertieren.
  • Hinweis 3: Wenn Sie printf bereits an einer beliebigen Stelle in Ihrem Programm verwenden, gibt es keinen Grund, snprintf () nicht zu verwenden, da der kompilierte Code größer (aber inline und erheblich schneller) wäre.
Technosaurus
quelle
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;
}
Miljan Rakita
quelle
1

Sie versuchen, eine Zeichenfolge in eine statisch zugewiesene Adresse zu kopieren. Sie müssen in einen Puffer katzen.

Speziell:

... schnipsen ...

Ziel

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

... schnipsen ...

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

Auch hier gibt es ein Beispiel.

Todd
quelle
0

Das war meine Lösung

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

Sie müssen jedoch angeben, wie viele Zeichenfolgen Sie verketten möchten

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

Versuchen Sie etwas Ähnliches:

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