Jak połączyć łańcuchy stałe / literalne w C?


347

Pracuję w C i muszę połączyć kilka rzeczy.

Teraz mam to:

message = strcat("TEXT ", var);

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

Teraz, jeśli masz doświadczenie w C, jestem pewien, że zdajesz sobie sprawę, że to powoduje błąd segmentacji, gdy próbujesz go uruchomić. Jak mam to obejść?


6
Chciałbym zasugerować użycie strlcat zamiast strcat! gratisoft.us/todd/papers/strlcpy.html
activout.se

3
Chciałbym powtórzyć tę sugestię. Strcat powoduje podatność na exploity przepełnienia bufora. Ktoś może podać dane twojego programu, które powodują, że wykonuje on dowolny kod.
Brian

Odpowiedzi:


388

W C „ciągi” są zwykłymi chartablicami. Dlatego nie można bezpośrednio łączyć ich z innymi „ciągami”.

Możesz użyć strcatfunkcji, która dodaje ciąg wskazany przez srcna końcu ciągu wskazanego przez dest:

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

Oto przykład z cplusplus.com :

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

W przypadku pierwszego parametru musisz podać sam bufor docelowy. Bufor docelowy musi być buforem tablicy znaków. Na przykład:char buffer[1024];

Upewnij się, że pierwszy parametr ma wystarczającą ilość miejsca do zapisania w nim tego, co próbujesz skopiować. Jeśli jest to dostępne, bezpieczniej jest używać funkcji takich jak: strcpy_si strcat_sgdzie wyraźnie musisz określić rozmiar bufora docelowego.

Uwaga : Literału łańcuchowego nie można użyć jako bufora, ponieważ jest on stałą. Dlatego zawsze musisz przydzielić tablicę char dla bufora.

Zwracaną wartość strcatmożna po prostu zignorować, po prostu zwraca ten sam wskaźnik, który został przekazany jako pierwszy argument. Jest tam dla wygody i pozwala łączyć połączenia w jedną linię kodu:

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

Twój problem można rozwiązać w następujący sposób:

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

66
Czy mógłbyś pogrubić „Bądź bardzo ostrożny, że ...”? Nie można tego wystarczająco zestresować. Niewłaściwe użycie strcat, strcpy i sprintf są sercem niestabilnego / niepewnego oprogramowania.
cokół

12
Ostrzeżenie: Jak napisano, ten kod pozostawi gigantyczną, dziurawą lukę w kodzie pod kątem wykorzystania przepełnienia bufora.
Brian

11
W powyższym przykładzie nie jest możliwe wykorzystanie przepełnienia bufora. I tak, zgadzam się ogólnie, że nie użyłbym powyższego przykładu dla nieokreślonej długości łańcucha foo i paska.
Brian R. Bondy

13
@psihodelia: Nie zapominaj też, że łyżki są znacznie lepsze niż widelce! więc zawsze używaj łyżki!
Brian R. Bondy

20
Na drugim @dolmen Joel Spolsky napisał dość skomplikowany artykuł na ten temat. Powinno być obowiązkowe czytanie. ;-)
peter.slizik

247

Unikaj używania strcatw kodzie C. Najczystszym i, co najważniejsze, najbezpieczniejszym sposobem jest użycie snprintf:

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

Niektórzy komentatorzy podnieśli problem, że liczba argumentów może nie pasować do ciągu formatu, a kod nadal będzie się kompilował, ale większość kompilatorów już wydaje ostrzeżenie, jeśli tak jest.


3
Warcaby, mówił o nawiasach wokół „buf” argumentu sizeof. nie są wymagane, jeśli argument jest wyrażeniem. Ale nie rozumiem, dlaczego jesteś przegłosowany. myślę, że twoja odpowiedź jest najlepsza ze wszystkich, nawet jeśli jest to c99. (być może z tego powodu się nie zgadzają! lamers!) +1
Johannes Schaub - litb

4
sizeof () działa tutaj tylko dla char buf [...]. NIE dla char * buf = malloc (...). Nie ma wielu różnic między tablicami i wskaźnikami, ale to jedna z nich!
Mr.Ree

2
Próbuje także dokonać konkatenacji. Łączenie za pomocą snprintf()jest BIG no no.
Leonardo Herrera

5
@MrRee: Różnice między wskaźnikami i tablicami są ogromne i pełne! Sposób, w jaki z nich korzystasz , nie zawsze się różni. Wskaźniki i alokacja dynamiczna są tak naprawdę pojęciami ortogonalnymi.
Wyścigi lekkości na orbicie

34
Jednym z moich ulubionych pomysłów są ludzie tacy jak @unwind, którzy nalegają na bezsensowne rozróżnienie między sizeof(x)i sizeof x. Notacja w nawiasach zawsze działa, a notacja w nawiasach działa tylko czasami, dlatego zawsze używaj notacji w nawiasach; jest to prosta zasada do zapamiętania i jest bezpieczna. Wchodzi to w argument religijny - byłem zaangażowany w dyskusje z tymi, którzy sprzeciwiają się wcześniej - ale prostota „zawsze używaj nawiasów” przeważa nad wszelką zasługą, by ich nie używać (IMNSHO, oczywiście). Przedstawiono to dla zachowania równowagi.
Jonathan Leffler

24

Ludzie, użyj str n cpy (), str n cat () lub s n printf ().
Przekroczenie przestrzeni bufora spowoduje usunięcie wszystkiego, co nastąpi w pamięci!
(I pamiętaj, aby pozostawić miejsce na końcowy znak zerowy „\ 0”!)


3
Nie tylko pamiętaj, aby pozostawić miejsce na znak NULL, ale pamiętaj, aby dodać znak NULL. strncpy i strncat nie robią tego dla ciebie.
Graeme Perrow,

Co? strncpy () i strncat () na pewno dodają znak kończący. W rzeczywistości dodają zbyt wiele. Przynajmniej tak długo, jak długo pozostało miejsce w buforze, co jest ogromną pułapką z tymi wywołaniami. Niepolecane.
odpręż się

3
@ unwind, myślę, że celem Graeme jest to, że jeśli bufor jest zbyt mały, strncpy lub strncat nie dodają kończącego „\ 0”.
quinmars,

2
snprintf jest dobry, strncpy / strncat jest najgorszą możliwą rekomendacją, strlcpy / strlcat jest znacznie lepszy.
Robert Gamble,

9
Nie używać strncpy(). To nie jest „bezpieczniejsza” wersja strcpy(). Docelowa tablica znaków może być niepotrzebnie wypełniona dodatkowymi '\0'znakami lub, co gorsza, może pozostać nieokreślona (tj. Nie ciąg znaków). (Został zaprojektowany do użytku z rzadko używaną strukturą danych, tablicą znaków wypełnioną do końca zerowymi lub więcej '\0'znakami.)
Keith Thompson,

22

Ciągi można także łączyć w czasie kompilacji.

#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

Również malloc i realloc są przydatne, jeśli nie wiesz z góry, ile łańcuchów jest konkatenowanych.

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

To skończy się nieskończoną pętlą, kiedy num_words>INT_MAX, być może powinieneś użyć size_tdoi
12431234123412341234123

5

Nie zapomnij zainicjować bufora wyjściowego. Pierwszym argumentem dla strcat musi być łańcuch zakończony znakiem null z wystarczającą ilością miejsca przeznaczonego dla łańcucha wynikowego:

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

4

Jak zauważyli ludzie, znacznie poprawiła się obsługa łańcuchów. Możesz więc chcieć nauczyć się korzystać z biblioteki ciągów C ++ zamiast ciągów w stylu C. Jednak tutaj jest rozwiązanie w czystym 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;
}

Nie jestem pewien, czy jest to poprawne / bezpieczne, ale w tej chwili nie mogłem znaleźć lepszego sposobu na zrobienie tego w ANSI C.


<string.h>jest w stylu C ++. Chcesz "string.h". Obliczasz także strlen(s1)dwa razy, co nie jest potrzebne. s3powinno być totalLenght+1długie.
Mooing Duck,

4
@MooingDuck: "string.h"to nonsens.
sbi,

Przez jakiś czas nie używałem ciągów w stylu C. Możesz opublikować poprawioną wersję.
Nils,

4
@MooingDuck: To nieprawda. #include <string.h>jest poprawny C. Użyj nawiasów kątowych dla nagłówków standardowych i systemowych (w tym <string.h>), cudzysłowów dla nagłówków, które są częścią twojego programu. ( #include "string.h"będzie działać, jeśli nie masz własnego pliku nagłówka o tej nazwie, ale <string.h>mimo to użyj .)
Keith Thompson,

Należy pamiętać, że zależy to od funkcji specyficznych dla C99: mieszania deklaracji i instrukcji oraz tablic o zmiennej długości (VLA). Należy również zauważyć, że VLA nie zapewniają mechanizmu wykrywania lub obsługi błędów alokacji; jeśli nie ma wystarczającej ilości miejsca na alokację VLA, zachowanie twojego programu jest niezdefiniowane.
Keith Thompson,

4

Niezdefiniowanym zachowaniem jest próba modyfikacji literałów łańcuchowych, co jest coś takiego:

strcat ("Hello, ", name);

spróbuje to zrobić. Spróbuje przyczepić nameciąg do końca dosłownego ciągu "Hello, ", który nie jest dobrze zdefiniowany.

Spróbuj czegoś takiego. Osiąga to, co próbujesz zrobić:

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

To tworzy obszar buforowy, który jest dozwolony zostać zmodyfikowane, a następnie kopiuje zarówno ciągiem znaków i inne teksty do niego. Uważaj tylko na przepełnienia bufora. Jeśli kontrolujesz dane wejściowe (lub sprawdzasz je wcześniej), możesz użyć buforów o stałej długości, takich jak ja.

W przeciwnym razie należy zastosować strategie ograniczania ryzyka, takie jak przydzielenie wystarczającej ilości pamięci ze sterty, aby mieć pewność, że można ją obsłużyć. Innymi słowy, coś takiego:

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
Co się stanie, jeśli var / foo / bar ma więcej niż 1000 znaków? > :)
Geo

1
Następnie otrzymasz przepełnienie bufora, które możesz dodać kod, aby sprawdzić wcześniej (powiedzmy, ze strlen). Jednak celem fragmentu kodu jest pokazanie, jak coś działa, bez zanieczyszczania go zbyt dużą ilością dodatkowego kodu. W przeciwnym razie sprawdzałbym długości, czy var / foo / bar ma wartość null itp.
paxdiablo

7
@paxdiablo: Ale nawet o tym nie wspomniałeś, w odpowiedzi na pytanie, w którym wydaje się, że trzeba o tym wspomnieć. To sprawia, że ​​twoja odpowiedź jest niebezpieczna . Nie wyjaśniłeś też, dlaczego ten kod jest lepszy od oryginalnego kodu OP, z wyjątkiem mitu, że „osiąga taki sam wynik jak twój oryginał” (a więc o co chodzi? Oryginał był zepsuty !), Więc odpowiedź jest również niekompletny .
Wyścigi lekkości na orbicie

Mam nadzieję, że odniosłem się do twoich obaw, @PreferenceBean, choć w sposób bardziej terminowy niż idealny :-) Daj mi znać, jeśli nadal masz problem z odpowiedzią, a ja ją poprawię.
paxdiablo

3

Pierwszy argument strcat () musi być w stanie pomieścić wystarczającą ilość miejsca na połączony ciąg. Przydziel więc bufor z wystarczającą ilością miejsca, aby otrzymać wynik.

char bigEnough[64] = "";

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

/* and so on */

strcat () połączy drugi argument z pierwszym argumentem i zapisze wynik w pierwszym argumencie, zwrócony char * jest po prostu pierwszym argumentem i tylko dla twojej wygody.

Nie otrzymujesz nowo przydzielonego ciągu z połączonym pierwszym i drugim argumentem, który, jak sądzę, oczekiwałeś na podstawie twojego kodu.


3

Najlepszym sposobem na to bez ograniczonego rozmiaru bufora jest użycie asprintf ()

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

2
Powinieneś wrócić char *, nie const char *. Wartość zwrotna będzie musiała zostać przekazana do free.
Per Johansson,

Niestety asprintfjest tylko rozszerzeniem GNU.
Calmarius

3

Jeśli masz doświadczenie w C, zauważysz, że ciągi znaków są tylko tablicami znaków, w których ostatni znak jest znakiem pustym.

Jest to dość niewygodne, ponieważ musisz znaleźć ostatnią postać, aby coś dodać. strcatzrobi to za ciebie.

Zatem strcat przeszukuje pierwszy argument pod kątem znaku zerowego. Następnie zastąpi to treścią drugiego argumentu (dopóki nie zakończy się na null).

Teraz przejdźmy do twojego kodu:

message = strcat("TEXT " + var);

Tutaj dodajesz coś do wskaźnika do tekstu „TEKST” (typ „TEKST” to const char *. Wskaźnik.).

To zwykle nie zadziała. Również modyfikacja tablicy „TEKST” nie będzie działać, ponieważ zwykle jest umieszczana w stałym segmencie.

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

To może działać lepiej, z tym wyjątkiem, że ponownie próbujesz zmodyfikować teksty statyczne. strcat nie przydziela nowej pamięci dla wyniku.

Zamiast tego zaproponowałbym coś takiego:

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

Przeczytaj dokumentację, sprintfaby sprawdzić dostępne opcje.

A teraz ważny punkt:

Upewnij się, że bufor ma wystarczającą ilość miejsca do przechowywania tekstu ORAZ znaku null. Istnieje kilka funkcji, które mogą ci pomóc, np. Strncat i specjalne wersje printf, które przydzielają ci bufor. Brak zapewnienia rozmiaru bufora spowoduje uszkodzenie pamięci i zdalne błędy, które można wykorzystać.


Typ "TEXT"jest char[5], nie const char* . Rozkłada się char*w większości kontekstów. Ze względu na kompatybilność wsteczną literały łańcuchowe nie są const, ale próba ich modyfikacji skutkuje niezdefiniowanym zachowaniem. (W C ++ literały łańcuchowe to const.)
Keith Thompson,

2

Możesz napisać własną funkcję, która robi to samo, strcat()ale nic nie zmienia:

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

Jeśli oba łańcuchy razem mają więcej niż 1000 znaków, spowoduje to odcięcie łańcucha na 1000 znaków. Możesz zmienić wartość według MAX_STRING_LENGTHwłasnych potrzeb.


Przewiduję przepełnienie bufora, widzę, że zostałeś przydzielony strlen(str1) + strlen(str2), ale piszesz strlen(str1) + strlen(str2) + 1znaki. Czy naprawdę możesz napisać własną funkcję?
Liviu

Łał! Nigdy nie uwolnisz pamięci, paskudne, paskudne! return buffer; free(buffer);
Liviu

BTW, sizeof(char) == 1(poza tym są jeszcze inne subtelniejsze błędy ...) Czy rozumiesz teraz, dlaczego nie musisz pisać własnej funkcji?
Liviu

@Liviu Zwalniam pamięć na linii free(buffer);.
Kaczor Donald

1
free(buffer);po return buffer;nigdy nie jest wykonywany, zobacz to w debuggerze;) Widzę teraz: tak, musisz zwolnić pamięć w mainfunkcji
Liviu

1

Zakładając, że masz char [fixed_size] zamiast char *, możesz użyć pojedynczego, kreatywnego makra, aby zrobić to wszystko jednocześnie z <<cout<<likezamówieniem („raczej% s rozłączony% s \ n”, „niż”, „printf format stylu ”). Jeśli pracujesz z systemami wbudowanymi, ta metoda pozwoli ci również pominąć malloc i dużą *printfrodzinę funkcji takich jak snprintf()(To powstrzymuje dietlibc przed narzekaniem na * printf też)

#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);
  • Uwaga 1, zwykle nie używasz argv [0] w ten sposób - tylko przykład
  • Uwaga 2, możesz użyć dowolnej funkcji, która wypisuje znak char *, w tym niestandardowych funkcji, takich jak itoa () do konwersji liczb całkowitych na typy łańcuchowe.
  • Uwaga 3, jeśli już używasz printf w dowolnym miejscu w swoim programie, nie ma powodu, aby nie używać snprintf (), ponieważ skompilowany kod byłby większy (ale wstawiany i znacznie szybszy)

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

Próbujesz skopiować ciąg na adres przydzielony statycznie. Musisz kot w buforze.

Konkretnie:

...fantastyczna okazja...

Miejsce docelowe

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

...fantastyczna okazja...

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

Jest tu także przykład.


0

To było moje rozwiązanie

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

ale musisz określić, ile ciągów chcesz połączyć

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

0

Spróbuj czegoś podobnego do tego:

#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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.