Apakah menginisialisasi char [] dengan string benar-benar praktik buruk?

44

Saya sedang membaca utas berjudul "strlen vs sizeof" di CodeGuru , dan salah satu balasan menyatakan bahwa "lagian [praktik] buruk untuk menginisialisasi [sic] chararray dengan string literal."

Apakah ini benar, atau apakah itu hanya pendapatnya (walaupun "anggota elit")?


Ini pertanyaan aslinya:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

Baik. ukurannya harus panjang plus 1 ya?

ini adalah output

the size of september is 8 and the length is 9

Ukuran harus 10 pasti. itu seperti menghitung sizeof string sebelum diubah oleh strcpy tetapi panjang setelahnya.

Apakah ada yang salah dengan sintaks saya atau apa?


Inilah jawabannya :

Lagi pula itu praktik yang buruk untuk menginisialisasi array char dengan string literal. Jadi selalu lakukan salah satu dari yang berikut:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");
Cole Johnson
sumber
Perhatikan "const" di baris pertama. Mungkinkah penulisnya mengasumsikan c ++ bukannya c? Dalam c ++ ini adalah "praktik buruk", karena literal harus berupa const dan setiap kompiler c ++ baru-baru ini akan memberikan peringatan (atau kesalahan) tentang menetapkan literal const ke array non-const.
André
@ André C ++ mendefinisikan string literal sebagai array array, karena itulah satu-satunya cara yang aman untuk menghadapinya. Bahwa C tidak masalah, jadi Anda memiliki aturan sosial yang memberlakukan hal yang aman
Caleth
@Caleth. Saya tahu, saya lebih berusaha untuk berdebat bahwa penulis jawaban itu mendekati "praktik buruk" dari perspektif c ++.
André
@ André itu bukan praktik buruk di C ++, karena ini bukan praktik , itu kesalahan tipe straight up. Ini seharusnya merupakan kesalahan ketik di C, tetapi tidak, jadi Anda harus memiliki aturan panduan gaya yang memberi tahu Anda "Ini dilarang"
Caleth

Jawaban:

59

Lagi pula itu praktik yang buruk untuk menginisialisasi array char dengan string literal.

Penulis komentar itu tidak pernah benar-benar membenarkannya, dan saya merasa pernyataan itu membingungkan.

Di C (dan Anda telah menandai ini sebagai C), itu satu-satunya cara untuk menginisialisasi array chardengan nilai string (inisialisasi berbeda dari penugasan). Anda bisa menulis

char string[] = "october";

atau

char string[8] = "october";

atau

char string[MAX_MONTH_LENGTH] = "october";

Dalam kasus pertama, ukuran array diambil dari ukuran initializer. Literal string disimpan sebagai array chardengan terminasi 0 byte, sehingga ukuran array adalah 8 ('o', 'c', 't', 'o', 'b', 'e', ​​'r', 0). Dalam dua kasus kedua, ukuran array ditentukan sebagai bagian dari deklarasi (8 dan MAX_MONTH_LENGTH, apa pun yang terjadi).

Yang tidak bisa Anda lakukan adalah menulis sesuatu

char string[];
string = "october";

atau

char string[8];
string = "october";

dll Dalam kasus pertama, deklarasi stringini tidak lengkap karena tidak ada ukuran array telah ditetapkan dan tidak ada initializer untuk mengambil ukuran dari. Dalam kedua kasus, =tidak akan bekerja karena a) ekspresi array seperti stringmungkin bukan target penugasan dan b) =operator tidak didefinisikan untuk menyalin konten dari satu array ke array yang lain.

Dengan token yang sama, Anda tidak dapat menulis

char string[] = foo;

di mana fooarray lain dari char. Bentuk inisialisasi ini hanya akan bekerja dengan string literal.

SUNTING

Saya harus mengubah ini untuk mengatakan bahwa Anda juga dapat menginisialisasi array untuk menahan string dengan penginisialisasi gaya array, seperti

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

atau

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

tetapi lebih mudah di mata untuk menggunakan string literal.

EDIT 2

Untuk menetapkan konten array di luar deklarasi, Anda harus menggunakan strcpy/strncpy(untuk string yang diakhiri 0) atau memcpy(untuk semua jenis array lainnya):

if (sizeof string > strlen("october"))
  strcpy(string, "october");

atau

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!
John Bode
sumber
@KeithThompson: tidak setuju, hanya menambahkannya demi kelengkapan.
John Bode
16
Harap perhatikan bahwa itu char[8] str = "october";adalah praktik buruk. Saya benar-benar harus menghitung sendiri untuk memastikan itu bukan overflow dan rusak dalam pemeliharaan ... misalnya memperbaiki kesalahan ejaan dari seprateke separateakan rusak jika ukuran tidak diperbarui.
djechlin
1
Saya setuju dengan djechlin, itu adalah praktik buruk karena alasan yang diberikan. Jawaban JohnBode sama sekali tidak mengomentari aspek "praktik buruk" (yang merupakan bagian utama dari pertanyaan !!), itu hanya menjelaskan apa yang dapat atau tidak dapat Anda lakukan untuk menginisialisasi array.
mastov
Kecil: Karena nilai 'panjang' yang dikembalikan dari strlen()tidak termasuk karakter nol, gunakan MAX_MONTH_LENGTHuntuk menahan ukuran maksimum yang diperlukan agar char string[]sering terlihat salah. IMO, MAX_MONTH_SIZEakan lebih baik di sini.
chux - Reinstate Monica
10

Satu-satunya masalah yang saya ingat adalah menetapkan string literal ke char *:

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

Sebagai contoh, ambil program ini:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

Ini pada platform saya (Linux) macet karena mencoba untuk menulis ke halaman yang ditandai sebagai hanya-baca. Pada platform lain mungkin mencetak 'September' dll.

Yang mengatakan - inisialisasi secara literal membuat jumlah pemesanan tertentu sehingga ini tidak akan berfungsi:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

Tapi ini akan

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

Sebagai komentar terakhir - saya tidak akan menggunakan strcpysama sekali:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

Sementara beberapa kompiler dapat mengubahnya menjadi panggilan strncpyaman jauh lebih aman:

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';
Maciej Piechotka
sumber
Masih ada risiko buffer overrun strncpykarena itu tidak mengakhiri string yang disalin ketika panjangnya something_elselebih besar dari sizeof(buf). Saya biasanya mengatur karakter terakhir buf[sizeof(buf)-1] = 0untuk melindungi dari itu, atau jika bufnol diinisialisasi, gunakan sizeof(buf) - 1sebagai panjang salinan.
syockit
Gunakan strlcpyatau strcpy_satau bahkan snprintfjika Anda harus.
user253751
Tetap. Sayangnya tidak ada cara portabel yang mudah untuk melakukan ini kecuali Anda memiliki kemewahan bekerja dengan kompiler terbaru ( strlcpydan snprintftidak dapat diakses langsung di MSVC, setidaknya pesanan dan strcpy_stidak pada * nix).
Maciej Piechotka
@MaciejPiechotka: Baiklah, terima kasih Tuhan Unix menolak lampiran k yang disponsori oleh microsoft.
Deduplicator
6

Satu hal yang tidak ditampilkan oleh kedua utas ini adalah:

char whopping_great[8192] = "foo";

vs.

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

Yang pertama akan melakukan sesuatu seperti:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

Yang terakhir hanya melakukan memcpy. Standar C menegaskan bahwa jika ada bagian dari array yang diinisialisasi, semuanya adalah. Jadi dalam hal ini, lebih baik melakukannya sendiri. Saya pikir itulah yang mungkin terjadi pada treuss.

Tentunya

char whopping_big[8192];
whopping_big[0] = 0;

lebih baik daripada:

char whopping_big[8192] = {0};

atau

char whopping_big[8192] = "";

ps Untuk poin bonus, Anda dapat melakukan:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

untuk membuang waktu kompilasi bagi dengan kesalahan nol jika Anda hendak melimpahi array.

Richard Fife
sumber
5

Terutama karena Anda tidak akan memiliki ukuran char[]dalam variabel / konstruksi yang dapat Anda gunakan dengan mudah dalam program.

Contoh kode dari tautan:

 char string[] = "october";
 strcpy(string, "september");

stringdialokasikan pada tumpukan sepanjang 7 atau 8 karakter. Saya tidak ingat apakah itu diakhiri dengan nol dengan cara ini atau tidak - utas yang Anda tautkan menyatakan bahwa itu adalah nol.

Menyalin "september" pada string itu adalah memori yang jelas dibanjiri.

Tantangan lain muncul jika Anda beralih stringke fungsi lain sehingga fungsi lainnya dapat menulis ke dalam array. Anda perlu memberitahu fungsi lain berapa lama array begitu itu tidak membuat overrun. Anda bisa meneruskan stringdengan hasil strlen()tetapi utas menjelaskan bagaimana ini bisa meledak jika stringtidak diakhiri null.

Anda lebih baik mengalokasikan string dengan ukuran tetap (lebih disukai didefinisikan sebagai konstanta) dan kemudian meneruskan array dan ukuran tetap ke fungsi lainnya. Komentar @John Bode benar, dan ada cara untuk mengurangi risiko ini. Mereka juga membutuhkan lebih banyak upaya dari Anda untuk menggunakannya.

Dalam pengalaman saya, nilai yang saya inisialisasi char[]ke biasanya terlalu kecil untuk nilai-nilai lain yang perlu saya tempatkan di sana. Menggunakan konstanta yang didefinisikan membantu menghindari masalah itu.


sizeof stringakan memberi Anda ukuran buffer (8 byte); gunakan hasil ekspresi itu alih-alih strlensaat Anda khawatir tentang memori.
Demikian pula, Anda dapat membuat cek sebelum panggilan untuk strcpyuntuk melihat jika target buffer Anda cukup besar untuk string sumber: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Ya, jika Anda harus melewati array ke fungsi, Anda harus lulus ukuran fisik juga: foo (array, sizeof array / sizeof *array);. - John Bode

Komunitas
sumber
2
sizeof stringakan memberi Anda ukuran buffer (8 byte); gunakan hasil ekspresi itu alih-alih strlensaat Anda khawatir tentang memori. Demikian pula, Anda dapat membuat cek sebelum panggilan untuk strcpyuntuk melihat jika target buffer Anda cukup besar untuk string sumber: if (sizeof target > strlen(src)) { strcpy (target, src); }. Ya, jika Anda harus melewati array ke fungsi, Anda harus lulus ukuran fisik juga: foo (array, sizeof array / sizeof *array);.
John Bode
1
@JohnBode - terima kasih, dan itu adalah poin yang bagus. Saya telah memasukkan komentar Anda ke dalam jawaban saya.
1
Lebih tepatnya, sebagian besar referensi ke nama array stringmenghasilkan konversi implisit ke char*, menunjuk ke elemen pertama array. Ini kehilangan informasi batas array. Panggilan fungsi hanyalah salah satu dari banyak konteks di mana ini terjadi. char *ptr = string;adalah yang lain. Bahkan string[0]adalah contoh dari ini; yang []Operator bekerja pada pointer, tidak langsung pada array. Disarankan membaca: Bagian 6 dari comp.lang.c FAQ .
Keith Thompson
Akhirnya jawaban yang sebenarnya merujuk pada pertanyaan!
mastov
2

Saya pikir ide "praktik buruk" berasal dari kenyataan bahwa bentuk ini:

char string[] = "october is a nice month";

secara implisit membuat strcpy dari kode mesin sumber ke stack.

Lebih efisien untuk hanya menangani tautan ke string itu. Suka dengan:

char *string = "october is a nice month";

atau langsung:

strcpy(output, "october is a nice month");

(tapi tentu saja di sebagian besar kode mungkin tidak masalah)

toto
sumber
Bukankah itu hanya akan membuat salinan jika Anda mencoba memodifikasinya? Saya pikir kompiler akan lebih pintar dari itu
Cole Johnson
1
Bagaimana dengan kasus-kasus seperti di char time_buf[] = "00:00";mana Anda akan memodifikasi buffer? A char *diinisialisasi ke string literal diatur ke alamat byte pertama, jadi mencoba untuk memodifikasinya menghasilkan perilaku yang tidak terdefinisi karena metode penyimpanan string literal tidak diketahui (implementasi didefinisikan), sementara memodifikasi byte a char[]adalah legal karena inisialisasi menyalin byte ke ruang tulis yang dialokasikan pada stack. Untuk mengatakan bahwa itu "kurang efisien" atau "praktik buruk" tanpa menguraikan nuansa char* vs char[]yang menyesatkan.
Braden Best
-3

Tidak pernah terlalu lama, tetapi Anda harus menghindari inisialisasi char [] ke string, karena, "string" adalah const char *, dan Anda menugaskannya ke char *. Jadi, jika Anda meneruskan karakter ini [] ke metode yang mengubah data, Anda dapat memiliki perilaku yang menarik.

Seperti yang saya katakan, saya mencampur sedikit char [] dengan char *, itu tidak bagus karena mereka sedikit berbeda.

Tidak ada yang salah tentang menugaskan data ke array char, tetapi karena niat menggunakan array ini adalah menggunakannya sebagai 'string' (char *), mudah untuk melupakan bahwa Anda tidak boleh memodifikasi array ini.

Dainius
sumber
3
Salah. Inisialisasi menyalin isi string literal ke dalam array. Objek array tidak constkecuali Anda mendefinisikannya seperti itu. (Dan string literal dalam C tidak const, meskipun setiap upaya untuk memodifikasi string literal memang memiliki perilaku yang tidak terdefinisi.) char *s = "literal";Memang memiliki jenis perilaku yang Anda bicarakan; lebih baik ditulis sebagaiconst char *s = "literal";
Keith Thompson
memang salahku, aku mencampur char [] dengan char *. Tapi saya tidak akan begitu yakin tentang menyalin konten ke array. Pemeriksaan cepat dengan kompiler MS C menunjukkan bahwa 'char c [] = "asdf";' akan membuat 'string' di segmen const dan kemudian menetapkan alamat ini ke variabel array. Itu sebenarnya alasan mengapa saya mengatakan tentang menghindari tugas ke array non const char.
Dainius
Saya skeptis. Coba program ini dan beri tahu saya hasil apa yang Anda dapatkan.
Keith Thompson
2
"Dan secara umum" asdf "adalah konstanta, jadi harus dinyatakan sebagai const." - Alasan yang sama akan membutuhkan conston int n = 42;, karena 42konstan.
Keith Thompson
1
Tidak masalah mesin apa yang Anda pakai. Standar bahasa menjamin yang cdapat dimodifikasi. Ini adalah jaminan yang sama kuatnya dengan yang 1 + 1dievaluasi 2. Jika program yang saya tautkan di atas melakukan apa pun selain mencetak EFGH, ini menunjukkan implementasi C yang tidak sesuai.
Keith Thompson