Apa perbedaan antara char s [] dan char * s?

506

Di C, seseorang dapat menggunakan string literal dalam deklarasi seperti ini:

char s[] = "hello";

atau seperti ini:

char *s = "hello";

Jadi apa bedanya? Saya ingin tahu apa yang sebenarnya terjadi dalam hal durasi penyimpanan, baik pada saat kompilasi maupun saat dijalankan.

StoryTeller - Unslander Monica
sumber
8
char * s = "hello", di sini s dapat menunjuk string lain pada saat run time Maksud saya bukan pointer konstan Anda dapat menetapkan nilai lain pada run time p = "Nishant", sementara s [] di sini s adalah pointer konstan .. ..it tidak dapat menetapkan ulang string lain tetapi kita dapat menetapkan nilai karakter lain di s [indeks].
Nishant Kumar

Jawaban:

541

Perbedaannya di sini adalah itu

char *s = "Hello world";

akan ditempatkan "Hello world"di bagian read-only memori , dan membuat spointer ke yang membuat operasi penulisan pada memori ini ilegal.

Saat melakukan:

char s[] = "Hello world";

menempatkan string literal dalam memori read-only dan menyalin string ke memori yang baru dialokasikan di stack. Dengan demikian membuat

s[0] = 'J';

hukum.

Rickard
sumber
22
String literal "Hello world"ada di "bagian read-only memori" dalam kedua contoh. Contoh dengan titik-titik array di sana, contoh dengan array menyalin karakter ke elemen array.
Pukul
28
pmg: Dalam kasus kedua string literal tidak selalu ada dalam memori sebagai objek yang bersebelahan - itu hanya penginisialisasi, kompiler dapat secara resonansi memancarkan serangkaian instruksi "muat byte langsung" yang berisi nilai karakter yang tertanam di dalam mereka.
caf
10
Contoh array char tidak selalu menempatkan string pada stack - jika muncul di tingkat file, mungkin akan menjadi semacam segmen data yang diinisialisasi.
caf
9
Saya ingin menunjukkan bahwa char s = "xx" tidak memiliki berada di memori read-only (beberapa implementasi tidak memiliki MMU, misalnya). Konsep n1362 c1x dengan sederhana menyatakan bahwa memodifikasi array seperti itu menyebabkan perilaku yang tidak terdefinisi. Tapi +1 tetap, karena mengandalkan perilaku itu adalah hal yang konyol untuk dilakukan.
paxdiablo
3
Saya mendapatkan kompilasi bersih pada file yang berisi hanya char msg[] = "hello, world!"; string yang berakhir di bagian data yang diinisialisasi. Ketika dinyatakan char * constberakhir di bagian data hanya baca. gcc-4.5.3
gcbenison
152

Pertama, dalam argumen fungsi, keduanya persis sama:

void foo(char *x);
void foo(char x[]); // exactly the same in all respects

Dalam konteks lain, char *alokasikan pointer, sementara char []alokasikan array. Di mana string pergi dalam kasus sebelumnya, Anda bertanya? Compiler diam-diam mengalokasikan array anonim statis untuk menahan string literal. Begitu:

char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;

Perhatikan bahwa Anda tidak boleh mencoba mengubah konten array anonim ini melalui pointer ini; efeknya tidak terdefinisi (sering berarti crash):

x[1] = 'O'; // BAD. DON'T DO THIS.

Menggunakan sintaks array secara langsung mengalokasikannya ke memori baru. Dengan demikian modifikasi aman:

char x[] = "Foo";
x[1] = 'O'; // No problem.

Namun array hanya hidup selama ruang lingkup yang sesuai, jadi jika Anda melakukan ini dalam suatu fungsi, jangan kembali atau membocorkan pointer ke array ini - membuat salinan sebagai gantinya dengan strdup()atau serupa. Jika array dialokasikan dalam ruang lingkup global, tentu saja, tidak ada masalah.

omong kosong
sumber
72

Deklarasi ini:

char s[] = "hello";

Menciptakan satu objek - chararray ukuran 6, disebut s, diinisialisasi dengan nilai-nilai 'h', 'e', 'l', 'l', 'o', '\0'. Di mana array ini dialokasikan dalam memori, dan berapa lama itu hidup, tergantung di mana deklarasi muncul. Jika deklarasi berada dalam suatu fungsi, ia akan hidup sampai akhir blok yang dideklarasikan, dan hampir pasti dialokasikan pada stack; jika itu di luar fungsi, itu mungkin akan disimpan dalam "segmen data yang diinisialisasi" yang diambil dari file yang dapat dieksekusi ke dalam memori yang dapat ditulisi ketika program dijalankan.

Di sisi lain, deklarasi ini:

char *s ="hello";

Menciptakan dua objek:

  • sebuah read-only array 6 chars yang mengandung nilai-nilai 'h', 'e', 'l', 'l', 'o', '\0', yang tidak memiliki nama dan memiliki durasi penyimpanan statis (yang berarti bahwa ia hidup untuk seluruh kehidupan program); dan
  • sebuah variabel bertipe pointer-to-char, dipanggil s, yang diinisialisasi dengan lokasi karakter pertama dalam array read-only yang tidak disebutkan namanya itu.

Array read-only yang tidak disebutkan namanya biasanya terletak di segmen "teks" dari program, yang berarti ia dimuat dari disk ke memori read-only, bersama dengan kode itu sendiri. Lokasi svariabel pointer di memori tergantung pada tempat deklarasi muncul (seperti pada contoh pertama).

kaf
sumber
1
Dalam kedua deklarasi untuk "halo" memori dialokasikan pada waktu yang bersamaan?. Dan satu hal lagi char * p = "halo" di sini "halo" disimpan dalam segmen teks seperti yang Anda nyatakan dalam jawaban Anda ... dan bagaimana dengan karakter s [] = "halo" akankah itu juga menyimpan pertama di bagian segmen teks dan selama waktu berjalan itu akan menyalin dalam tumpukan seperti yang dinyatakan Rickard dalam jawaban di sana. tolong jelaskan hal ini.
Nishant Kumar
2
@Nishant: Dalam char s[] = "hello"kasus ini, "hello"ini hanya merupakan penginisialisasi memberitahu kompiler bagaimana array harus diinisialisasi. Ini mungkin atau mungkin tidak menghasilkan string yang sesuai di segmen teks - misalnya, jika smemiliki durasi penyimpanan statis maka kemungkinan bahwa satu-satunya contoh "hello"akan berada di segmen data yang diinisialisasi - objek situ sendiri. Bahkan jika smemiliki durasi penyimpanan otomatis, ini dapat diinisialisasi dengan urutan penyimpanan literal daripada salinan (mis. movl $1819043176, -6(%ebp); movw $111, -2(%ebp)).
caf
Lebih tepatnya, GCC 4.8 memasukkannya ke dalam .rodata, yang kemudian skrip linker dibuang ke segmen yang sama .text. Lihat jawaban saya .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@caf Pada jawaban pertama oleh Rickard, Ada tertulis yang char s[] = "Hello world";menempatkan string literal dalam memori read-only dan menyalin string ke memori yang baru dialokasikan di stack. Tapi, jawaban Anda hanya berbicara tentang put string literal dalam memori read-only dan melompat bagian kedua dari kalimat yang mengatakan: copies the string to newly allocated memory on the stack. Jadi, apakah jawaban Anda tidak lengkap karena tidak menentukan bagian kedua?
KPMG
1
@AjaySinghNegi: Seperti yang telah saya nyatakan di komentar lain (untuk jawaban ini, dan jawaban Rickard), string di char s[] = "Hellow world";hanyalah penginisialisasi dan tidak harus disimpan sebagai salinan read-only terpisah sama sekali. Jika smemiliki durasi penyimpanan statis maka satu-satunya salinan string kemungkinan berada dalam segmen baca-tulis di lokasi s, dan bahkan jika tidak maka kompiler dapat memilih untuk menginisialisasi array dengan instruksi load-direct atau serupa daripada menyalin. dari string hanya baca. Intinya adalah bahwa dalam kasus ini, string initializer itu sendiri tidak memiliki kehadiran runtime.
caf
60

Diberikan deklarasi

char *s0 = "hello world";
char s1[] = "hello world";

mengasumsikan peta memori hipotetis berikut:

                    0x01 0x02 0x03 0x04
        0x00008000: 'h' 'e' 'l' 'l'
        0x00008004: 'o' '' 'w' 'o'
        0x00008008: 'r' 'l' 'd' 0x00
        ...
s0: 0x00010000: 0x00 0x00 0x80 0x00
s1: 0x00010004: 'h' 'e' 'l' 'l'
        0x00010008: 'o' '' 'w' 'o'
        0x0001000C: 'r' 'l' 'd' 0x00

String literal "hello world"adalah array 12-elemen dari char( const chardalam C ++) dengan durasi penyimpanan statis, yang berarti bahwa memori untuk itu dialokasikan ketika program dijalankan dan tetap dialokasikan sampai program berakhir. Mencoba mengubah isi string literal memunculkan perilaku yang tidak terdefinisi.

Garis

char *s0 = "hello world";

mendefinisikan s0sebagai penunjuk ke chardengan durasi penyimpanan otomatis (artinya variabel s0hanya ada untuk lingkup yang dideklarasikan) dan menyalin alamat string literal ( 0x00008000dalam contoh ini) ke sana. Perhatikan bahwa sejak s0menunjuk ke string literal, itu tidak boleh digunakan sebagai argumen untuk fungsi apa pun yang akan mencoba memodifikasinya (misalnya,strtok() , strcat(), strcpy(), dll).

Garis

char s1[] = "hello world";

mendefinisikan s1sebagai array 12-elemen dari char(panjang diambil dari string literal) dengan durasi penyimpanan otomatis dan menyalin isi literal ke array. Seperti yang dapat Anda lihat dari peta memori, kami memiliki dua salinan string "hello world"; perbedaannya adalah Anda dapat memodifikasi string yang ada di dalamnyas1 .

s0dan s1dapat dipertukarkan dalam sebagian besar konteks; ini pengecualiannya:

sizeof s0 == sizeof (char*)
sizeof s1 == 12

type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char

Anda bisa menetapkan ulang variabel s0untuk menunjuk ke string string yang berbeda atau ke variabel lain. Anda tidak dapat menetapkan kembali variabel s1untuk menunjuk ke array yang berbeda.

John Bode
sumber
2
Saya pikir peta memori hipotetis membuatnya mudah dimengerti!
midnightBlue
32

C99 N1256 konsep

Ada dua penggunaan literal string karakter yang berbeda:

  1. Inisialisasi char[]:

    char c[] = "abc";      

    Ini "lebih banyak sihir", dan dijelaskan pada 6.7.8 / 14 "Inisialisasi":

    Array tipe karakter dapat diinisialisasi dengan string karakter literal, secara opsional tertutup dalam kurung. Karakter yang berurutan dari string karakter literal (termasuk karakter null terminating jika ada ruang atau jika array berukuran tidak diketahui) menginisialisasi elemen array.

    Jadi ini hanyalah jalan pintas untuk:

    char c[] = {'a', 'b', 'c', '\0'};

    Seperti array reguler lainnya, cdapat dimodifikasi.

  2. Di tempat lain: ia menghasilkan:

    Jadi ketika Anda menulis:

    char *c = "abc";

    Ini mirip dengan:

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;

    Perhatikan pemeran implisit dari char[]ke char *, yang selalu sah.

    Kemudian jika Anda memodifikasi c[0], Anda juga memodifikasi __unnamed, yaitu UB.

    Ini didokumentasikan pada 6.4.5 "String literal":

    5 Dalam fase terjemahan 7, byte atau kode bernilai nol ditambahkan ke setiap urutan karakter multibyte yang dihasilkan dari string literal atau literal. Urutan karakter multibyte kemudian digunakan untuk menginisialisasi array durasi penyimpanan statis dan panjang hanya cukup untuk mengandung urutan. Untuk literal karakter string, elemen array memiliki tipe char, dan diinisialisasi dengan byte individu dari urutan karakter multibyte [...]

    6 Tidak ditentukan apakah array ini berbeda asalkan elemen mereka memiliki nilai yang sesuai. Jika program mencoba untuk mengubah susunan seperti itu, perilaku tidak terdefinisi.

6.7.8 / 32 "Inisialisasi" memberikan contoh langsung:

CONTOH 8: Deklarasi

char s[] = "abc", t[3] = "abc";

mendefinisikan objek array char "biasa" sdant yang elemennya diinisialisasi dengan literal karakter string

Deklarasi ini identik dengan

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

Isi dari array dapat dimodifikasi. Di sisi lain, deklarasi

char *p = "abc";

mendefinisikan pdengan tipe "pointer to char" dan menginisialisasi untuk menunjuk ke objek dengan tipe "array of char" dengan panjang 4 yang elemennya diinisialisasi dengan karakter string literal. Jika ada upaya yang dilakukan untuk pmengubah konten array, perilaku tersebut tidak ditentukan.

Implementasi ELF GCC 4,8 x86-64

Program:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

Kompilasi dan dekompilasi:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

Output berisi:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

Kesimpulan: GCC menyimpannya char*di .rodatabagian, bukan di.text .

Namun perlu dicatat bahwa skrip tautan default menempatkan .rodatadan .textdi segmen yang sama , yang telah menjalankan tetapi tidak memiliki izin menulis. Ini dapat diamati dengan:

readelf -l a.out

yang mengandung:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Jika kami melakukan hal yang sama untuk char[]:

 char s[] = "abc";

kami memperoleh:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

sehingga disimpan di stack (relatif terhadap %rbp).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
15
char s[] = "hello";

mendeklarasikan smenjadi array charyang cukup panjang untuk menampung initializer (5 +1 chars) dan menginisialisasi array dengan menyalin anggota string string yang diberikan ke dalam array.

char *s = "hello";

menyatakan ssebagai penunjuk ke satu atau lebih (dalam hal ini lebih) chardan mengarahkannya langsung ke lokasi tetap (hanya baca) yang berisi literal "hello".

CB Bailey
sumber
1
Metode apa yang lebih disukai untuk digunakan dalam fungsi jika s tidak akan diubah, f (const char s []) atau f (const char * s)?
psihodelia
1
@psihodelia: Dalam deklarasi fungsi tidak ada perbedaan. Dalam kedua kasus sadalah pointer ke const char.
CB Bailey
4
char s[] = "Hello world";

Di sini, sadalah array karakter, yang dapat ditimpa jika kita inginkan.

char *s = "hello";

String literal digunakan untuk membuat blok karakter ini di suatu tempat di memori yang sditunjuk oleh pointer ini. Di sini kita dapat menetapkan ulang objek yang ditunjuknya dengan mengubah itu, tetapi selama itu menunjuk ke string literal blok karakter yang ditunjukkannya tidak dapat diubah.

Sailaja
sumber
@bo Persson Mengapa blok karakter tidak dapat diubah dalam kasus kedua?
Pankaj Mahato
3

Sebagai tambahan, pertimbangkan itu, karena untuk tujuan hanya baca penggunaan keduanya identik, Anda dapat mengakses char dengan mengindeks baik dengan []atau *(<var> + <index>) format:

printf("%c", x[1]);     //Prints r

Dan:

printf("%c", *(x + 1)); //Prints r

Tentunya, jika Anda berusaha melakukannya

*(x + 1) = 'a';

Anda mungkin akan mendapatkan Kesalahan Segmentasi, karena Anda mencoba mengakses memori hanya baca.

Nick Louloudakis
sumber
Ini sama sekali tidak berbeda dari x[1] = 'a';yang akan segfault juga (tergantung pada platform, tentu saja).
glglgl
3

Hanya untuk menambahkan: Anda juga mendapatkan nilai berbeda untuk ukurannya.

printf("sizeof s[] = %zu\n", sizeof(s));  //6
printf("sizeof *s  = %zu\n", sizeof(s));  //4 or 8

Seperti disebutkan di atas, untuk sebuah array '\0'akan dialokasikan sebagai elemen terakhir.

Muzab
sumber
2
char *str = "Hello";

Set di atas str untuk menunjuk ke nilai literal "Hello" yang dikodekan dalam gambar biner program, yang ditandai sebagai hanya-baca dalam memori, berarti setiap perubahan dalam string literal ini ilegal dan yang akan menyebabkan kesalahan segmentasi.

char str[] = "Hello";

menyalin string ke memori yang baru dialokasikan di stack. Dengan demikian membuat perubahan apa pun di dalamnya diizinkan dan sah.

means str[0] = 'M';

akan mengubah str ke "Mello".

Untuk detail lebih lanjut, silakan lihat pertanyaan serupa:

Mengapa saya mendapatkan kesalahan segmentasi saat menulis ke string yang diinisialisasi dengan "char * s" tetapi bukan "char s []"?

Mohit
sumber
0

Dalam kasus:

char *x = "fred";

x adalah nilai - itu dapat ditugaskan untuk. Tetapi dalam hal:

char x[] = "fred";

x bukan lvalue, ini adalah rvalue - Anda tidak dapat menetapkannya.

Lee-Man
sumber
3
Secara teknis, xadalah nilai yang tidak dapat dimodifikasi. Di hampir semua konteks meskipun, itu akan mengevaluasi untuk pointer ke elemen pertama, dan bahwa nilai adalah nilai p.
caf
0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal
Atul
sumber
-1

Dalam terang komentar di sini harus jelas bahwa: char * s = "hello"; Adalah ide yang buruk, dan harus digunakan dalam lingkup yang sangat sempit.

Ini mungkin merupakan kesempatan yang baik untuk menunjukkan bahwa "kebenaran konst" adalah "hal yang baik". Kapan pun dan di mana pun Anda bisa, gunakan kata kunci "const" untuk melindungi kode Anda, dari penelepon atau programmer yang "santai", yang biasanya paling "santai" ketika pointer mulai digunakan.

Cukup melodrama, di sini adalah apa yang bisa dicapai ketika menghiasi pointer dengan "const". (Catatan: Seseorang harus membaca deklarasi pointer dari kanan ke kiri.) Berikut adalah 3 cara berbeda untuk melindungi diri sendiri ketika bermain dengan pointer:

const DBJ* p means "p points to a DBJ that is const" 

- yaitu, objek DBJ tidak dapat diubah melalui hal.

DBJ* const p means "p is a const pointer to a DBJ" 

- yaitu, Anda dapat mengubah objek DBJ melalui p, tetapi Anda tidak dapat mengubah pointer p itu sendiri.

const DBJ* const p means "p is a const pointer to a const DBJ" 

- yaitu, Anda tidak dapat mengubah pointer p itu sendiri, Anda juga tidak dapat mengubah objek DBJ melalui hal.

Kesalahan terkait dengan upaya mutasi const-semut ditangkap pada waktu kompilasi. Tidak ada ruang runtime atau penalti kecepatan untuk const.

(Asumsi apakah Anda menggunakan kompiler C ++, tentu saja?)

--DBJ


sumber
Ini semua benar, tetapi tidak ada hubungannya dengan pertanyaan. Dan sejauh asumsi Anda tentang kompiler C ++, pertanyaannya ditandai sebagai C, bukan sebagai C ++.
Fabio mengatakan Reinstate Monica
Tidak ada yang buruk tentang char * s = "const string";
Paul Smith