0 boyutlu dinamik diziye bir işaretçi arttırmak tanımsız mı?


34

AFAIK, 0 boyutlu bir statik bellek dizisi oluşturamasak da dinamik dizilerle yapabiliriz:

int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined

Okuduğum pgibi, tek uçlu bir eleman gibi davranıyor. İşaret eden adresi yazdırabilirim p.

if(p)
    cout << p << endl;
  • Her ne kadar yineleyicilerle (geçmiş son öğe) o işaretçiyi (geçmiş-son öğe) kaldıramayacağımızdan emin olmama rağmen, emin değilim ki bu işaretçiyi artırıp artırmamak pmı? Tanımlanmamış bir davranış (UB) yineleyiciler gibi mi?

    p++; // UB?

4
UB "... Diğer tüm durumlar (yani, aynı dizinin bir öğesini veya sonun sonunu işaret etmeyen bir işaretçi üretmeye çalışır) tanımlanmamış davranışı başlatır
Richard Critten

3
Bu, std::vectoriçindeki 0 öğeye benzer . begin()zaten eşittir, end()bu nedenle başlangıçta işaret eden bir yineleyiciyi artıramazsınız.
Phil1970

1
@PeterMortensen Bence düzenlemeniz son cümlenin anlamını değiştirdi ("Emin olduğum şey -> neden emin değilim"), lütfen tekrar kontrol edebilir misiniz?
Fabio, Reinstate Monica'ya

@PeterMortensen: Düzenlediğiniz son paragraf biraz daha az okunabilir hale geldi.
Itachi Uchiwa

Yanıtlar:


32

Dizilerin öğelerine işaretçiler, geçerli bir öğeyi veya bir sonun ötesini gösterebilir. Bir işaretçiyi sonun ötesine geçecek şekilde artırırsanız, davranış tanımsız olur.

0 boyutlu diziniz piçin zaten bir ucu işaret ediyor, bu nedenle artırılmasına izin verilmiyor.

+Operatöre ilişkin C ++ 17 8.7 / 4'e bakın ( ++aynı kısıtlamalara sahiptir):

f ifadesi , n elemanlı bir dizi nesnesinin Pelemanına işaret eder , ifadeler ve ( değerin olduğu yerde ) , 0≤i + j≤n ise (muhtemelen varsayımsal) öğeye işaret eder; aksi takdirde, davranış tanımsızdır.x[i]xP + JJ + PJjx[i+j]


2
Yani tek durum her ikisinde x[i]de x[i + j]olduğu gibi aynıdır ive j0 değerine sahip mi?
Rami Yen

8
@RamiYen x[i]aynı elemandır x[i+j]ise j==0.
interjay

1
Ugh, C ++ anlambiliminin "alacakaranlık bölgesinden" nefret ediyorum ... +1.
einpoklum

4
@ einpoklum-reinstateMonica: Gerçekten alacakaranlık bölgesi yok. Sadece C ++, N = 0 durumu için bile tutarlı. Bir dizi N öğesi için, dizinin arkasına işaret edebildiğiniz için N + 1 geçerli işaretçi değerleri vardır. Bu, dizinin başında başlayabileceğiniz ve sonuna ulaşmak için işaretçiyi N kez artırabileceğiniz anlamına gelir.
MSalters

1
@MaximEgorushkin Cevabım dilin şu anda izin verdiği şeyle ilgili. Bunun hakkında izin vermesini istediğiniz tartışma konu dışıdır.
interjay

2

Sanırım cevabınız zaten var; Biraz daha derin bakarsanız: Sonu olmayan bir yineleyiciyi artırmanın UB olduğunu söylediniz: Bu cevap bir yineleyici nedir?

Yineleyici sadece bir işaretçisi olan ve yineleyicinin sahip olduğu işaretçiyi gerçekten artırdığını arttıran bir nesnedir. Böylece birçok yönden bir işaretçi bir işaretçi olarak ele alınır.

int arr [] = {0,1,2,3,4,5,6,7,8,9};

int * p = dizi; // p, arr içindeki ilk öğeye işaret eder

++ p; // p arr'yi gösteriyor [1]

Tıpkı bir vektördeki öğeleri çaprazlamak için yineleyiciler kullanabildiğimiz gibi, bir dizideki öğeleri çaprazlamak için işaretçiler kullanabiliriz. Tabii ki, bunu yapmak için, ilk öğeye ve son öğenin ötesine bir işaretçi edinmeliyiz. Daha önce gördüğümüz gibi, dizinin kendisini kullanarak veya ilk öğenin adresini alarak ilk öğeye bir işaretçi elde edebiliriz. Dizilerin başka bir özel özelliğini kullanarak son teknoloji ürünü bir işaretçi elde edebiliriz. Varolmayan öğenin adresini bir dizinin son öğesinden bir daha geçebiliriz:

int * e = & arr [10]; // arr'deki son öğenin hemen ötesinde işaretçi

Burada var olmayan bir elemanı indekslemek için alt simge operatörünü kullandık; arr öğesinin on öğesi vardır, bu nedenle arr öğesindeki son öğe dizin konumunda 9'dur. Bu öğeyle yapabileceğimiz tek şey e'yi başlatmak için yaptığımız adresini almaktır. Uçtan uca yineleyici gibi (§ 3.4.1, s. 106), uçtan uca işaretçi bir öğeye işaret etmez. Sonuç olarak, son dışı bir işaretçiyi dereference veya arttıramayabiliriz.

Bu Lipmann'ın C ++ primer 5 baskısından.

Yani UB yapma.


-4

En katı anlamda, bu Tanımsız Davranış değil, uygulama tanımlıdır. Dolayısıyla, anaakım olmayan mimarileri desteklemeyi planlıyorsanız, tavsiye edilemez olsa da , muhtemelen bunu yapabilirsiniz.

Interjay tarafından verilen standart alıntı, UB'yi gösteren iyi bir tekliftir, ancak işaretçi-işaretçi aritmetiği ile ilgilentiği için (bence, biri açıkça UB'dir, diğeri değil). Doğrudan söz konusu operasyonla ilgili bir paragraf var:

[expr.post.incr] / [expr.pre.incr] İşlenen
[...] veya tamamen tanımlanmış bir nesne türüne işaretçi olacaktır.

Bir dakika, tamamen tanımlanmış bir nesne türü mü? Bu kadar? Gerçekten, yazın ? Yani bir nesneye ihtiyacınız yok mu?
İçerideki bir şeyin o kadar iyi tanımlanamayacağına dair bir ipucu bulmak gerçekten biraz okuma gerektirir. Çünkü şimdiye kadar, bunu yapmanıza izin verilmiş gibi görünüyor, kısıtlama yok.

[basic.compound] 3ne tür bir işaretçi olabileceğine dair bir açıklama yapar ve diğer üçünden hiçbiri olmadığında, işleminizin sonucu açıkça 3.4'ün altına düşer: geçersiz işaretçi .
Ancak geçersiz bir işaretçiye sahip olmanıza izin verilmediğini söylemez. Aksine, işaretçilerin düzenli olarak geçersiz hale geldiği bazı çok yaygın, normal koşulları (örneğin depolama süresi sonu) listeler. Görünüşe göre bu izin verilebilir bir şey. Ve gerçekten:

[basic.stc] 4
Geçersiz bir işaretçi değeri aracılığıyla aktarma ve geçersiz bir işaretçi değerini bir ayırma işlevine aktarma tanımsız davranışa sahiptir. Geçersiz bir işaretçi değerinin başka herhangi bir şekilde kullanılması, uygulama tanımlı davranışa sahiptir.

Orada bir "başka" yapıyoruz, bu yüzden Tanımlanmamış Davranış değil, uygulama tanımlı, dolayısıyla genel olarak izin verilebilir (uygulama açıkça farklı bir şey söylemedikçe).

Ne yazık ki, bu hikayenin sonu değil. Net sonuç buradan daha fazla değişmese de, daha kafa karıştırıcı hale gelir, "işaretçi" için daha uzun süre arama yaparsınız:

[basic.compound]
Nesne işaretçisi türünün geçerli bir değeri, bellekteki bir baytın adresini veya boş bir işaretçiyi temsil eder . T tipi bir nesnenin A [...] adresinde bulunması durumunda, değerin nasıl elde edildiğine bakılmaksızın o nesneyi işaret ettiği söylenir .
[Not: Örneğin, bir dizinin sonunu geçen adresin, dizinin o adreste bulunabilecek öğe türünün alakasız bir nesnesine işaret ettiği düşünülür. [...]].

Oku: Tamam, kimin umurunda! Bir işaretçi bellekte bir yere işaret ettiği sürece iyiyim?

[basic.stc.dynamic.safety] Bir işaretçi değeri güvenli bir şekilde oluşturulmuş bir işaretçi [blah blah]

Şu şekilde okuyun: Tamam, güvenli bir şekilde türetilmiş, her neyse. Bunun ne olduğunu açıklamıyor, aslında buna ihtiyacım olduğunu da söylemiyor. Güvenli bir şekilde türetilmiş-halt. Görünüşe göre hala güvenli bir şekilde türetilmemiş işaretçiler olabilir. Sanırım onları silme işleminin böyle iyi bir fikir olmayacağını, ancak onlara sahip olmasına izin verilebilir. Aksini söylemiyor.

Bir uygulama işaretçi güvenliğini gevşetmiş olabilir, bu durumda bir işaretçi değerinin geçerliliği, güvenli bir şekilde türetilen işaretçi değeri olup olmadığına bağlı değildir.

Oh, bu yüzden önemli değil, sadece düşündüğüm şey. Ama bekleyin ... "olmayabilir"? Bu da olabilir . Nasıl bilebilirim?

Alternatif olarak, bir uygulama katı işaretçi güvenliğine sahip olabilir; bu durumda, güvenli bir şekilde türetilmiş işaretçi değeri olmayan bir işaretçi değeri, başvurulan tam nesne dinamik depolama süresine sahip olmadığı ve daha önce erişilebilir olduğu bildirilmedikçe geçersiz bir işaretçi değeridir.

Bekle, bu yüzden declare_reachable()her işaretçiyi aramam gerekiyor mu? Nasıl bilebilirim?

Şimdi, intptr_tgüvenli bir şekilde türetilmiş işaretçinin tamsayı temsilini vererek, iyi tanımlanmış bir biçime dönüştürebilirsiniz . Tabii ki, bir tamsayı olmak, onu istediğiniz gibi artırmak tamamen meşru ve iyi tanımlanmıştır.
Ve evet, intptr_tarka kısmı iyi tanımlanmış bir işaretçiye dönüştürebilirsiniz . Sadece orijinal değer değil, artık güvenli bir şekilde türetilmiş bir işaretçiniz olduğu garanti edilmez. Yine de, genel olarak, standardın mektubuna, uygulama tanımlı olmakla birlikte, bu% 100 meşru bir şeydir:

[expr.reinterpret.cast] 5
Bir integral türü veya numaralandırma türü değeri açıkça bir işaretçiye dönüştürülebilir. Bir işaretçi yeterli büyüklükte [...] tamsayıya ve aynı işaretçi türü [...] orijinal değerine dönüştürülür; işaretçiler ve tamsayılar arasındaki eşlemeler aksi takdirde uygulama tarafından tanımlanır.

Yakalayış

İşaretçiler sıradan tamsayılardır, yalnızca işaretçiler olarak kullanılırsınız. Ah, bu doğru olsaydı!
Ne yazık ki, bunun hiç de doğru olmadığı mimariler var ve sadece geçersiz bir işaretçi oluşturmak (kaydı silme değil, sadece işaretçi kaydında bulundurmak) bir tuzağa neden olacak.

Yani bu, "tanımlanmış uygulamanın" temelidir. Bu ve bir işaretçiyi istediğiniz zaman artırmanın, tabii ki istediğiniz gibi , standartın ele almak istemediği taşmaya neden olabileceği gerçeği . Uygulama adres alanının sonu taşma konumu ile çakışmayabilir ve belirli bir mimarideki işaretçiler için taşma gibi bir şey olup olmadığını bile bilmiyorsunuzdur. Sonuçta, olası faydalarla herhangi bir ilişkide değil, kabus gibi bir karmaşa.

Öte yandan bir geçmiş nesne koşulu ile başa çıkmak kolaydır: Uygulama, hiçbir nesnenin hiç tahsis edilmediğinden emin olmalıdır, böylece adres alanındaki son bayt işgal edilir. Bu, garantinin faydalı ve önemsiz olması nedeniyle iyi tanımlanmıştır.


1
Mantık kusurlu. "Yani bir nesneye ihtiyacın yok mu?" Tek bir kurala odaklanarak Standardı yanlış yorumlar. Bu kural, programınızın iyi biçimlendirilmiş olsun, derleme zamanı ile ilgilidir. Çalışma süresi hakkında başka bir kural daha var. Sadece çalışma zamanında nesnelerin belirli bir adresteki varlığı hakkında konuşabilirsiniz. programınızın tüm kuralları karşılaması gerekir ; derleme zamanı kuralları derleme zamanı ve çalışma zamanı kuralları çalışma zamanı.
MSalters

5
"Tamam, kimin umurunda! Bir işaretçi bellekte bir yere işaret ettiği sürece, iyi miyim?" İle benzer mantık kusurlarına sahipsiniz. Hayır. Tüm kurallara uymalısınız. "Bir dizinin sonu başka bir dizinin başlangıcıdır" konusundaki zor dil, uygulamaya belleği sürekli olarak ayırma izni verir ; tahsisler arasında boş alan bırakması gerekmez. Bu, kodunuzun hem bir dizi nesnesinin sonu hem de diğerinin başlangıcı ile aynı A değerine sahip olabileceği anlamına gelir .
MSalters

1
"Bir tuzak", "uygulama tanımlı" davranışla tanımlanabilecek bir şey değildir. Interjay'in +operatöre (hangi noktadan ++aktığına) ilişkin kısıtlama bulduğunu, yani "sondan sonra" işaretinin tanımsız olduğunu gösterir.
Martin Bonner, Monica

1
@PeterCordes: Lütfen basic.stc, paragraf 4'ü okuyun . O diyor "indirection [...] tanımsız davranış. Başka herhangi bir kullanım geçersiz işaretçi değeri vardır uygulanması tanımlı davranışları" . İnsanları bu terimi başka bir anlam için kullanarak karıştırmıyorum. Bu tam bir ifadedir. O edilir değil davranışı tanımsız.
Damon

2
Artım sonrası için bir boşluk buldunuz ancak arttırma sonrası ne yaptığının tamamını alıntılamıyorsunuz. Şu an kendime bakmayacağım. Eğer varsa, istenmeyen olduğunu kabul etti. Her neyse, ISO C ++ düz bellek modelleri için @MaximEgorushkin için daha fazla şey tanımlasaydı, keyfi şeylere izin vermemenin başka nedenleri (işaretçi sarması gibi) var. Yorum var bakın Meli işaretçi karşılaştırmalar imzalanmış veya 64 bit x86 imzasız olması?
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.