Bir yığın dizisinin işaretçisine göstericiye neden erişemiyorum?


35

Lütfen aşağıdaki koda bir göz atın. Bir diziyi char**işleve bir olarak geçirmeye çalışır :

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

Sadece açıkça dökerek derlemesini sağlayabiliyorum &test2 ile char**zaten bu kodu yanlış olduğunu ima.

Yine de, bunun tam olarak neyin yanlış olduğunu merak ediyorum. Bir işaretçiyi dinamik olarak ayrılmış bir diziye bir işaretçi geçirebilir ancak yığın üzerinde bir dizi için bir işaretçi bir işaretçi geçiremezsiniz. Tabii ki, ilk önce geçici bir değişkene dizi atayarak sorunu kolayca çözebilirim:

char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);

O döküm için çalışmıyor neden Yine kutu birisi bana açıklayabilir char[256]için char**doğrudan?

Yanıtlar:


29

Çünkü testbir işaretçi değil.

&testdiziye char (*)[256]uyumlu char**olmayan bir işaretçi alır (bir dizi işaretçi olmadığından). Bu tanımlanmamış davranış ile sonuçlanır.


3
Peki C derleyicisi neden böyle bir şeyin geçmesine izin char (*)[256]veriyor char**?
ComFreek

@ComFreek Maksimum uyarılar ve -Beyaz ile buna izin verilmediğinden şüpheleniyorum.
PiRocks

@ComFreek: Buna gerçekten izin vermiyor. Derleyiciyi açıkça yayınlayarak kabul etmeye zorlamalıyım char**. Bu oyuncu olmadan, derlenmez.
Andreas

38

testbir işaretçi değil, bir dizi &testve diziye bir işaretçi. İşaretçiye bir işaretçi değildir.

Bir dizinin bir işaretçi olduğu söylenmiş olabilir, ancak bu yanlıştır. Bir dizinin adı tüm nesnenin, yani tüm öğelerin adıdır. İlk öğeye bir işaretçi değildir. Çoğu ifadede, bir dizi otomatik olarak ilk öğesine bir işaretçiye dönüştürülür. Bu genellikle kullanışlı bir rahatlıktır. Ancak bu kuralın üç istisnası vardır:

  • Dizi işlenenidir sizeof.
  • Dizi işlenenidir &.
  • Dizi, bir diziyi başlatmak için kullanılan bir dize değişmezidir.

İçinde &test, dizi işlenenidir &, bu nedenle otomatik dönüşüm gerçekleşmez. Sonucu &test256 bir dizi için bir göstericichar türü vardır, char (*)[256]değil, char **.

İşaretçiden işaretçi almak chariçin test, önce bir işaretçi yapmanız gerekir.char . Örneğin:

char *p = test; // Automatic conversion of test to &test[0] occurs.
printchar(&p);  // Passes a pointer to a pointer to char.

Bunu düşünmenin başka bir yolu da test, tüm nesneyi - 256 dizisinin tamamını adlandırdığını fark etmektir char. Bir işaretçi adını vermez, bu nedenle, &testadresi alınabilecek hiçbir işaretçi yoktur, bu nedenle a üretemez char **. A oluşturmak için char **önce bir char *.


1
Bu üç istisna listesi ayrıntılı mıdır?
Ruslan

8
@Ruslan: Evet, C 2018 başına 6.3.2.1 3.
Eric Postpischil

Oh, ve C11 ile de vardı _Alignofek olarak bahsedilen operatör sizeofve &. Neden kaldırıldıklarını merak ediyorum ...
Ruslan

@Ruslan: Bu bir hata olduğu için kaldırıldı. _Alignofbir tür adını yalnızca işlenen olarak kabul eder ve hiçbir zaman bir diziyi veya başka bir nesneyi işlenen olarak kabul etmez. (Nedenini bilmiyorum; sözdizimsel ve dilbilgisel olarak benziyor olabilir sizeofama öyle değil.)
Eric Postpischil

6

Tipi test2DİR char *. Yani, türü &test2olacak olması char **parametrenin tipi ile uyumludur xarasında printchar().
Tipi testDİR char [256]. Yani, türü &testolacak olması char (*)[256]olduğunu değil parametresinin türü ile uyumlu xbir printchar().

Size adresleri testve arasındaki farkları göstereyim test2.

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("x = %p\n", (void*)x);
    printf("*x  = %p\n", (void*)(*x));
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printf ("test2 : %p\n", (void*)test2);
    printf ("&test2 : %p\n", (void*)&test2);
    printf ("&test2[0] : %p\n", (void*)&test2[0]);
    printchar(&test2);            // works

    printf ("\n");
    printf ("test : %p\n", (void*)test);
    printf ("&test : %p\n", (void*)&test);
    printf ("&test[0] : %p\n", (void*)&test[0]);

    // Commenting below statement
    //printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

Çıktı:

$ ./a.out 
test2 : 0x7fe974c02970
&test2 : 0x7ffee82eb9e8
&test2[0] : 0x7fe974c02970
x = 0x7ffee82eb9e8
*x  = 0x7fe974c02970
Test: A

test : 0x7ffee82eba00
&test : 0x7ffee82eba00
&test[0] : 0x7ffee82eba00

Burada dikkat etmeniz gereken nokta:

Çıktısı (bellek adresi) test2ve &test2[0]bir sayısal aynı ve tipi olan aynı olan char *.
Ancak test2ve &test2adresleri farklıdır ve türleri de farklıdır.
Tipi test2DİR char *.
Tipi &test2DİR char **.

x = &test2
*x = test2
(*x)[0] = test2[0] 

Çıkış (bellek adresi) test, &testve &test[0]bir sayısal aynı ancak türü farklıdır .
Tipi testDİR char [256].
Türü&test DİR char (*) [256].
Tipi &test[0]DİRchar * .

Çıktıda görüldüğü gibi &test gibi &test[0].

x = &test[0]
*x = test[0]       //first element of test array which is 'B'
(*x)[0] = ('B')[0]   // Not a valid statement

Bu nedenle segmentasyon hatası alıyorsunuz.


3

İşaretçiye işaretçi olmadığından bir işaretçiye erişemezsiniz &test; bu bir dizidir.

Bir dizinin adresini alırsanız, diziyi ve dizinin adresini şu şekilde yayınlar (void *)ve karşılaştırırsanız, bunlar (olası işaretçi bilgiçliğini engeller) eşdeğer olur.

Gerçekten yaptığınız şuna benzer (yine, sıkı bir takma adın engellenmesi):

putchar(**(char **)test);

ki bu kesinlikle yanlış.


3

Kodunuz argümanlar bekler xait printchara içeren belleğe noktasına(char *) .

İlk çağrıda, kullanılan depolamayı işaret eder test2ve bu nedenle gerçekten (char *)a'yı işaret eden bir değerdir , ikincisi tahsis edilen belleğe işaret eder.

Ancak ikinci çağrıda, böyle bir (char *)değerin depolanabileceği bir yer yoktur ve bu nedenle böyle bir hafızaya işaret etmek imkansızdır. (char **)Eklediğiniz yayın, bir derleme hatasını (dönüştürmek (char *)üzere (char **)) kaldırmış olurdu, ancak (char *)testin ilk karakterlerini gösterecek şekilde başlatılmış bir içeriği içerecek şekilde depolama alanının ince havadan görünmesini sağlamaz . C'deki işaretçi döküm, işaretçinin gerçek değerini değiştirmez.

İstediğinizi elde etmek için, bunu açıkça yapmanız gerekir:

char *tempptr = &temp;
printchar(&tempptr);

Ben senin örnek çok daha büyük bir kod parçası bir damıtma olduğunu varsayalım; örnek olarak, bir sonraki çağrıda bir sonraki karakter yazdırılacak şekilde, iletilen değerin işaret printcharettiği (char *)değeri artırmak isteyebilirsiniz x. Eğer durum böyle değilse, neden sadece (char *)basılacak karaktere işaret etmiyorsunuz , hatta sadece karakterin kendisini geçmiyorsunuz ?


İyi cevap; Ben bu düz tutmak için en kolay yolu dizinin adresini tutan bir C nesnesi, yani almak için adresini alabilir bir işaretçi nesne olup olmadığını düşünmek olduğunu kabul ediyorum char **. Dizi değişkenler / basitçe nesnelerin olan adres örtük değil saklanan her yerde olmak üzere dizi. Diğer depolamaya işaret eden bir işaretçi değişkeninin aksine, bunlara erişmek için fazladan dolaylama düzeyi yoktur.
Peter Cordes

0

Görünüşe göre, adresini testalmak, adresini almakla aynıdır test[0]:

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("[printchar] Address of pointer to pointer: %p\n", (void *)x);
    printf("[printchar] Address of pointer: %p\n", (void *)*x);
    printf("Test: %c\n", **x);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    printf("[main] Address of test: %p\n", (void *)test);
    printf("[main] Address of the address of test: %p\n", (void *)&test);
    printf("[main] Address of test2: %p\n", (void *)test2);
    printf("[main] Address of the address of test2: %p\n", (void *)&test2);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar(&test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

Derleyin ve çalıştırın:

forcebru$ clang test.c -Wall && ./a.out
test.c:25:15: warning: incompatible pointer types passing 'char (*)[256]' to
      parameter of type 'char **' [-Wincompatible-pointer-types]
    printchar(&test);   // crashes because *x in printchar() has an inva...
              ^~~~~
test.c:4:30: note: passing argument to parameter 'x' here
static void printchar(char **x)
                             ^
1 warning generated.
[main] Address of test: 0x7ffeeed039c0
[main] Address of the address of test: 0x7ffeeed039c0 [THIS IS A PROBLEM]
[main] Address of test2: 0x7fbe20c02aa0
[main] Address of the address of test2: 0x7ffeeed039a8
[printchar] Address of pointer to pointer: 0x7ffeeed039a8
[printchar] Address of pointer: 0x7fbe20c02aa0
Test: A
[printchar] Address of pointer to pointer: 0x7ffeeed039c0
[printchar] Address of pointer: 0x42 [THIS IS THE ASCII CODE OF 'B' in test[0] = 'B';]
Segmentation fault: 11

Segmentasyon hatasının nihai nedeni, bu programın mutlak adresi 0x42(aynı zamanda bilinen)'B' programınızın okuma iznine sahip olmadığı ) kaldırmaya çalışmasıdır.

Farklı bir derleyici / makine ile adresler farklı olsa da: Çevrimiçi deneyin! , ancak yine de bazı nedenlerden dolayı bunu elde edersiniz:

[main] Address of test: 0x7ffd4891b080
[main] Address of the address of test: 0x7ffd4891b080  [SAME ADDRESS!]

Ancak, segmentasyon hatasına neden olan adres çok farklı olabilir:

[printchar] Address of pointer to pointer: 0x7ffd4891b080
[printchar] Address of pointer: 0x9c000000942  [WAS 0x42 IN MY CASE]

1
Adresini almak, adresini testalmakla aynı şey değildir test[0]. Birincisinin tipi char (*)[256], ikincisinin tipi vardır char *. Uyumlu değildirler ve C standardı farklı temsiliyetlere sahip olmalarına izin verir.
Eric Postpischil

İşaretçiyi biçimlendirirken, işaretine %pdönüştürülmelidir void *(yine uyumluluk ve temsil nedenleriyle).
Eric Postpischil

1
printchar(&test);sizin için çökebilir, ancak davranış C standardı tarafından tanımlanmaz ve insanlar diğer durumlarda diğer davranışları gözlemleyebilir.
Eric Postpischil

Re “Segmentasyon hatasının nihai nedeni, bu programın muhtemelen işletim sistemi tarafından işgal edilen mutlak 0x42 ('B' olarak da bilinir) adresinden vazgeçmeye çalışmasıdır.”: Okumayı deneyen bir segment hatası varsa bir yer, işletim sisteminin işgal ettiği değil, hiçbir şeyin eşlenmediği anlamına gelir. (Orada, örneğin okuma izni olmadan yalnızca yürütme gibi eşlenen bir şey olabilir, ancak bu olası değildir.)
Eric Postpischil

1
&test == &test[0]C 2018 6.5.9 2'deki kısıtlamaları ihlal ediyor çünkü türler uyumlu değil. C standardı, bu ihlali teşhis etmek için bir uygulama gerektirir ve ortaya çıkan davranış C standardı tarafından tanımlanmaz. Bu, derleyicinizin eşit olduğunu değerlendiren kod üretebileceği anlamına gelir, ancak başka bir derleyici olmayabilir.
Eric Postpischil

-4

Temsili char [256]uygulamaya bağlıdır. İle aynı olmamalıdırchar * .

Döküm &testÇeşidi char (*)[256]etmekchar ** verim davranışı tanımsız.

Bazı derleyicilerle, beklediğinizi yapabilir, diğerleri üzerinde değil.

DÜZENLE:

Gcc 9.2.1 ile test ettikten sonra printchar((char**)&test), aslında test kullanılan değer olarak geçer char**. Sanki talimat buydu printchar((char**)test). Olarak printcharişlev, xbir dizi testi, ilk karaktere bir çift işaretçi ilk karakter için bir işaretçi. Çift referans yokxDizinin ilk 8 baytı geçerli bir adrese karşılık gelmediğinden, kaldırma segmentasyon hatasına neden olur.

Programı clang 9.0.0-2 ile derlerken tam olarak aynı davranışı ve sonucu alıyorum.

Bu bir derleyici hatası veya sonucu derleyiciye özgü olabilecek tanımlanmamış bir davranışın sonucu olarak düşünülebilir.

Beklenmeyen başka bir davranış, kodun

void printchar2(char (*x)[256]) {
    printf("px: %p\n", *x);
    printf("x: %p\n", x);
    printf("c: %c\n", **x);
}

Çıktı

px: 0x7ffd92627370
x: 0x7ffd92627370
c: A

Garip davranış şudur xve*x aynı değere sahiptir.

Bu derleyici bir şey. Bunun dil tarafından tanımlandığından şüpheliyim.


1
Yani temsili char (*)[256]uygulamaya bağlıdır mı? Temsili char [256]bu soru ile ilgili değildir - sadece bir parça bittir. Ancak, bir işaretçinin bir diziye gösterimi, bir işaretçinin bir işaretçiye gösterilmesinden farklı olsa da, bu noktayı da kaçırır. Aynı temsillere sahip olsalar bile, OP'nin kodu çalışmaz, çünkü bir işaretçiye işaretçi yapıldığı gibi iki kez kaydı silinebilir printchar, ancak bir diziye işaretçi gösterime bakılmaksızın yapılamaz.
Eric Postpischil

Dan döküm @EricPostpischil char (*)[256]için char **derleyici tarafından kabul edilir, ancak bir nedeni beklenen sonuç ortaya koymamıştır char [256]bir aynı değildir char *. Kodlamanın farklı olduğunu varsaydım, aksi takdirde beklenen sonucu verecektir.
chmike

“Beklenen sonuç” ile ne demek istediğinizi bilmiyorum. C standardında sonucun ne olması gerektiğine ilişkin tek spesifikasyon, eğer hizalama yetersizse char **, davranışın tanımsız olması ve aksi halde sonuç tekrar dönüştürülürse char (*)[256]orijinal işaretçiye eşit olmasıdır. “Beklenen sonuç” ile, (char **) &testa char *değerine daha fazla dönüştürülürse , eşittir &test[0]. Bu, düz bir adres alanı kullanan uygulamalarda pek olası bir sonuç değildir, ancak yalnızca bir temsil meselesi değildir.
Eric Postpischil

2
Ayrıca, "char (*) [256] 'un char ** türüne döküm ve testi tanımlanmamış davranış sağlar." Doğru değil. C 2018 6.3.2.3 7, bir nesne tipinin göstergesinin başka bir göstericiye bir nesne tipine dönüştürülmesini sağlar. İşaretçiyi doğru atıfta bulunulan tipteki (başvurulan türü için hizalanmamışsa char **IS char *), daha sonra davranış tanımlanmamış. Aksi takdirde, yukarıdaki yorumuma göre değer yalnızca kısmen tanımlanmasına rağmen dönüşüm tanımlanır.
Eric Postpischil

char (*x)[256]ile aynı şey değildir char **x. Aynı işaretçi değerinin nedeni xve *xyazdırılması x, diziye yalnızca bir işaretçi olmasıdır. Sizin *x dizinizdir ve bir işaretçi bağlamında kullanmak dizinin adresine geri döner . Orada derleyici hata (ya da ne (char **)&testyapar), sadece biraz zihinsel jimnastik anlamaya gerekli türleri ile devam ediyor. (cdecl bunu "char 'ın 256 dizisine işaretçi olarak bildir" olarak açıklar). char*Bir char**UB değil nesnenin temsili erişmek için bile kullanarak ; her şeyi takma adlandırabilir.
Peter Cordes
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.