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

287

Kode berikut menerima kesalahan seg pada baris 2:

char *str = "string";
str[0] = 'z';  // could be also written as *str = 'z'
printf("%s\n", str);

Sementara ini bekerja dengan sangat baik:

char str[] = "string";
str[0] = 'z';
printf("%s\n", str);

Diuji dengan MSVC dan GCC.

Markus
sumber
1
Ini lucu - tetapi ini sebenarnya mengkompilasi dan berjalan dengan sempurna ketika menggunakan kompiler windows (cl) pada prompt perintah pengembang visual studio. Membuat saya bingung untuk beberapa saat ...
David Refaeli

Jawaban:

241

Lihat C FAQ, Pertanyaan 1.32

T : Apa perbedaan antara inisialisasi ini?
char a[] = "string literal";
char *p = "string literal";
Program saya mogok jika saya mencoba menetapkan nilai baru p[i].

A : String literal (istilah formal untuk string kutipan ganda dalam sumber C) dapat digunakan dalam dua cara yang sedikit berbeda:

  1. Sebagai penginisialisasi untuk array char, seperti dalam deklarasi char a[], ia menentukan nilai awal karakter dalam array itu (dan, jika perlu, ukurannya).
  2. Di tempat lain, itu berubah menjadi array karakter statis yang tidak disebutkan namanya, dan array yang tidak disebutkan namanya ini dapat disimpan dalam memori read-only, dan yang karenanya tidak dapat serta merta dimodifikasi. Dalam konteks ekspresi, array dikonversi sekaligus menjadi pointer, seperti biasa (lihat bagian 6), jadi deklarasi kedua menginisialisasi p untuk menunjuk ke elemen pertama array yang tidak disebutkan namanya itu.

Beberapa kompiler memiliki saklar yang mengontrol apakah literal string dapat ditulis atau tidak (untuk mengkompilasi kode lama), dan beberapa mungkin memiliki opsi untuk menyebabkan string literal diperlakukan secara formal sebagai array dari const char (untuk menangkap kesalahan yang lebih baik).

matli
sumber
7
Beberapa poin lainnya: (1) segfault terjadi seperti yang dijelaskan, tetapi kemunculannya adalah fungsi dari lingkungan yang dijalankan; jika kode yang sama berada di sistem yang disematkan, penulisan mungkin tidak berpengaruh, atau mungkin benar-benar mengubah s menjadi z. (2) Karena string literal tidak dapat ditulis, kompiler dapat menghemat ruang dengan meletakkan dua instance "string" di tempat yang sama; atau, jika di tempat lain dalam kode Anda memiliki "string lain", maka satu keping memori dapat mendukung kedua literal. Jelas, jika kode kemudian diizinkan untuk mengubah byte itu, bug aneh dan sulit bisa terjadi.
greggo
1
@ Greggo: Poin bagus. Ada juga cara untuk melakukan ini pada sistem dengan MMU dengan menggunakan mprotectuntuk melambangkan perlindungan read-only (lihat di sini ).
Jadi char * p = "blah" sebenarnya membuat array sementara? Aneh.
rahul tyagi
1
Dan setelah 2 tahun menulis dalam C ++ ... TIL
zeboidlund
@rahultyagi apa maksudmu?
Suraj Jain
105

Biasanya, string literal disimpan dalam memori read-only ketika program dijalankan. Ini untuk mencegah Anda mengubah konstanta string secara tidak sengaja. Dalam contoh pertama Anda, "string"disimpan dalam memori hanya-baca dan *strmenunjuk ke karakter pertama. Segfault terjadi ketika Anda mencoba mengubah karakter pertama menjadi'z' .

Dalam contoh kedua, string "string"yang disalin oleh compiler dari rumah read-only kepada str[]larik. Kemudian mengubah karakter pertama diizinkan. Anda dapat memeriksa ini dengan mencetak alamat masing-masing:

printf("%p", str);

Juga, mencetak ukuran strpada contoh kedua akan menunjukkan kepada Anda bahwa kompiler telah mengalokasikan 7 byte untuknya:

printf("%d", sizeof(str));
Greg Hewgill
sumber
13
Setiap kali menggunakan "% p" pada printf, Anda harus membuang pointer ke void * seperti pada printf ("% p", (void *) str); Saat mencetak size_t dengan printf, Anda harus menggunakan "% zu" jika menggunakan standar C terbaru (C99).
Chris Young
4
Juga, tanda kurung dengan sizeof hanya diperlukan ketika mengambil ukuran tipe (argumen kemudian terlihat seperti pemain). Ingat bahwa sizeof adalah operator, bukan fungsi.
bersantai
34

Sebagian besar jawaban ini benar, tetapi hanya untuk menambah sedikit kejelasan ...

"Memori baca saja" yang dirujuk orang adalah segmen teks dalam istilah ASM. Itu adalah tempat yang sama di memori tempat instruksi dimuat. Ini hanya-baca untuk alasan yang jelas seperti keamanan. Saat Anda membuat karakter * diinisialisasi ke string, data string dikompilasi ke dalam segmen teks dan program menginisialisasi pointer untuk menunjuk ke dalam segmen teks. Jadi jika Anda mencoba mengubahnya, kaboom. Segfault.

Ketika ditulis sebagai array, kompiler menempatkan data string yang diinisialisasi dalam segmen data sebagai gantinya, yang merupakan tempat yang sama dengan variabel global Anda dan live tersebut. Memori ini bisa berubah, karena tidak ada instruksi di segmen data. Kali ini ketika kompilator menginisialisasi array karakter (yang masih hanya char *) itu menunjuk ke segmen data daripada segmen teks, yang dapat Anda ubah dengan aman saat dijalankan.

Bob Somers
sumber
Tapi bukankah benar bahwa ada implementasi yang memungkinkan memodifikasi "memori read-only"?
Pacerier
Ketika ditulis sebagai array, kompiler menempatkan data string yang diinisialisasi dalam segmen data jika mereka statis atau global. Kalau tidak (mis. Untuk susunan otomatis normal) ia letakkan di tumpukan, di bingkai tumpukan fungsi utama. Benar?
SE
26

Mengapa saya mendapatkan kesalahan segmentasi saat menulis ke string?

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-karakter berturut-turut dari string karakter literal (termasuk karakter null terminating jika ada ruang atau jika array berukuran tidak diketahui) menginisialisasi elemen-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[]hinggachar * , 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 memodifikasi array seperti itu, perilaku tidak akan ditentukan.

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 isi 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.

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 ).

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
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
17

Dalam kode pertama, "string" adalah konstanta string, dan konstanta string tidak boleh dimodifikasi karena sering dimasukkan ke dalam memori read only. "str" ​​adalah pointer yang digunakan untuk memodifikasi konstanta.

Dalam kode kedua, "string" adalah penginisialisasi array, semacam kependekan dari

char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '\0' };

"str" ​​adalah array yang dialokasikan pada stack dan dapat dimodifikasi secara bebas.

Andru Luvisi
sumber
1
Di tumpukan, atau segmen data jika strglobal atau static.
Gauthier
12

Karena tipe "whatever"dalam konteks contoh pertama adalah const char *(bahkan jika Anda menugaskannya ke karakter non-const *), yang berarti Anda tidak boleh mencoba dan menulisnya.

Compiler telah memberlakukan ini dengan meletakkan string di bagian read-only memori, karenanya menulis kepadanya menghasilkan segfault.


sumber
8

Untuk memahami kesalahan atau masalah ini, Anda harus terlebih dahulu tahu perbedaan b / w pointer dan array jadi di sini pertama-tama saya telah menjelaskan perbedaan Anda b / w mereka

array string

 char strarray[] = "hello";

Dalam memori array disimpan dalam sel memori terus menerus, disimpan sebagai [h][e][l][l][o][\0] =>[]adalah 1 Char sel ukuran byte memori, dan ini sel-sel memori terus menerus dapat diakses dengan nama bernama strArray here.so sini array string strarrayitu sendiri berisi semua karakter string diinisialisasi ke it.In ini terjadi di sini "hello" sehingga kita dapat dengan mudah mengubah konten memorinya dengan mengakses setiap karakter dengan nilai indeksnya

`strarray[0]='m'` it access character at index 0 which is 'h'in strarray

dan nilainya berubah menjadi 'm'nilai strarray berubah menjadi "mello";

satu hal yang perlu diperhatikan di sini adalah bahwa kita dapat mengubah isi string array dengan mengubah karakter demi karakter tetapi tidak dapat menginisialisasi string lain secara langsung seperti strarray="new string"itu tidak valid

Pointer

Seperti kita ketahui titik penunjuk menunjuk ke lokasi memori dalam memori, penunjuk yang tidak diinisialisasi menunjuk ke lokasi memori acak sehingga dan setelah inisialisasi menunjuk ke lokasi memori tertentu

char *ptr = "hello";

di sini pointer ptr diinisialisasi ke string "hello"yang string konstan disimpan dalam memori hanya baca (ROM) sehingga "hello"tidak dapat diubah karena disimpan dalam ROM

dan ptr disimpan di bagian tumpukan dan menunjuk ke string konstan "hello"

jadi ptr [0] = 'm' tidak valid karena Anda tidak dapat mengakses memori hanya baca

Tetapi ptr dapat diinisialisasi ke nilai string lainnya secara langsung karena itu hanya pointer sehingga dapat menunjuk ke alamat memori variabel variabel tipe datanya

ptr="new string"; is valid
Komunitas
sumber
7
char *str = "string";  

Set di atas strmenunjukkan nilai literal"string" yang dikodekan secara keras dalam gambar biner program, yang mungkin ditandai sebagai hanya-baca dalam memori.

Jadi str[0]=sedang mencoba untuk menulis ke kode read-only dari aplikasi. Saya kira ini mungkin tergantung pada kompiler.

DougN
sumber
6
char *str = "string";

mengalokasikan pointer ke string literal, yang diletakkan oleh kompiler di bagian yang tidak dapat dimodifikasi dari executable Anda;

char str[] = "string";

mengalokasikan dan menginisialisasi array lokal yang dapat dimodifikasi

Rob Walker
sumber
dapatkah kita menulis int *b = {1,2,3) seperti kita menulis char *s = "HelloWorld"?
Suraj Jain
6

FAQ C yang @matli ditautkan dengan menyebutkannya, tetapi belum ada orang lain di sini, jadi untuk klarifikasi: jika string literal (string kutipan ganda di sumber Anda) digunakan di mana pun selain untuk menginisialisasi array karakter (yaitu: @ Contoh kedua Mark, yang bekerja dengan benar), string itu disimpan oleh kompiler dalam spesial tabel string statis , yang mirip dengan membuat variabel statis global (read-only, tentu saja) yang pada dasarnya anonim (tidak memiliki variabel "nama "). Bagian read-only adalah bagian yang penting, dan itulah sebabnya contoh kode pertama Mark @ adalah.

rpj
sumber
dapatkah kita menulis int *b = {1,2,3) seperti kita menulis char *s = "HelloWorld"?
Suraj Jain
4

Itu

 char *str = "string";

line mendefinisikan pointer dan mengarahkannya ke string literal. String literal tidak dapat ditulis sehingga saat Anda melakukannya:

  str[0] = 'z';

Anda mendapatkan kesalahan seg. Pada beberapa platform, literal mungkin berada dalam memori yang dapat ditulisi sehingga Anda tidak akan melihat segfault, tetapi itu kode yang tidak valid (menghasilkan perilaku yang tidak ditentukan) terlepas.

Garis:

char str[] = "string";

mengalokasikan array karakter dan menyalin string literal ke dalam array itu, yang sepenuhnya dapat ditulis, sehingga pembaruan selanjutnya tidak ada masalah.

Michael Burr
sumber
dapatkah kita menulis int *b = {1,2,3) seperti kita menulis char *s = "HelloWorld"?
Suraj Jain
3

Literal string seperti "string" mungkin dialokasikan di ruang alamat yang dapat dieksekusi sebagai data hanya baca (beri atau ambil kompiler Anda). Ketika Anda menyentuhnya, itu aneh bahwa Anda berada di area pakaian renang dan membiarkan Anda tahu dengan kesalahan seg.

Dalam contoh pertama Anda, Anda mendapatkan pointer ke data const itu. Dalam contoh kedua, Anda menginisialisasi array 7 karakter dengan salinan data const.

Jurney
sumber
2
// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";

// create an array of characters like this 
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];

// now we try to change a character in the array first, this will work
*arr_p = 'E';

// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.


/*-----------------------------------------------------------------------------
 *  String constants can't be modified. A segmentation fault is the result,
 *  because most operating systems will not allow a write
 *  operation on read only memory.
 *-----------------------------------------------------------------------------*/

//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array. 
jokeysmurf
sumber
1

Di tempat pertama, stradalah pointer yang menunjuk "string". Kompiler diizinkan untuk menempatkan string literal di tempat-tempat dalam memori yang tidak dapat Anda tulis, tetapi hanya dapat membaca. (Ini seharusnya memicu peringatan, karena Anda menetapkan const char *achar * . Apakah peringatan Anda dinonaktifkan, atau apakah Anda mengabaikannya saja?)

Di tempat kedua, Anda membuat array, yang merupakan memori yang Anda punya akses penuh, dan menginisialisasi dengan itu "string". Anda membuat char[7](enam untuk huruf-huruf, satu untuk mengakhiri '\ 0'), dan Anda melakukan apa pun yang Anda suka dengannya.

David Thornley
sumber
@Ferruccio,? Ya constawalan membuat variabel Read-Only
EsmaeelE
Dalam C string literal memiliki tipe char [N], tidak const char [N], jadi tidak ada peringatan. (Anda dapat mengubahnya dalam gcc setidaknya dengan melewati -Wwrite-strings.)
melpomene
0

Asumsikan stringnya adalah,

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

Dalam kasus pertama, literal harus disalin ketika 'a' masuk ke dalam ruang lingkup. Di sini 'a' adalah array yang didefinisikan pada stack. Ini berarti string akan dibuat pada stack dan datanya disalin dari kode (teks) memori, yang biasanya hanya-baca (ini adalah implementasi khusus, kompiler dapat menempatkan data program read-only ini dalam memori read-writable juga ).

Dalam kasus kedua, p adalah pointer yang didefinisikan pada stack (cakupan lokal) dan merujuk string literal (data program atau teks) yang disimpan di tempat lain. Biasanya memodifikasi memori semacam itu bukanlah praktik yang baik atau dianjurkan.

Venki
sumber
-1

Pertama adalah satu string konstan yang tidak dapat dimodifikasi. Kedua adalah array dengan nilai yang diinisialisasi, sehingga dapat dimodifikasi.

libralhb
sumber
-2

Kesalahan segmentasi disebabkan ketika Anda mencoba mengakses memori yang tidak dapat diakses.

char *str adalah pointer ke string yang tidak dapat dimodifikasi (alasan untuk mendapatkan segfault).

sedangkan char str[]array dan dapat dimodifikasi ..

Raghu Srikanth Reddy
sumber