Char s [] ve char * s arasındaki fark nedir?


506

C de, böyle bir bildirimde dize değişmezi kullanılabilir:

char s[] = "hello";

ya da bunun gibi:

char *s = "hello";

Peki fark nedir? Hem derleme hem de çalışma zamanında depolama süresi açısından gerçekte ne olduğunu bilmek istiyorum.



8
char * s = "merhaba", burada s çalışma zamanında başka bir dize işaret edebilir Bu sabit işaretçi değil yani çalışma zamanında başka bir değer atayabilirsiniz demek p = "Nishant", s [] burada s sabit işaretçi .. ..bir dizeyi yeniden atayamaz ancak s [index] 'e başka bir karakter değeri atayabiliriz.
Nishant Kumar

Yanıtlar:


541

Buradaki fark şu ki

char *s = "Hello world";

yerleştirecektir "Hello world"içinde belleğin salt okunur parça ve yapım sbu belleğin herhangi yazma işlemi yasadışı yapacak bir işaretçi.

Yaparken:

char s[] = "Hello world";

değişmez dizeyi salt okunur belleğe koyar ve dizeyi yığındaki yeni ayrılan belleğe kopyalar. Böylece

s[0] = 'J';

yasal.


22
Değişmez dize "Hello world"her iki örnekte de "belleğin salt okunur kısımlarında" bulunur. Dizisi ile, örneğin işaret dizisi ile, örneğin, orada kopya dizi elemanlarına karakter.
pmg

28
pmg: İkinci durumda değişmez dize bellekte tek bir bitişik nesne olarak bulunmayabilir - bu sadece bir başlatıcıdır, derleyici içine gömülü karakter değerlerini içeren bir dizi "anında bayt yükle" talimatı verebilir onlar.
caf

10
Char dizi örneği yok değil mutlaka yığın dize koyun - bu dosya seviyesinde görünürse, muhtemelen yerine Başlatıldı veri segmentinin çeşit olacaktır.
caf

9
Ben Char s = "xx" olmadığını işaret etmek istiyorum var (bazı uygulamaları, örneğin hiçbir MMus var) salt okunur bellekte olması. N1362 c1x taslağı, böyle bir diziyi değiştirmenin tanımlanmamış davranışa neden olduğunu belirtir. Ama yine de +1, çünkü bu davranışa güvenmek aptalca bir şey.
paxdiablo

3
Ben sadece char msg[] = "hello, world!"; dize içeren bir dosya temiz bir derleme başlatılan veri bölümünde biter olsun. char * constSonuna kadar salt okunur veri bölümünde ilan edildi . gcc-4.5.3
gcbenison

152

İlk olarak, işlev bağımsız değişkenlerinde tam olarak eşdeğerdir:

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

Diğer bağlamlarda, char * bir işaretçi char []ayırırken bir dizi ayırır. Önceki durumda dize nereye gidiyor? Derleyici, dizgi değişmezini tutmak için gizlice statik bir anonim dizi ayırır. Yani:

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

Bu anonim dizinin içeriğini hiçbir zaman bu işaretçi aracılığıyla değiştirmeye çalışmamanız gerektiğini unutmayın; etkiler tanımsızdır (genellikle çökme anlamına gelir):

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

Dizi sözdizimini kullanmak, onu doğrudan yeni belleğe ayırır. Böylece değişiklik güvenlidir:

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

Bununla birlikte, dizi yalnızca contaning kapsamı kadar yaşar, bu nedenle bunu bir işlevde yaparsanız, bu diziye bir işaretçi döndürmeyin veya sızdırmayın - bunun yerine strdup()veya benzeri bir kopya oluşturun . Dizi küresel kapsamda tahsis edilirse, elbette, sorun değil.


72

Bu beyan:

char s[] = "hello";

Bir nesne oluşturur - değerlerle başlatılmış olarak charadlandırılan 6 boyutlu bir dizi . Bu dizinin bellekte nereye tahsis edildiği ve ne kadar sürdüğü, bildirimin nerede göründüğüne bağlıdır. Açıklama bir işlev içindeyse, bildirildiği bloğun sonuna kadar yaşayacak ve neredeyse kesinlikle yığına tahsis edilecektir; bir işlevin dışındaysa, büyük olasılıkla program çalıştırıldığında çalıştırılabilir dosyadan yazılabilir belleğe yüklenen "başlatılan veri segmentinde" saklanır.s'h', 'e', 'l', 'l', 'o', '\0'

Öte yandan, bu beyan:

char *s ="hello";

İki nesne oluşturur :

  • bir salt okunur 6 dizisi chardeğerlerini içeren s 'h', 'e', 'l', 'l', 'o', '\0'adı yok vardır ve, statik depolama süresi (programın tüm ömrü için yaşayan anlamına gelir); ve
  • sbu adlandırılmamış salt okunur dizideki ilk karakterin konumu ile başlatılan , işaretçi-char türünde bir değişken .

Adsız salt okunur dizi genellikle programın "metin" bölümünde bulunur; bu, kodun kendisiyle birlikte diskten salt okunur belleğe yüklendiği anlamına gelir. Konumu sbelleğe işaretçi değişken bildirimi (yalnızca ilk örnekteki gibi) görünür yere bağlıdır.


1
"Merhaba" için her iki bildirimde bellek ortak zamanda tahsis edilir? Ve başka bir şey char * p = "merhaba" burada "merhaba" cevabınızda belirtildiği gibi metin segmentinde saklanır ... ve char s [] hakkında ne = "merhaba" da önce metin segmentinde saklayacak mı ve çalışma süresi boyunca Rickard'ın cevabında belirttiği gibi yığın halinde kopyalanacak. lütfen bu noktayı açıklığa kavuşturun.
Nishant Kumar

2
@Nishant: Bu char s[] = "hello"durumda, "hello"derleyiciye dizinin nasıl başlatılması gerektiğini söyleyen bir başlatıcıdır. Metin segmentinde karşılık gelen bir dizeyle sonuçlanabilir veya sonuçlanmayabilir - örneğin, sstatik depolama süresi varsa , tek örneği örneğin "hello"başlatılmış veri segmentinde (nesnenin skendisi) olabilir. sOtomatik depolama süresine sahip olsa bile , bir kopya yerine bir dizi değişmez mağaza ile başlatılabilir (örn. movl $1819043176, -6(%ebp); movw $111, -2(%ebp)).
caf

Daha kesin olarak, GCC 4.8 bunu koyar .rodata, daha sonra linker betiği ile aynı segmente atılır .text. Cevabımı gör .
Ciro Santilli 法轮功 7 病 六四 事件 法轮功

@caf Rickard'ın ilk cevabında char s[] = "Hello world";, değişmez dizgiyi salt okunur belleğe koyan ve dizeyi yığındaki yeni ayrılan belleğe kopyalayan yazılıdır . Ama, cevabın sadece salt okunur bellekte değişmez dize koymak bahseder ve der cümlenin ikinci kısmını atlar: copies the string to newly allocated memory on the stack. Peki, ikinci kısmı belirtmediğiniz için cevabınız eksik mi?
KPMG

1
@AjaySinghNegi: Diğer yorumlarda (bu cevaba ve Rickard'ın cevabına) belirttiğim gibi, dize char s[] = "Hellow world";sadece bir başlatıcıdır ve mutlaka ayrı bir salt okunur kopya olarak saklanmaz. sStatik depolama süresi varsa , dizenin tek kopyası muhtemelen konumunda bir okuma-yazma segmentinde solabilir ve olmasa bile, derleyici diziyi kopyalamak yerine yük anında komutlarla veya benzerleriyle başlatmayı seçebilir salt okunur bir dizeden. Mesele şu ki, bu durumda, başlatıcı dizesinin kendisinin çalışma zamanı varlığı yoktur.
caf

60

Beyanlar verildiğinde

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

aşağıdaki varsayımsal bellek haritasını varsayalım:

                    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

Dize değişmez "hello world"değeri , statik depolama süresine sahip char( const charC ++ 'da) 12 öğeli bir dizidir , yani program başlatıldığında ve program sona erene kadar ayrılmış durumda kaldığı için belleğin ayrıldığı anlamına gelir. Bir dizgi değişmezinin içeriğini değiştirmeye çalışmak tanımsız davranışı başlatır.

Çizgi

char *s0 = "hello world";

otomatik depolama süresi olan s0bir işaretçi olarak tanımlar char(yani değişken s0yalnızca bildirildiği kapsam için vardır) ve değişmez değer dizesinin adresini ( 0x00008000bu örnekte) buna kopyalar . Çünkü o Not s0bir dize kadar puan, onu değiştirmeye çalışacaktı herhangi bir fonksiyon (örneğin bir argüman olarak kullanılmamalıdır strtok(), strcat(), strcpy()vb.)

Çizgi

char s1[] = "hello world";

otomatik saklama süresine sahip s112 öğeli bir dizi char(uzunluk dizgesinden alınır) olarak tanımlar ve değişmezin içeriğini diziye kopyalar . Bellek haritasından da görebileceğiniz gibi, dizenin iki kopyası var "hello world"; fark, içinde yer alan dizeyi değiştirebilmenizdir s1.

s0ve s1çoğu bağlamda değiştirilebilir; İşte istisnalar:

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

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

Değişkeni s0farklı bir dize değişmezine veya başka bir değişkene işaret edecek şekilde yeniden atayabilirsiniz . Değişkeni s1farklı bir diziyi işaret edecek şekilde yeniden atayamazsınız.


2
Varsayımsal hafıza haritasının anlaşılmasını kolaylaştırdığını düşünüyorum!
midnightBlue

32

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 dizisi 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 fazı 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ğerine 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ünü 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ız olur.

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.

Ancak unutmayın varsayılan bağlayıcı komut dosyası koyar .rodatave .textaynı içinde segmentinde yürütmek vardır, ancak hiçbir yazma izni. Bu aşağıdakilerle gözlemlenebilir:

readelf -l a.out

içeren:

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

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ığına kaydedilir (göreceli olarak %rbp).


15
char s[] = "hello";

başlatıcıyı (5 + 1 sn) tutacak kadar uzun sbir dizi olduğunu bildirir ve belirtilen dize değişmezinin üyelerini diziye kopyalayarak diziyi başlatır.charchar

char *s = "hello";

sbir veya daha fazla (bu durumda daha fazla) chars'ye işaretçi olduğunu bildirir ve bunu doğrudan değişmezi içeren sabit (salt okunur) bir yere yönlendirir "hello".


1
S değiştirilmeyecekse, f (const char s []) veya f (const char * s) işlevlerinde hangi yöntem kullanılır?
psihodelia

1
@psihodelia: İşlev bildiriminde hiçbir fark yoktur. Her iki durumda sda bir işaretçi const char.
CB Bailey

4
char s[] = "Hello world";

Burada, sdilersek üzerine yazılabilecek bir dizi karakter var.

char *s = "hello";

Dize değişmezi, bu karakter bloklarını bellekte bu işaretçinin sişaret ettiği bir yerde oluşturmak için kullanılır . Burada işaret ettiği nesneyi değiştirerek yeniden atayabiliriz, ancak bir dizgi değişmezine işaret ettiği sürece, işaret ettiği karakter bloğu değiştirilemez.


@bo Persson İkinci durumda neden karakter bloğu değiştirilemiyor?
Pankaj Mahato

3

Ek olarak, salt okunur amaçlar için her ikisinin de kullanımı aynı olduğundan, bir karaktere []veya *(<var> + <index>) biçimiyle dizine ekleyerek bir karaktere erişebileceğinizi düşünün :

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

Ve:

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

Açıkçası, eğer yapmaya çalışırsanız

*(x + 1) = 'a';

Salt okunur belleğe erişmeye çalıştığınız için muhtemelen bir Segmentasyon Hatası alırsınız.


Bu hiçbir şekilde farklı x[1] = 'a';olmayacak (elbette platforma bağlı olarak).
glglgl

3

Sadece eklemek için: boyutları için de farklı değerler elde edersiniz.

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

Yukarıda belirtildiği gibi, bir dizi '\0'için son eleman olarak tahsis edilecektir.


2
char *str = "Hello";

Yukarıdaki ayarlar, programın ikili görüntüsünde sabit olarak kodlanmış ve bellekte salt okunur olarak işaretlenen "Hello" değişmez değerini gösterecek şekilde ayarlanır.

char str[] = "Hello";

dizeyi yığındaki yeni ayrılan belleğe kopyalar. Böylece herhangi bir değişiklik yapılmasına izin verilir ve yasaldır.

means str[0] = 'M';

str'yi "Mello" olarak değiştirir.

Daha fazla ayrıntı için lütfen benzer soruyu inceleyin:

"Char * s" ile başlatılmış ancak "char s []" ile başlatılmamış bir dizeye yazarken neden bir segmentasyon hatası alıyorum?


0

Bu durumuda:

char *x = "fred";

x bir değerdir - atanabilir. Ancak şu durumlarda:

char x[] = "fred";

x bir değer değil, bir değerdir - atayamazsınız.


3
Teknik olarak, xdeğiştirilemez bir değerdir. Yine de hemen hemen tüm bağlamlarda, ilk öğesinin göstergesini değerlendirir ve bu değer bir değerdir.
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

-1

Buradaki yorumlar ışığında şu açık olmalıdır: char * s = "merhaba"; Kötü bir fikirdir ve çok dar kapsamda kullanılmalıdır.

Bu, "sabit doğruluk" un "iyi bir şey" olduğunu belirtmek için iyi bir fırsat olabilir. Mümkün olduğunda ve nerede olursanız olun, kodunuzu korumak için "rahat" arayanlardan veya programcılardan "işaret" anahtar kelimesini kullanın; bu anahtarlar işaretçiler devreye girdiğinde genellikle "rahat" olur.

Yeterli melodram, "const" ile işaretçileri süslerken neler başarabilir. (Not: Kişinin sağdan sola işaretçi bildirimlerini okuması gerekir.) İşaretçilerle oynarken kendinizi korumanın 3 farklı yolu:

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

- yani, DBJ nesnesi p ile değiştirilemez.

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

- yani, DBJ nesnesini p ile değiştirebilirsiniz, ancak p işaretçisinin kendisini değiştiremezsiniz.

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

- yani, işaretçinin p kendisini değiştiremezsiniz veya DBJ nesnesini p ile değiştiremezsiniz.

Sabit yapı mutasyonları ile ilgili hatalar derleme zamanında yakalanır. Sabit çalışma zamanı alanı veya hız cezası yoktur.

(Tabii ki C ++ derleyicisini kullanıyorsunuz?)

--DBJ


Bunların hepsi doğru, ama soru ile ilgisi yok. Ve bir C ++ derleyicisi hakkındaki varsayımınıza göre, soru C ++ olarak değil C olarak etiketlenir.
Fabio, Reinstate Monica'ya

Char * s = "const string" hakkında kötü bir şey yoktur;
Paul Smith
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.