C harfinde char dizisi ve karakter işaretçisi arasındaki fark nedir?


216

C işaretçiler anlamaya çalışıyorum ama şu anda aşağıdaki ile karıştırıyorum:

  • char *p = "hello"

    Bu, h'den başlayarak karakter dizisini gösteren bir karakter işaretçisi .

  • char p[] = "hello"

    Bu, merhaba depolayan bir dizidir .

Bu değişkenlerin her ikisini de bu işleve geçirdiğimde fark nedir?

void printSomething(char *p)
{
    printf("p: %s",p);
}

5
Bu geçerli olmaz: char p[3] = "hello";Başlatıcı dizesi, bildirdiğiniz dizinin boyutu için çok uzun. Tipo?
Cody Gray

16
Yoksa char p[]="hello";yeterli olur!
deepdive


1
olası yinelenen karakter s [] ve char * s C arasındaki fark nedir? Doğru, bu ayrıca özellikle işlev parametresini sorar, ancak bu charspesifik değildir .
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

1
temelde farklı olduklarını anlamalısınız. buradaki tek ortak nokta, arry p [] 'nin tabanının bir işaretçi aracılığıyla p [] dizisine erişmesini sağlayan bir sabit işaretçi olmasıdır. p [] 'nin kendisi bir dize için bellek tutarken, * p sadece ONE CHAR'ın ilk elemanının adresini işaret eder (yani önceden tahsis edilmiş dizenin tabanını gösterir). Bunu daha iyi açıklamak için aşağıdakileri göz önünde bulundurun: char * cPtr = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> bu bir hatadır, çünkü cPtr yalnızca bir karakter işaretçisi olduğundan char cBuff [] = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; CBuff kendisi karakter dizisidir bcos ==> Bu Tamam, olduğu
Ilavarasan

Yanıtlar:


223

char*ve char[] farklı türlerdir , ancak her durumda hemen görülmez. Bunun nedeni, dizilerin işaretçilere bozulmasıdır ; başka bir deyişle char[], türden char*birinin beklendiği yerde bir tür ifadesi sağlanırsa , derleyici diziyi otomatik olarak ilk öğesine bir işaretçiye dönüştürür.

Örnek işleviniz printSomethingbir işaretçi bekler, bu nedenle bir diziyi bu şekilde geçirmeye çalışırsanız:

char s[10] = "hello";
printSomething(s);

Derleyici şunu yazmış gibi yapıyor:

char s[10] = "hello";
printSomething(&s[0]);

2012'den günümüze bir şey değişti. Bir karakter dizisi için "s" dizinin tamamını yazdırır .. yani, "merhaba"
Bhanu Tez

@BhanuTez Hayır, verilerin nasıl saklandığı ve verilerle ne yapılacağı ayrı endişelerdir. Bu örnek tüm dizeyi yazdırır çünkü string dizesini şu şekilde printfişler %s: sağlanan adresten başlayın ve null sonlandırıcıyla karşılaşana kadar devam edin. Örneğin yalnızca bir karakter yazdırmak istiyorsanız, %cbiçim dizesini kullanabilirsiniz .
iX3

Sadece char *p = "abc";NULL karakterinin \0char [] dizisinde olduğu gibi otomatik olarak eklenip eklenmediğini sormak istediniz mi?
KPMG

neden ayarlayabilirim char *name; name="123";ama inttip ile aynı şeyi yapabilirim ? Ve kullandıktan sonra %cyazdırmak için name, çıkış okunmaz dizesidir: ?
TomSawyer

83

Bakalım:

#include <stdio.h>
#include <string.h>

int main()
{
    char *p = "hello";
    char q[] = "hello"; // no need to count this

    printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
    printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both

    // size_t strlen(const char *s) and we don't get any warnings here:
    printf("%zu\n", strlen(p)); // => 5
    printf("%zu\n", strlen(q)); // => 5

    return 0;
}

foo * ve foo [] farklı türlerdir ve derleyici tarafından farklı şekilde ele alınırlar (işaretçi = adres + işaretçi türünün gösterimi, dizi = işaretçi + biliniyorsa dizinin isteğe bağlı uzunluğu, örneğin dizi statik olarak ayrılmışsa ), ayrıntılar standartta bulunabilir. Ve çalışma zamanında, aralarında hiçbir fark yoktur (montajcıda, neredeyse aşağıya bakınız).

Ayrıca, ilgili olduğu sorusu içinde C SSS :

S : Bu başlatmalar arasındaki fark nedir?

char a[] = "string literal";   
char *p  = "string literal";   

P [i] 'ye yeni bir değer atamaya çalışırsam programım çöküyor.

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. Char dizisinin başlatıcısı olarak, char a [] bildiriminde olduğu gibi, o dizideki karakterlerin başlangıç ​​değerlerini (ve gerekirse boyutunu) 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).

Ayrıca bkz. 1.31, 6.1, 6.2, 6.8 ve 11.8b soruları.

Kaynaklar: K & R2 Sec. 5.5 s. 104

ISO Sec. 6.1.4, Sec. 6.5.7

Gerekçe Sec. 3.1.4

H&S Sec. 2.7.4 s.31-2


Sizeof (q) 'da q neden bir işaretçiye bozulmuyor, @Jon cevabında bahsediyor?
garyp

@garyp q bir işaretçiye bozulmaz çünkü sizeof bir işleç değildir, bir işlev değildir (sizeof bir işlev olsa bile, q yalnızca işlev bir char işaretçisi beklerse çürür).
GiriB

teşekkürler, ancak printf yerine "printf ("% u \ n "("% zu \ n "),
Zakaria

33

C'deki char array vs char pointer arasındaki fark nedir?

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 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ğerine sahip 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ığına kaydedilir (göreceli olarak %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

2
@ leszek.hanusz Tanımsız Davranış stackoverflow.com/questions/2766731/… Google "C dili UB" ;-)
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

9

Bir dize sabitinin içeriğini değiştirmenize izin verilmez, bu ilk pişarettir. İkinci pbir dize sabiti ile başlatıldı bir dizidir ve yapabilirsiniz içeriğini değiştirir.


6

Bu gibi durumlarda, etki aynıdır: Bir karakter dizisindeki ilk karakterin adresini geçirirsiniz.

Beyanlar kesinlikle aynı değil.

Aşağıdakiler, bir dize ve bir karakter işaretçisi için belleği ayırır ve daha sonra işaretçiyi dizedeki ilk karakteri gösterecek şekilde başlatır.

char *p = "hello";

Aşağıdakiler sadece dize için bellek ayırır. Yani aslında daha az bellek kullanabilir.

char p[10] = "hello";

codeplusplus.blogspot.com/2007/09/… "Ancak, değişkenin başlatılması dizi için büyük bir performans ve alan cezası gerektirir"
leef

@ leef: Değişkenin bulunduğu yere bağlı olduğunu düşünüyorum. Statik bellekte ise, dizi ve verilerin EXE görüntüsünde saklanabileceğini ve herhangi bir başlatma gerektirmeyeceğini düşünüyorum. Aksi takdirde, evet, verilerin tahsis edilmesi ve ardından statik verilerin kopyalanması gerekiyorsa kesinlikle daha yavaş olabilir.
Jonathan Wood

3

Hatırlayabildiğim kadarıyla, bir dizi aslında bir işaretçiler grubudur. Örneğin

p[1]== *(&p+1)

gerçek bir ifade


2
Ben bir dizi bellek bloğunun adresine bir işaretçi olarak tarif ediyorum. Bu yüzden neden *(arr + 1)sizi ikinci üyesine getiriyor arr. Eğer *(arr), örneğin, bir 32-bit bellek adresi, işaret bfbcdf5e, daha sonra *(arr + 1)işaret bfbcdf60(ikinci bayt). Bu nedenle, işletim sistemi segfault olmazsa neden bir dizinin kapsamından çıkmak garip sonuçlara yol açacaktır. Eğer int a = 24;adreste bfbcdf62ardından erişen arr[2]kudreti dönüşü 24, bir segfault varsayarak ilk gerçekleşmez.
Braden Best

3

Gönderen APUE Bölüm 5.14:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/

... İlk şablon için, bir dizi değişkeni kullandığımız için ad yığına ayrılır. Ancak ikinci isim için bir işaretçi kullanıyoruz. Bu durumda, yalnızca işaretçinin kendisi için bellek yığının üzerinde bulunur; derleyici, dizenin yürütülebilir dosyanın salt okunur segmentinde saklanmasını düzenler. Ne zaman mkstempfonksiyon dizesini değiştirmeye kalktığında bir segmentasyon hatası oluşur.

Alıntılanan metin @Ciro Santilli'nin açıklamasıyla eşleşir.


1

char p[3] = "hello"? olmalıdır char p[6] = "hello"var hatırlıyorum '\ 0' karakter C. bir "dizge" sonunda

her neyse, C'deki dizi, bellekteki bir ayarlama nesnelerinin ilk nesnesinin sadece bir göstergesidir. sadece farklı semantik anlamlıdır. bir işaretçinin değerini bellekteki farklı bir yeri gösterecek şekilde değiştirebildiğinizde, bir dizi oluşturulduktan sonra her zaman aynı yeri gösterecektir.
Ayrıca dizi kullanıldığında "yeni" ve "sil" sizin için otomatik olarak yapılır.

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.