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.
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.
Yanıtlar:
Buradaki fark şu ki
char *s = "Hello world";
yerleştirecektir "Hello world"
içinde belleğin salt okunur parça ve yapım s
bu 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.
"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.
char msg[] = "hello, world!";
dize içeren bir dosya temiz bir derleme başlatılan veri bölümünde biter olsun. char * const
Sonuna kadar salt okunur veri bölümünde ilan edildi . gcc-4.5.3
İ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.
Bu beyan:
char s[] = "hello";
Bir nesne oluşturur - değerlerle başlatılmış olarak char
adlandı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 :
char
değ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); ves
bu 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 s
belleğe işaretçi değişken bildirimi (yalnızca ilk örnekteki gibi) görünür yere bağlıdır.
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, s
statik depolama süresi varsa , tek örneği örneğin "hello"
başlatılmış veri segmentinde (nesnenin s
kendisi) olabilir. s
Otomatik 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)
).
.rodata
, daha sonra linker betiği ile aynı segmente atılır .text
. Cevabımı gör .
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?
char s[] = "Hellow world";
sadece bir başlatıcıdır ve mutlaka ayrı bir salt okunur kopya olarak saklanmaz. s
Statik depolama süresi varsa , dizenin tek kopyası muhtemelen konumunda bir okuma-yazma segmentinde s
olabilir 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.
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 char
C ++ '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 s0
bir işaretçi olarak tanımlar char
(yani değişken s0
yalnızca bildirildiği kapsam için vardır) ve değişmez değer dizesinin adresini ( 0x00008000
bu örnekte) buna kopyalar . Çünkü o Not s0
bir 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 s1
12 öğ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
.
s0
ve 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 s0
farklı bir dize değişmezine veya başka bir değişkene işaret edecek şekilde yeniden atayabilirsiniz . Değişkeni s1
farklı bir diziyi işaret edecek şekilde yeniden atayamazsınız.
C99 N1256 taslak
Karakter dizesi değişmezlerinin iki farklı kullanımı vardır:
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, c
değiştirilebilir.
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
s
vet
öğ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.p
Dizinin 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 .rodata
bölümünde değil bölümünde saklar .text
.
Ancak unutmayın varsayılan bağlayıcı komut dosyası koyar .rodata
ve .text
aynı 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
).
char s[] = "hello";
başlatıcıyı (5 + 1 sn) tutacak kadar uzun s
bir dizi olduğunu bildirir ve belirtilen dize değişmezinin üyelerini diziye kopyalayarak diziyi başlatır.char
char
char *s = "hello";
s
bir veya daha fazla (bu durumda daha fazla) char
s'ye işaretçi olduğunu bildirir ve bunu doğrudan değişmezi içeren sabit (salt okunur) bir yere yönlendirir "hello"
.
s
da bir işaretçi const char
.
char s[] = "Hello world";
Burada, s
dilersek üzerine yazılabilecek bir dizi karakter var.
char *s = "hello";
Dize değişmezi, bu karakter bloklarını bellekte bu işaretçinin s
iş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.
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.
x[1] = 'a';
olmayacak (elbette platforma bağlı olarak).
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:
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.
x
değ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.
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