Bagaimana cara menggabungkan string const / literal dalam C?


346

Saya bekerja di C, dan saya harus menggabungkan beberapa hal.

Sekarang saya punya ini:

message = strcat("TEXT ", var);

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

Sekarang jika Anda memiliki pengalaman dalam C Saya yakin Anda menyadari bahwa ini memberi Anda kesalahan segmentasi ketika Anda mencoba menjalankannya. Jadi bagaimana saya mengatasinya?


6
Saya ingin menyarankan Anda menggunakan strlcat daripada strcat! gratisoft.us/todd/papers/strlcpy.html
activout.se

3
Saya ingin mengulangi saran itu. Strcat menyebabkan kerentanan untuk mengeksploitasi buffer overflow. Seseorang dapat memberikan data program Anda yang menyebabkannya mengeksekusi kode arbitrer.
Brian

Jawaban:


386

Dalam C, "string" hanyalah chararray biasa . Oleh karena itu, Anda tidak dapat langsung menyatukannya dengan "string" lainnya.

Anda dapat menggunakan strcatfungsi, yang menambahkan string yang ditunjuk oleh srchingga akhir string yang ditunjuk oleh dest:

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

Berikut ini contoh dari cplusplus.com :

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

Untuk parameter pertama, Anda perlu menyediakan buffer tujuan itu sendiri. Buffer tujuan harus berupa buffer array char. Misalnya:char buffer[1024];

Pastikan parameter pertama memiliki cukup ruang untuk menyimpan apa yang Anda coba salin ke dalamnya. Jika tersedia untuk Anda, lebih aman untuk menggunakan fungsi-fungsi seperti: strcpy_sdan di strcat_smana Anda secara eksplisit harus menentukan ukuran buffer tujuan.

Catatan : String literal tidak dapat digunakan sebagai buffer, karena itu adalah konstanta. Dengan demikian, Anda selalu harus mengalokasikan array char untuk buffer.

Nilai kembalian dari strcathanya bisa diabaikan, itu hanya mengembalikan pointer yang sama seperti yang diteruskan sebagai argumen pertama. Itu ada untuk kenyamanan, dan memungkinkan Anda untuk menghubungkan panggilan menjadi satu baris kode:

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

Jadi masalah Anda bisa diselesaikan sebagai berikut:

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

66
Maukah Anda menuliskan "Berhati-hatilah ..." dalam huruf tebal? Ini tidak bisa cukup ditekankan. Penyalahgunaan strcat, strcpy, dan sprintf adalah jantung dari perangkat lunak yang tidak stabil / tidak aman.
alas

12
Peringatan: Seperti yang tertulis, kode ini akan meninggalkan lubang yang sangat besar di dalam kode Anda untuk exploitasi buffer overflow.
Brian

11
Tidak ada exploit buffer overflow yang dimungkinkan pada contoh di atas. Dan ya saya setuju secara umum saya tidak akan menggunakan contoh di atas untuk panjang string foo dan bar yang tidak ditentukan.
Brian R. Bondy

13
@psihodelia: Juga jangan lupa bahwa sendok jauh lebih baik daripada garpu! jadi pastikan untuk selalu menggunakan sendok!
Brian R. Bondy

20
Untuk @dolmen kedua, Joel Spolsky telah menulis artikel yang cukup rumit tentang masalah ini. Harus menjadi bacaan wajib. ;-)
peter.slizik

247

Hindari penggunaan strcatdalam kode C. Cara paling bersih dan yang paling penting, paling aman adalah menggunakan snprintf:

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

Beberapa komentator mengajukan masalah bahwa jumlah argumen mungkin tidak cocok dengan format string dan kode masih akan dikompilasi, tetapi sebagian besar kompiler sudah mengeluarkan peringatan jika ini masalahnya.


3
Checkers, dia berbicara tentang tanda kurung di sekitar "buf" dari argumen sizeof. mereka tidak diperlukan jika argumennya adalah ekspresi. Tapi saya tidak mengerti mengapa Anda downvoted. Saya pikir jawaban Anda adalah yang terbaik dari semua, meskipun c99. (mungkin karena itu mereka tidak setuju! lamers!) +1
Johannes Schaub - litb

4
sizeof () hanya berfungsi di sini untuk char buf [...]. BUKAN untuk char * buf = malloc (...). Tidak banyak perbedaan antara array dan pointer, tetapi ini adalah salah satunya!
Mr.Ree

2
Selain itu, ia mencoba melakukan penggabungan. Menggabungkan penggunaan snprintf()adalah BESAR tidak, tidak.
Leonardo Herrera

5
@ MrRee: Perbedaan antara pointer dan array sangat besar dan lengkap! Ada dalam cara Anda menggunakannya yang tidak selalu berbeda. Juga, petunjuk dan alokasi dinamis adalah konsep yang benar-benar ortogonal.
Lightness Races in Orbit

34
Salah satu kencing hewan peliharaan saya adalah orang-orang seperti @wwind yang bersikeras tentang perbedaan yang tidak ada gunanya antara sizeof(x)dan sizeof x. Notasi tanda kurung selalu bekerja dan notasi yang tidak ditandai hanya berfungsi kadang-kadang, jadi selalu gunakan notasi tanda kurung; itu adalah aturan sederhana untuk diingat dan aman. Ini menjadi argumen agama - saya telah terlibat dalam diskusi dengan orang-orang yang keberatan sebelumnya - tetapi kesederhanaan 'selalu menggunakan tanda kurung' melebihi kelebihan untuk tidak menggunakannya (IMNSHO, tentu saja). Ini disajikan untuk saldo.
Jonathan Leffler

24

Teman-teman, gunakan str n cpy (), str n cat (), atau s n printf ().
Melebihi ruang buffer Anda akan membuang apa pun yang mengikuti di memori!
(Dan ingatlah untuk memberikan ruang untuk karakter nol '\ 0' yang tertinggal!)


3
Anda tidak hanya harus ingat untuk memberikan ruang bagi karakter NULL, Anda harus ingat untuk menambahkan karakter NULL. strncpy dan strncat tidak melakukan itu untuk Anda.
Graeme Perrow

Uh? strncpy () dan strncat () tentu saja menambahkan karakter terminating. Bahkan, mereka menambahkan terlalu banyak. Setidaknya selama ada ruang yang tersisa di buffer, yang merupakan jebakan besar dengan panggilan ini. Tidak direkomendasikan.
bersantai

3
@wwind, saya pikir maksud Graeme adalah bahwa jika buffer terlalu kecil, strncpy atau strncat tidak akan menambahkan terminating '\ 0'.
kuinmars

2
snprintf baik, strncpy / strncat adalah rekomendasi terburuk, strlcpy / strlcat jauh lebih baik.
Robert Gamble

9
Jangan gunakan strncpy(). Ini bukan versi "lebih aman" dari strcpy(). Array karakter target mungkin tidak perlu diisi dengan '\0'karakter tambahan , atau lebih buruk, itu bisa dibiarkan tidak terestimasi (yaitu, bukan string). (Itu dirancang untuk digunakan dengan struktur data yang jarang digunakan lagi, array karakter yang empuk sampai akhir dengan nol atau lebih '\0'karakter.)
Keith Thompson

22

String juga dapat digabungkan pada waktu kompilasi.

#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

Juga malloc dan realloc berguna jika Anda tidak tahu sebelumnya berapa banyak string yang digabungkan.

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

Ini akan berakhir dalam satu lingkaran tanpa akhir ketika num_words>INT_MAX, mungkin Anda harus menggunakan size_tuntuki
12431234123412341234123

5

Jangan lupa untuk menginisialisasi buffer output. Argumen pertama untuk strcat harus berupa string yang diakhiri dengan nol dengan cukup ruang tambahan yang dialokasikan untuk string yang dihasilkan:

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

4

Seperti yang ditunjukkan orang, penanganan string meningkat pesat. Jadi, Anda mungkin ingin mempelajari cara menggunakan pustaka string C ++ alih-alih string gaya-C. Namun di sini ada solusi dalam C murni

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

Saya tidak yakin apakah itu benar / aman tetapi saat ini saya tidak dapat menemukan cara yang lebih baik untuk melakukan ini di ANSI C.


<string.h>adalah gaya C ++. Kamu ingin "string.h". Anda juga menghitung strlen(s1)dua kali, yang tidak diperlukan. s3harus totalLenght+1panjang.
Mooing Duck

4
@ MoingDuck: "string.h"tidak masuk akal.
sbi

Saya belum pernah menggunakan string C-style untuk sementara waktu. Jangan ragu untuk memposting versi tetap.
Nils

4
@ MoooDuck: Itu tidak benar. #include <string.h>benar C. Gunakan kurung sudut untuk header standar dan sistem (termasuk <string.h>), tanda kutip untuk header yang merupakan bagian dari program Anda. ( #include "string.h"akan terjadi jika Anda tidak memiliki file tajuk sendiri dengan nama itu, tetapi <string.h>tetap gunakan .)
Keith Thompson

Perhatikan bahwa ini tergantung pada fitur khusus C99: pencampuran deklarasi dan pernyataan, dan array panjang variabel (VLA). Perhatikan juga bahwa VLA tidak menyediakan mekanisme untuk mendeteksi atau menangani kegagalan alokasi; jika tidak ada cukup ruang untuk mengalokasikan VLA, perilaku program Anda tidak ditentukan.
Keith Thompson

4

Ini adalah perilaku yang tidak terdefinisi untuk mencoba memodifikasi string literal, yang merupakan sesuatu seperti:

strcat ("Hello, ", name);

akan berusaha melakukan. Ini akan mencoba untuk menempelkan namestring ke akhir string literal "Hello, ", yang tidak didefinisikan dengan baik.

Coba sesuatu ini. Ini mencapai apa yang Anda coba lakukan:

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

Hal ini menciptakan daerah penyangga yang sudah diperbolehkan untuk dimodifikasi dan kemudian salinan kedua literal tali dan teks lain untuk itu. Berhati-hatilah dengan buffer overflows. Jika Anda mengontrol data input (atau mengeceknya sebelumnya), boleh saja menggunakan buffer dengan panjang tetap seperti yang saya miliki.

Jika tidak, Anda harus menggunakan strategi mitigasi seperti mengalokasikan cukup memori dari heap untuk memastikan Anda bisa mengatasinya. Dengan kata lain, sesuatu seperti:

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
Apa yang terjadi jika var / foo / bar memiliki lebih dari 1000 karakter? > :)
Geo

1
Kemudian Anda akan mendapatkan buffer overflow, yang dapat Anda tambahkan kode untuk memeriksa sebelumnya (katakanlah, dengan strlen). Tetapi tujuan dari potongan kode adalah untuk menunjukkan bagaimana sesuatu bekerja tanpa mencemarinya dengan terlalu banyak kode tambahan. Kalau tidak, saya akan memeriksa panjang, apakah var / foo / bar adalah nol, dll
paxdiablo

7
@ paxdiablo: Tapi Anda bahkan tidak menyebutkannya, dalam jawaban untuk pertanyaan di mana tampaknya perlu disebutkan. Itu membuat jawaban Anda berbahaya . Anda juga tidak menjelaskan mengapa kode ini lebih baik daripada kode asli OP, kecuali untuk mitos bahwa "mencapai hasil yang sama dengan aslinya" (lalu apa gunanya? Asli rusak !), Jadi jawabannya juga tidak lengkap .
Lightness Races in Orbit

Mudah-mudahan telah mengatasi masalah Anda, @PreferenceBean, meskipun dalam waktu yang kurang tepat dari ideal :-) Beritahu saya jika Anda masih memiliki masalah dengan jawabannya, dan saya akan memperbaikinya lebih lanjut.
paxdiablo

3

Argumen pertama dari strcat () harus dapat menampung ruang yang cukup untuk string bersambung. Jadi alokasikan buffer dengan ruang yang cukup untuk menerima hasilnya.

char bigEnough[64] = "";

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

/* and so on */

strcat () akan menggabungkan argumen kedua dengan argumen pertama, dan menyimpan hasilnya dalam argumen pertama, char * yang dikembalikan hanyalah argumen pertama ini, dan hanya untuk kenyamanan Anda.

Anda tidak mendapatkan string yang baru dialokasikan dengan argumen pertama dan kedua, yang saya kira Anda harapkan berdasarkan kode Anda.


3

Cara terbaik untuk melakukannya tanpa memiliki ukuran buffer terbatas adalah dengan menggunakan asprintf ()

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

2
Anda harus kembali char *, bukan const char *. Nilai kembali harus diteruskan ke free.
Per Johansson

Sayangnya asprintfhanya ekstensi GNU.
Calmarius

3

Jika Anda memiliki pengalaman dalam C, Anda akan melihat bahwa string hanya array char di mana karakter terakhir adalah karakter nol.

Sekarang itu cukup merepotkan karena Anda harus menemukan karakter terakhir untuk menambahkan sesuatu. strcatakan melakukannya untuk Anda.

Jadi strcat mencari argumen pertama untuk karakter null. Maka itu akan menggantikan ini dengan konten argumen kedua (sampai berakhir pada nol).

Sekarang mari kita telusuri kode Anda:

message = strcat("TEXT " + var);

Di sini Anda menambahkan sesuatu ke pointer ke teks "TEXT" (tipe "TEXT" adalah const char *. Sebuah pointer.).

Itu biasanya tidak akan berhasil. Juga memodifikasi array "TEXT" tidak akan berfungsi karena biasanya ditempatkan di segmen konstan.

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

Itu mungkin bekerja lebih baik, kecuali bahwa Anda lagi mencoba mengubah teks statis. strcat tidak mengalokasikan memori baru untuk hasilnya.

Saya akan mengusulkan untuk melakukan sesuatu seperti ini sebagai gantinya:

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

Baca dokumentasi sprintf untuk memeriksa opsi itu.

Dan sekarang poin penting:

Pastikan buffer memiliki cukup ruang untuk menampung teks DAN karakter nol. Ada beberapa fungsi yang dapat membantu Anda, misalnya, strncat dan versi khusus printf yang mengalokasikan buffer untuk Anda. Tidak memastikan ukuran buffer akan menyebabkan kerusakan memori dan bug yang dapat dieksploitasi dari jarak jauh.


Jenis "TEXT"ini char[5], tidak const char* . Itu meluruh char*dalam sebagian besar konteks. Untuk alasan kompatibilitas ke belakang, literal string tidak const, tetapi berusaha untuk memodifikasinya menghasilkan perilaku yang tidak ditentukan. (Dalam C ++, string literal adalah const.)
Keith Thompson

2

Anda dapat menulis fungsi Anda sendiri yang melakukan hal yang sama strcat()tetapi tidak mengubah apa pun:

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

Jika kedua string bersama lebih dari 1000 karakter, itu akan memotong string pada 1000 karakter. Anda dapat mengubah nilai MAX_STRING_LENGTHsesuai dengan kebutuhan Anda.


Saya melihat buffer overflow, saya melihat Anda dialokasikan strlen(str1) + strlen(str2), tetapi Anda menulis strlen(str1) + strlen(str2) + 1karakter. Jadi bisakah Anda benar-benar menulis fungsi Anda sendiri?
Liviu

Wow! Anda tidak pernah membebaskan memori, jahat, jahat! return buffer; free(buffer);
Liviu

BTW, sizeof(char) == 1(Selain itu, ada kesalahan lebih halus lainnya ...) Dapatkah Anda melihat sekarang mengapa Anda tidak harus menulis fungsi Anda sendiri?
Liviu

@ Liviu saya membebaskan memori di telepon free(buffer);.
Donald Duck

1
free(buffer);setelah return buffer;tidak pernah dieksekusi, lihat di debugger;) Saya melihat sekarang: ya, Anda harus membebaskan memori dalam mainfungsi
Liviu

1

Dengan anggapan Anda memiliki char [fix_size] daripada char *, Anda dapat menggunakan makro kreatif tunggal untuk melakukan semuanya sekaligus dengan <<cout<<likepemesanan ("bukan% s% s yang terputus-putus", "daripada", "printf format gaya "). Jika Anda bekerja dengan sistem embedded, metode ini juga akan memungkinkan Anda untuk meninggalkan malloc dan *printfkeluarga besar fungsi seperti snprintf()(Ini menjaga dietlibc dari mengeluh tentang * printf juga)

#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);
  • Catatan 1, Anda biasanya tidak akan menggunakan argv [0] seperti ini - hanya sebuah contoh
  • Catatan 2, Anda dapat menggunakan fungsi apa pun yang menghasilkan karakter *, termasuk fungsi tidak standar seperti itoa () untuk mengubah bilangan bulat menjadi tipe string.
  • Catatan 3, jika Anda sudah menggunakan printf di mana saja dalam program Anda, tidak ada alasan untuk tidak menggunakan snprintf (), karena kode yang dikompilasi akan lebih besar (tetapi sebaris dan secara signifikan lebih cepat)

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

Anda mencoba menyalin string ke alamat yang dialokasikan secara statis. Anda harus memasukkan buffer ke dalam buffer.

Secara khusus:

...menggunting...

tujuan

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

...menggunting...

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

Ada contoh di sini juga.


0

Ini solusi saya

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

tetapi Anda perlu menentukan berapa banyak string yang akan Anda gabungkan

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

0

Coba sesuatu yang mirip dengan ini:

#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;
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.