Bir dizgi değişmeziyle başlatılan ancak "char s []" ile başlatılmayan bir "char * s" öğesine yazarken neden bir segmentasyon hatası alıyorum?


288

Aşağıdaki kod, 2. satırdaki seg hatasını alır:

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

Bu mükemmel bir şekilde çalışıyor olsa da:

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

MSVC ve GCC ile test edilmiştir.


1
Komik - ama bu aslında bir görsel stüdyo geliştirici komut isteminde windows derleyici (cl) kullanırken mükemmel derler ve çalışır. Birkaç
dakikalığına

Yanıtlar:


242

Bkz. C SSS, Soru 1.32

S : Bu başlatmalar arasındaki fark nedir?
char a[] = "string literal";
char *p = "string literal";
Yeni bir değer atamaya çalışırsam programım çöküyor p[i].

C : Bir dize değişmez değeri (C kaynağında çift tırnaklı bir dize için resmi terim) biraz farklı iki şekilde kullanılabilir:

  1. Bir karakter dizisinin başlatıcısı olarak, bildiriminde olduğu gibi char a[], o dizideki karakterlerin (ve gerekirse boyutunun) başlangıç ​​değerlerini belirtir.
  2. Başka herhangi bir yerde, adsız, statik bir karakter dizisine dönüşür ve bu adsız dizi salt okunur bellekte saklanabilir ve bu nedenle mutlaka değiştirilemez. Bir ifade bağlamında, dizi her zamanki gibi bir kerede bir işaretçiye dönüştürülür (bkz. Bölüm 6), bu nedenle ikinci bildirim, adlandırılmamış dizinin ilk öğesine işaret etmek için p'yi başlatır.

Bazı derleyiciler, dizgi değişmezlerinin yazılabilir olup olmadığını kontrol eden bir anahtara sahiptir (eski kodu derlemek için) ve bazılarında dizgi değişmezlerinin resmi olarak const char dizileri olarak ele alınmasına neden olan seçenekler olabilir (daha iyi hata yakalama için).


7
Diğer birkaç nokta: (1) segfault tarif edildiği gibi gerçekleşir, ancak oluşumu çalışma ortamının bir fonksiyonudur; aynı kod gömülü bir sistemdeyse, yazma işleminin bir etkisi olmayabilir veya s'yi z olarak değiştirebilir. (2) Dize değişmezleri yazılabilir olmadığından, derleyici aynı yere iki "dize" örneği yerleştirerek yerden tasarruf edebilir; veya kodda başka bir yerde "başka bir dize" varsa, bir bellek yığını her iki değişmezi de destekleyebilir. Açıkçası, kodun bu baytları değiştirmesine izin verilirse, garip ve zor hatalar meydana gelebilir.
greggo

1
@greggo: İyi nokta. MMU'lu sistemlerde mprotectsalt okunur korumayı dalgalandırmak için bunu yapmanın bir yolu da vardır ( buraya bakın ).

Yani char * p = "blah" aslında geçici bir dizi yaratır mı?
rahul tyagi

1
Ve C ++ 'da 2 yıl yazdıktan sonra ... TIL
zeboidlund

@rahultyagi ne demek istiyorsun?
Suraj Jain

105

Normalde, dizgi değişmezleri program çalıştırıldığında salt okunur bellekte saklanır. Bu, yanlışlıkla bir dize sabitini değiştirmenizi önlemek içindir. İlk örneğinizde, "string"salt okunur bellekte saklanır *strve ilk karakteri gösterir. Segfault, ilk karakteri olarak değiştirmeye çalıştığınızda olur 'z'.

İkinci örnekte, dize "string"olan kopyalanan onun salt okunur evden derleyici tarafından str[]diziden. Daha sonra ilk karakterin değiştirilmesine izin verilir. Her birinin adresini yazdırarak bunu kontrol edebilirsiniz:

printf("%p", str);

Ayrıca, strikinci örnekte boyutunun yazdırılması, derleyicinin bunun için 7 bayt ayırdığını gösterecektir:

printf("%d", sizeof(str));

13
Printf üzerinde "% p" kullanılırken, işaretçiyi printf ("% p", (void *) str) 'de olduğu gibi void *' e atmalısınız; Printf ile bir size_t yazdırırken, en son C standardını (C99) kullanıyorsanız "% zu" kullanmalısınız.
Chris Young

4
Ayrıca, sizeof içeren parantez yalnızca bir türün boyutunu alırken gereklidir (bağımsız değişken bir döküm gibi görünür). Sizeof'in bir işleç değil, bir işleç olduğunu unutmayın.
açma


34

Bu cevapların çoğu doğrudur, ancak sadece biraz daha netlik eklemek için ...

İnsanların bahsettiği "salt okunur bellek" ASM terimlerindeki metin bölümüdür. Hafızada talimatların yüklendiği aynı yer. Bu, güvenlik gibi bariz nedenlerle salt okunurdur. Bir dizeye başlatılan bir char * oluşturduğunuzda, dize verileri metin segmentine derlenir ve program işaretçiyi metin segmentini gösterecek şekilde başlatır. Eğer değiştirmeye çalışırsanız, kaboom. Segfault.

Bir dizi olarak yazıldığında, derleyici başlangıçtaki dize verilerini veri segmentine yerleştirir; bu, global değişkenlerinizin ve bu tür canlıların bulunduğu yerle aynıdır. Bu bellek değiştirilebilir, çünkü veri segmentinde talimat yoktur. Bu kez derleyici karakter dizisini başlattığında (hala sadece bir karakterdir *) metin segmentinden ziyade veri segmentine işaret eder ve çalışma zamanında güvenli bir şekilde değiştirebilirsiniz.


Ancak "salt okunur belleği" değiştirmeye izin veren uygulamalar olabileceği doğru değil mi?
Pacerier

Dizi olarak yazıldığında, derleyici başlatılmış dize verilerini statik veya genelse veri segmentine yerleştirir. Aksi takdirde (örn. Normal otomatik dizi için) yığına, main işlevinin yığın çerçevesine yerleştirir. Doğru?
SE

27

Bir dizeye yazarken neden bir segmentasyon hatası alıyorum?

C99 N1256 taslak

Karakter dizesi değişmezlerinin iki farklı kullanımı vardır:

  1. Başlat char[]:

    char c[] = "abc";      

    Bu "daha sihirli" dir ve 6.7.8 / 14 "Başlatma" bölümünde açıklanmıştır:

    Bir karakter türü dizisi, isteğe bağlı olarak parantez içine alınmış bir karakter dizesi değişmeziyle başlatılabilir. Karakter dizesi değişmezinin ardışık karakterleri (oda varsa veya dizi bilinmeyen boyutta ise son boş karakter dahil) dizinin öğelerini başlatır.

    Yani bu sadece bir kısayol:

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

    Herhangi bir diğer normal dizi gibi, cdeğiştirilebilir.

  2. Başka her yerde: bir üretir:

    Yani yazarken:

    char *c = "abc";

    Bu şuna benzer:

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

    Dan örtülü döküm Not char[]için char *her zaman yasal olduğunu.

    Daha sonra c[0]değiştirirseniz __unnamed, UB olan da değiştirirsiniz .

    Bu, 6.4.5 "Dize değişmez değerleri" nde belgelenmiştir:

    Çeviri aşaması 7'de, bir dize değişmez değeri veya değişmez değerlerden kaynaklanan her çok baytlı karakter dizisine sıfır değerinde bir bayt veya kod eklenir. Daha sonra çok baytlı karakter dizisi, diziyi içermek için yeterli olan bir dizi statik depolama süresi ve uzunluğunu başlatmak için kullanılır. Karakter dizesi değişmezleri için, dizi öğeleri char türüne sahiptir ve çok baytlı karakter dizisinin ayrı baytlarıyla başlatılır [...]

    6 Elemanlarının uygun değerlere sahip olması koşuluyla bu dizilerin farklı olup olmadığı açıklanmamıştır. Program böyle bir diziyi değiştirmeye çalışırsa, davranış tanımsızdır.

6.7.8 / 32 "Başlatma" doğrudan bir örnek verir:

ÖRNEK 8: Beyan

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

"düz" karakter dizisi nesnelerini tanımlar sve töğeleri karakter dizesi değişmezleriyle başlatılır.

Bu beyan aşağıdakilerle aynıdır:

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

Dizilerin içeriği değiştirilebilir. Öte yandan, deklarasyon

char *p = "abc";

p"işaretçi karakterine" türüyle tanımlar ve bunu, karakter karakter dizisi ile başlatılan 4 uzunluklu "karakter dizisi" türüne sahip bir nesneyi işaret edecek şekilde başlatır. pDizinin içeriğini değiştirmek için bir girişimde bulunulursa , davranış tanımsızdır.

GCC 4.8 x86-64 ELF uygulaması

Programı:

#include <stdio.h>

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

Derleme ve derleme:

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

Çıktı şunları içerir:

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

Sonuç: GCC char*bunu .rodatabölümünde değil bölümünde saklar .text.

Eğer aynısını şu şekilde yaparsak char[]:

 char s[] = "abc";

elde ederiz:

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

böylece yığın halinde depolanır ( %rbp ).

Ancak unutmayın varsayılan bağlayıcı komut koyar .rodatave .textyürütmek sahip aynı segmentte, ancak hiçbir yazma izni de. Bu aşağıdakilerle gözlemlenebilir:

readelf -l a.out

içeren:

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

17

İlk kodda, "string" bir dize sabitidir ve dize sabitleri hiçbir zaman değiştirilmemelidir çünkü bunlar genellikle salt okunur belleğe yerleştirilir. "str", sabiti değiştirmek için kullanılan bir işaretçidir.

İkinci kodda, "string" bir dizi başlatıcıdır.

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

"str", yığına ayrılan bir dizidir ve serbestçe değiştirilebilir.


1
Yığında veya strglobal veya veri segmentinde ise static.
Gauthier

12

Çünkü türü "whatever"1. örnek bağlamındakiconst char * const olmayan bir karaktere atasanız bile), yani denemeye ve yazmaya çalışmamanız gerektiği anlamına gelir.

Derleyici bunu dizeyi belleğin salt okunur bir kısmına koyarak zorladı, böylece bir segfault oluşturur.


8

Bu hatayı veya problemi anlamak için önce işaretçiyi ve diziyi b / w farkını bilmelisiniz, bu yüzden burada öncelikle size b / w farklarını açıklayacağım

dize dizisi

 char strarray[] = "hello";

Anısına dizisi, sürekli hafıza hücrelerinde depolanan depolanmış olarak bir [h][e][l][l][o][\0] =>[]1 karakter bayt boyutu hafıza hücresi olduğunu ve dize dizisi bu sürekli hafıza hücreleri strArray here.so burada adlandırılmış adıyla erişim olabilir strarraydizenin tüm karakterleri içeren kendisi bu it.in için başlatılır Burada "hello" her bir karaktere dizin değerine erişerek bellek içeriğini kolayca değiştirebiliriz

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

değeri değiştirildi ve 'm'böylece strarray değeri şu şekilde değiştirildi "mello";

karakter dizisi karakterini değiştirerek dize dizisinin içeriğini değiştirebileceğimizi, ancak diğer dizeyi doğrudan bu şekilde başlatamadığımızı belirtmek için bir strarray="new string"nokta var

Işaretçi

İşaretçinin bellekte bellek konumunu gösterdiğini bildiğimiz için, başlatılmamış işaretçi rastgele bellek konumunu işaret eder ve başlatmadan sonra belirli bir bellek konumunu işaret eder

char *ptr = "hello";

burada işaretçi ptr, "hello"salt okunur bellekte (ROM) saklanan sabit dize olan diziye başlatılır, bu nedenle "hello"ROM'da saklandığı için değiştirilemez

ve ptr yığın bölümünde saklanır ve sabit dizeyi gösterir "hello"

bu nedenle ptr [0] = 'm' geçersiz, çünkü salt okunur belleğe erişemiyorsunuz

Ancak ptr, sadece işaretçi olduğundan doğrudan diğer dize değerine başlatılabilir, böylece veri türünün değişkeninin herhangi bir bellek adresini gösterebilir

ptr="new string"; is valid

7
char *str = "string";  

Yukarıdakiler strdeğişmez değere işaret eder"string" , programın ikili görüntüsünde sabit olarak kodlanmış ve muhtemelen bellekte salt okunur olarak işaretlenen .

Yani str[0]=uygulamanın salt okunur kod yazma çalışıyor. Bu muhtemelen derleyici bağımlı olsa tahmin ediyorum.


6
char *str = "string";

dize hazır bilgisine bir işaretçi ayırır; derleyici çalıştırılabilirinizin değiştirilemez bir parçasına yerleştirir;

char str[] = "string";

değiştirilebilir yerel bir dizi ayırır ve başlatır


yazdığımız int *b = {1,2,3) gibi yazabilir char *s = "HelloWorld"miyiz?
Suraj Jain

6

@Matli'nin bundan bahsettiği C SSS'den bahsediyor, ancak burada başka kimse henüz açıklama yapmadı: açıklamak için: bir karakter dizisini başlatmaktan başka bir yerde bir dizgi değişmezi (kaynağınızda çift tırnaklı dize) kullanılıyorsa (yani: @ Mark'ın düzgün çalışan ikinci örneği, bu dize derleyici tarafından özel bir statik dize tablosunda depolanır; bu, temelde anonim olan (değişken "adı olmayan bir genel statik değişken (elbette salt okunur) oluşturmaya benzer "). Salt okunur kısım önemli kısımdır ve bu nedenle @ Mark'ın ilk kod örneği segfaults nedenidir.


yazdığımız int *b = {1,2,3) gibi yazabilir char *s = "HelloWorld"miyiz?
Suraj Jain

4

 char *str = "string";

çizgi bir işaretçiyi tanımlar ve onu değişmez bir dizeye yönlendirir. Değişmez dize yazılabilir değildir;

  str[0] = 'z';

seg hatası alıyorsunuz. Bazı platformlarda, hazır bilgi yazılabilir bellekte olabilir, bu nedenle bir segfault görmezsiniz, ancak ne olursa olsun geçersiz kod (tanımlanmamış davranışla sonuçlanır).

Çizgi:

char str[] = "string";

bir karakter dizisi tahsis eder ve değişmez dizgiyi tam olarak yazılabilir olan diziye kopyalar , böylece sonraki güncelleme sorun olmaz.


yazdığımız int *b = {1,2,3) gibi yazabilir char *s = "HelloWorld"miyiz?
Suraj Jain

3

"String" gibi dize değişmezleri, yürütülebilir dosyalarınızın adres alanında salt okunur veri olarak ayrılır (derleyicinizi verin veya alın). Dokunmaya gittiğinizde, mayo alanında olduğunuzdan korkuyor ve bir seg hatasıyla sizi bilgilendiriyor.

İlk örneğinizde, bu sabit veriye bir işaretçi alıyorsunuz. İkinci örneğinizde, const verilerinin bir kopyasıyla 7 karakterlik bir dizi başlatıyorsunuz.


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. 

1

İlk olarak, strişaret eden bir işaretçi "string". Derleyicinin dize değişmezlerini bellekte yazamadığınız, ancak okuyabileceğiniz yerlere koymasına izin verilir. (Bu gerçekten bir atıyorsanız beri bir uyarıyı tetiklemiştir gerekirdi const char *a char *sen uyarıları devre dışı bırakmış mü., Ya da sadece onları görmezden mi?)

İkinci olarak, tam erişime sahip olduğunuz bir dizi olan bir dizi oluşturuyorsunuz ve "string" . Bir yaratıyorsun char[7](sonlandıran '\ 0' için mektupları altı tane) ve onunla gibi olan şeyleri yapmak.


@Ferruccio,? Evet constöneki değişkenleri Salt Okunur yapar
EsmaeelE

C dize değişmezleri tür var char [N], değil const char [N], bu nedenle uyarı yok. (Bunu en azından -Wwrite-strings
gcc'de

0

Dize varsayalım,

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

İlk durumda, 'a' kapsamına girdiğinde gerçek bilgi kopyalanacaktır. Burada 'a' yığın üzerinde tanımlanan bir dizidir. Bu, dizenin yığın üzerinde oluşturulacağı ve verilerinin genellikle salt okunur olan kod (metin) belleğinden kopyalanacağı anlamına gelir (bu uygulamaya özgüdür, bir derleyici bu salt okunur program verilerini ayrıca yazılabilir belleğe de yerleştirebilir ).

İkinci durumda, p, yığın (yerel kapsam) üzerinde tanımlanan ve başka bir yerde saklanan bir dizgi hazır bilgisine (program verileri veya metin) gönderme yapan bir işaretçidir. Genellikle bu tür bir hafızayı değiştirmek iyi bir uygulama veya teşvik değildir.


-1

Birincisi, değiştirilemeyen sabit bir dizedir. İkincisi, başlatılmış değeri olan bir dizidir, bu yüzden değiştirilebilir.


-2

Segmentasyon hatası, erişilemeyen belleğe erişmeye çalıştığınızda oluşur.

char *str değiştirilemeyen bir dizenin işaretçisi (segfault alma nedeni).

oysa char str[]bir dizi ve değiştirilebilir olabilir ..

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.