C dilinde const / literal dizeleri nasıl birleştirebilirim?


349

C ile çalışıyorum ve birkaç şeyi birleştirmem gerekiyor.

Şu anda bu var:

message = strcat("TEXT ", var);

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

Şimdi C tecrübeniz varsa, eminim çalıştırmaya çalıştığınızda bunun size bir segmentasyon hatası verdiğini anlıyorsunuz. Öyleyse nasıl çalışacağım?


6
Strcat yerine strlcat kullanmanızı öneririm! gratisoft.us/todd/papers/strlcpy.html
activout.se

3
Bu öneriyi tekrarlamak istiyorum. Strcat taşma istismarlarında güvenlik açığına neden olur. Birisi program verilerinizi rasgele kod çalıştırmasına neden olabilir.
Brian

Yanıtlar:


390

C'de "dizeler" sadece düz chardizilerdir. Bu nedenle, bunları doğrudan diğer "dizelerle" birleştiremezsiniz.

Sen kullanabilirsiniz strcattarafından işaret dizesini ekler işlevi, srctarafından işaret dizisinin sonuna dest:

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

İşte cplusplus.com'dan bir örnek :

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

İlk parametre için, hedef arabelleğinin kendisini sağlamanız gerekir. Hedef arabelleği bir char dizi arabelleği olmalıdır. Örneğin:char buffer[1024];

İlk parametrenin, kopyalamaya çalıştığınız şeyi depolamak için yeterli alana sahip olduğundan emin olun . Size sunulan, bu gibi işlevleri kullanmak güvenlidir: strcpy_sve strcat_saçıkça hedef tampon boyutunu belirtmek zorunda nerede.

Not : Bir dize hazır bilgisi, sabit olduğu için tampon olarak kullanılamaz. Bu nedenle, her zaman arabellek için bir char dizisi ayırmanız gerekir.

Dönüş değeri strcatyok sayılabilir, yalnızca ilk argümanla aynı işaretçiyi döndürür. Kolaylık sağlamak için oradadır ve aramaları bir kod satırında zincirlemenizi sağlar:

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

Böylece probleminiz şu şekilde çözülebilir:

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

66
"Bu konuda çok dikkatli ol ..." yazar mısın lütfen? Bu yeterince vurgulanamaz. Strcat, strcpy ve sprintf'nin yanlış kullanımı, kararsız / güvensiz yazılımların kalbidir.
kaide

12
Uyarı: Yazıldığı gibi, bu kod, arabellek taşması istismarları için kodunuzda devasa bir boşluk bırakacaktır.
Brian

11
Yukarıdaki örnekte tampon taşması istismarı mümkün değildir. Ve evet, genel olarak yukarıdaki örneği foo ve bar'ın belirsiz dize uzunlukları için kullanmayacağımı kabul ediyorum.
Brian R. Bondy

13
@psihodelia: Kaşıkların çatallardan çok daha iyi olduğunu da unutmayın! bu yüzden her zaman bir kaşık kullandığınızdan emin olun!
Brian R. Bondy

20
İkinci @dolmen olarak, Joel Spolsky konuyla ilgili oldukça ayrıntılı bir makale yazdı . Zorunlu bir okuma olmalıdır. ;-)
peter.slizik

247

strcatC kodunda kullanmaktan kaçının . En temiz ve en önemlisi, en güvenli yol snprintf:

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

Bazı yorumcular, argüman sayısının biçim dizesiyle eşleşmeyebileceği ve kodun hala derleneceği konusunda bir sorun ortaya koydu, ancak bu durumda çoğu derleyici zaten bir uyarı verir.


3
Dama, sizeof argümanının "buf" etrafındaki parantezlerden bahsediyordu. argüman bir ifade ise bunlar gerekli değildir. Ama neden aşağı indirildiğini anlamıyorum. bence c99 olsa da cevabınız en iyisi. (belki de bu yüzden katılmıyorum! lamers!) +1
Johannes Schaub - litb

4
sizeof () burada sadece char buf için çalışır [...]. Char * buf = malloc (...) için DEĞİL. Diziler ve işaretçiler arasında çok fazla fark yok, ama bu onlardan biri!
Mr.Ree

2
Ayrıca, birleştirme yapmaya çalışıyor. Kullanarak bitiştirmek snprintf()BÜYÜK hayır hayır.
Leonardo Herrera

5
@MrRee: İşaretçiler ve diziler arasındaki farklar çok büyük ve eksiksiz! Onları nasıl kullandığınız her zaman farklı değildir. Ayrıca, işaretçiler ve dinamik ayırma gerçekten dik kavramlardır.
Yörüngedeki Hafiflik Yarışları

34
Evcil hayvan peelinglerimden biri, @ unwind gibi sizeof(x)ve arasındaki anlamsız ayrımda ısrar eden insanlar sizeof x. Parantez içindeki gösterim her zaman çalışır ve gizli olmayan gösterim yalnızca bazen çalışır, bu nedenle her zaman parantez içindeki gösterimi kullanın; hatırlanması basit bir kuraldır ve güvenlidir. Bu dini bir tartışmaya giriyor - daha önce itiraz edenlerle tartışmalara katıldım - ama 'her zaman parantez kullan' basitliği, onları kullanmamanın herhangi bir değerinden ağır basar (IMNSHO, elbette). Bu denge için sunulmuştur.
Jonathan Leffler

24

Millet, str n cpy (), str n cat () veya s n printf () kullanın.
Arabellek alanınızı aşmanız, bellekte takip edenleri çöpe atacaktır!
(Ve sonunda boş '\ 0' karakteri için yer bırakmayı unutmayın!)


3
Yalnızca NULL karakteri için boşluk bırakmayı hatırlamakla kalmaz , NULL karakteri eklemeyi de hatırlamanız gerekir . strncpy ve strncat bunu sizin için yapma.
Graeme Perrow

Ah? strncpy () ve strncat () mutlaka sonlandırma karakterini ekler. Aslında, çok fazla ekliyorlar. En azından arabellekte boşluk kaldığı sürece, bu çağrılarla büyük bir tuzak. Tavsiye edilmez.
gevşeyin

3
@unwind, ben Graeme noktası tampon çok küçükse, strncpy veya strncat olacağını düşünüyorum değil sonlanan '\ 0' ekleyin.
quinmars

2
snprintf iyidir, strncpy / strncat mümkün olan en kötü tavsiye, strlcpy / strlcat çok daha iyidir.
Robert Gamble

9
Kullanma strncpy(). O var olmayan bir "güvenli" sürüm strcpy(). Hedef karakter dizisi gereksiz yere ekstra '\0'karakterlerle doldurulabilir veya daha da kötüsü, sonlandırılmadan bırakılabilir (yani bir dize değil). (Artık nadiren kullanılan bir veri yapısı, sıfır veya daha fazla '\0'karakterle sonuna kadar doldurulmuş bir karakter dizisi ile kullanılmak üzere tasarlanmıştır .)
Keith Thompson

22

Dizeler derleme zamanında birleştirilebilir.

#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

Ayrıca, kaç dizenin birleştirildiğini önceden bilmiyorsanız, malloc ve realloc yararlıdır.

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

Bu num_words>INT_MAX, belki desize_ti
12431234123412341234123

5

Çıktı tamponunu başlatmayı unutmayın. Strcat için ilk argüman, sonuçta elde edilen dize için yeterli fazladan boşluk bırakılmış boş bir sonlandırılmış dize olmalıdır:

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

4

İnsanlar işaret ettikleri gibi ip kullanımı çok gelişti. Bu nedenle, C stili dizeler yerine C ++ dize kitaplığını nasıl kullanacağınızı öğrenmek isteyebilirsiniz. Ancak burada saf 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;
}

Doğru / güvenli olup olmadığından emin değilim ama şu anda bunu ANSI C'de yapmanın daha iyi bir yolunu bulamadım.


<string.h>C ++ tarzıdır. Sen istiyorsun "string.h". Ayrıca strlen(s1)iki kez hesaplarsınız , bu da gerekli değildir. uzun s3olmalı totalLenght+1.
Mooing Duck

4
@MooingDuck: "string.h"saçmalık.
sbi

Bir süredir C tarzı dizeler kullanmadım. Sabit bir sürüm yayınlamaktan çekinmeyin.
Nils

4
@MooingDuck: Bu yanlış. #include <string.h>Standart ve sistem başlıkları (dahil <string.h>) için köşeli ayraçlar , programınızın bir parçası olan başlıklar için tırnak işaretleri kullanın. ( #include "string.h"bu adla kendi başlık dosyanız yoksa, <string.h>yine de kullanın .)
Keith Thompson

Bunun C99'a özgü özelliklere bağlı olduğuna dikkat edin: karıştırma bildirimleri ve ifadeleri ile değişken uzunluklu diziler (VLA'lar). Ayrıca VLA'ların ayırma hatalarını algılamak veya işlemek için hiçbir mekanizma sağlamadığını unutmayın; bir VLA tahsis etmek için yeterli alan yoksa, programınızın davranışı tanımsızdır.
Keith Thompson

4

Dize değişmezlerini değiştirmeye çalışmak tanımlanmamış bir davranıştır;

strcat ("Hello, ", name);

yapmaya çalışır. nameDize "Hello, ", iyi tanımlanmamış dize hazır bilgisinin sonuna kadar tutturmaya çalışacaktır .

Bunu bir şey deneyin. Yapmaya çalıştığınız şeyi başarır:

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

Bu bir tampon alanı oluşturur edilir daha sonra dize ve ona diğer metinleri hem kopya değiştirilmiş ve izin. Sadece arabellek taşmalarına dikkat edin. Giriş verilerini kontrol ederseniz (veya önceden kontrol ederseniz), benim gibi sabit uzunluklu tamponlar kullanmakta fayda var.

Aksi takdirde, işleyebilmeniz için yığından yeterli bellek ayırma gibi azaltma stratejilerini kullanmalısınız. Başka bir deyişle, şöyle bir şey:

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
Var / foo / bar 1000'den fazla karakter içeriyorsa ne olur? > :)
Geo

1
Daha sonra, önceden kontrol etmek için kod ekleyebileceğiniz bir tampon taşması alacaksınız (örneğin, strlen ile). Ancak bir kod snippet'inin amacı, bir şeyin fazladan kodla kirletmeden nasıl çalıştığını göstermektir. Aksi takdirde uzunlukları kontrol ederdim, var / foo / bar null, vb.
Olsun

7
@paxdiablo: Ama bundan bahsetmeye ihtiyaç duyacağı bir soruya cevap olarak bahsetmedin bile. Bu cevabınızı tehlikeli yapar . Ayrıca, bu kodun neden OP'nin orijinal kodundan daha iyi olduğunu açıklamıyorsunuz, ancak "orijinalinizle aynı sonucu elde ettiği" efsanesi dışında (o zaman asıl nokta ne olacaktı? Orijinal kırıldı !) de eksik .
Yörüngedeki Hafiflik Yarışları

Umarım endişelerinizi, @PreferenceBean, ideal daha az zamanında olsa da, var :-) Cevap hala bir sorun varsa bana bildirin, ve ben daha da geliştireceğim.
paxdiablo

3

Strcat () yönteminin ilk argümanı, birleştirilmiş dize için yeterli alan tutabilmelidir. Sonuç almak için yeterli alana sahip bir tampon tahsis edin.

char bigEnough[64] = "";

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

/* and so on */

strcat (), ikinci argümanı ilk argümanla birleştirir ve sonucu ilk argümanda saklar, döndürülen char * sadece bu ilk argümandır ve sadece sizin rahatınız içindir.

İlk ve ikinci argüman birleştirilmiş yeni bir dize alamazsınız, ki bu sizin kodunuza göre beklediğiniz tahmin ediyorum.


3

Sınırlı bir arabellek boyutu olmadan yapmanın en iyi yolu asprintf () kullanmaktır

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

2
Geri dönmelisin char *, değil const char *. Dönüş değerinin iletilmesi gerekir free.
Per Johansson

Ne yazık ki asprintfsadece bir GNU uzantısı.
Calmarius

3

C'de deneyiminiz varsa, dizelerin yalnızca son karakterin boş karakter olduğu karakter dizileri olduğunu fark edeceksiniz.

Şimdi bir şey eklemek için son karakteri bulmak zorunda oldukça rahatsız edici. strcatbunu sizin için yapacak.

Böylece strcat ilk argümanda boş bir karakter arar. Daha sonra bunu ikinci argümanın içeriği ile değiştirir (bu bir null ile bitene kadar).

Şimdi kodunuzu inceleyelim:

message = strcat("TEXT " + var);

Burada işaretçiye "TEXT" metnine bir şey ekliyorsunuz ("TEXT" tipi const char *. Bir işaretçi.).

Bu genellikle işe yaramaz. Ayrıca "TEXT" dizisini değiştirmek genellikle sabit bir segmente yerleştirildiği için çalışmaz.

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

Statik metinleri tekrar değiştirmeye çalışmanız dışında bu daha iyi sonuç verebilir. strcat sonuç için yeni bellek ayırmıyor.

Bunun yerine böyle bir şey yapmayı öneriyorum:

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

sprintfSeçeneklerini kontrol etmek için dokümanlarını okuyun .

Ve şimdi önemli bir nokta:

Arabellek metni VE null karakteri tutmak için yeterli alana sahip olduğundan emin olun. Size yardımcı olabilecek birkaç işlev vardır, örneğin strncat ve tamponu sizin için ayıran özel printf sürümleri. Arabellek boyutunun sağlanmaması bellek bozulmasına ve uzaktan istismar edilebilir hatalara yol açacaktır.


Türü "TEXT"olduğunu char[5], değil const char* . char*Çoğu bağlamda bozunur . Geriye dönük uyumluluk nedeniyle, dize değişmezleri değildir const, ancak bunları değiştirmeye çalışmak tanımsız davranışa neden olur. (C ++ 'da dize değişmez değerleri vardır const.)
Keith Thompson

2

Aynı şeyi yapan strcat()ama hiçbir şeyi değiştirmeyen kendi fonksiyonunuzu yazabilirsiniz :

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

Her iki dize birlikte 1000 karakterden uzunsa, dizeyi 1000 karakterden keser. Değerini MAX_STRING_LENGTHihtiyaçlarınıza göre değiştirebilirsiniz.


Arabellek taşmasını öngörüyorum, tahsis edildiğini görüyorum strlen(str1) + strlen(str2), ama strlen(str1) + strlen(str2) + 1karakter yazıyorsun . Gerçekten kendi fonksiyonunuzu yazabilir misiniz?
Liviu

Vaov! Hafızayı asla boşaltılamazsın, kötü, kötü return buffer; free(buffer);
Liviu

BTW, sizeof(char) == 1(Ayrıca, başka daha ince hatalar var ...) Şimdi neden kendi işlevinizi yazmak zorunda olmadığınızı görebiliyor musunuz?
Liviu

@Liviu Hattaki hafızayı serbest bırakıyorum free(buffer);.
Donald Duck

1
free(buffer);sonra return buffer;asla yürütülmez, bir hata ayıklayıcıda bakın;) Şimdi görüyorum: evet, mainfonksiyonunda hafıza boşaltmak zorunda
Liviu

1

Char * yerine char [fixed_size] karakteriniz olduğunu varsayarsak, hepsini bir <<cout<<likesiparişle yapmak için tek bir, yaratıcı makro kullanabilirsiniz ("," printf yerine "% s ayrık% s \ n", " biçem biçimi "). Gömülü sistemlerle çalışıyorsanız, bu yöntem ayrıca malloc ve geniş *printfişlev ailesini dışarıda bırakmanıza izin verecektir snprintf()(Bu, dietlibc'nin * printf hakkında da şikayet etmesini engeller)

#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);
  • Not 1, genellikle böyle bir argv [0] kullanmazsınız - sadece bir örnek
  • Not 2'de, tam sayıları dize türlerine dönüştürmek için itoa () gibi standart olmayan işlevler de dahil olmak üzere bir char * çıkaran herhangi bir işlevi kullanabilirsiniz.
  • Not 3, zaten programınızda herhangi bir yerde printf kullanıyorsanız, derlenmiş kod daha büyük (ancak satır içi ve çok daha hızlı) olacağından snprintf () kullanmamanın bir nedeni yoktur.

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

Bir dizeyi statik olarak ayrılmış bir adrese kopyalamaya çalışıyorsunuz. Bir tampon içine kedi gerekir.

özellikle:

... kesik ...

hedef

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

... kesik ...

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

Burada da bir örnek var.


0

Bu benim çözümümdü

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

ancak kaç dizeyi birleştireceğinizi belirtmeniz gerekir

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

0

Buna benzer bir şey deneyin:

#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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.