İşaretçi dizine ekleme


11

Şu anda "C de Nümerik Tarifler" başlıklı bir kitap okuyorum. 1 Bunun üstesinden gelmek için, tahsis ettikten sonra işaretçiyi basitçe azaltmanızı önerir, örneğin:

float *a = malloc(size);
a--;

Bu, size 1 ile başlayan bir endeksi olan bir işaretçi verecek ve daha sonra serbest kalacak:

free(a + 1);

Bildiğim kadarıyla, bu C standardı tarafından tanımlanmamış bir davranıştır. Görünüşe göre bu HPC topluluğu içinde oldukça saygın bir kitap, bu yüzden söylediklerini göz ardı etmek istemiyorum, ancak sadece tahsis edilen aralığın dışında bir işaretçiyi azaltmak benim için oldukça kabataslak görünüyor. C bu "izin verilen" davranış mı? Bunu hem gcc hem de icc kullanarak test ettim ve her iki sonuç da hiçbir şey için endişelenmediğimi gösteriyor, ancak kesinlikle olumlu olmak istiyorum.


3
hangi C standardından bahsediyorsunuz? Soruyorum çünkü hatırladığım kadarıyla, 1990'larda K&R ve belki de ANSI C
gnat


3
"Bunu hem gcc hem de icc kullanarak test ettim ve bu sonuçların her ikisi de hiçbir şey için endişelenmediğimi gösteriyor ama kesinlikle olumlu olmak istiyorum." Derleyiciniz izin verdiği için C dili izin verdiğini asla varsaymayın. Tabii ki, gelecekte kod kırılması konusunda sorun yoksa.
Doval

5
Sinsi olmak istemeden, "Sayısal Tarifler" genellikle yazılım geliştirme veya sayısal analiz paradigması değil, yararlı, hızlı ve kirli bir kitap olarak kabul edilir. Bazı eleştirilerin bir özeti için "Sayısal Tarifler" hakkındaki Wikipedia makalesine göz atın.
Charles E. Grant

1
Bir yana, neden sıfırdan indeksliyoruz: cs.utexas.edu/~EWD/ewd08xx/EWD831.PDF
Russell

Yanıtlar:


16

Bu kod gibi haklısın

float a = malloc(size);
a--;

ANSI C standardına göre tanımlanmamış davranış verir, bölüm 3.3.6:

Hem işaretçi işleneni hem de sonuç aynı dizi nesnesinin bir üyesini veya dizi nesnesinin son üyesini geçmediği sürece davranış tanımsızdır.

Böyle bir kod için, kitaptaki C kodunun kalitesi (1990'ların sonlarında kullandığım zaman) çok yüksek değildi.

Tanımlanmamış davranışla ilgili sorun, derleyicinin sonucu ne olursa olsun, bu sonucun tanım gereği doğru olmasıdır (son derece yıkıcı ve öngörülemez olsa bile).
Neyse ki, çok az derleyici bu tür durumlar için gerçekten beklenmedik davranışlara neden olmak için çaba sarf etmektedir ve mallocHPC için kullanılan makinelerde tipik uygulama, döndüğü adresten hemen önce bazı defter tutma verilerine sahiptir, bu nedenle azalma, genellikle bu defter tutma verilerine bir işaretçi olacaktır. Oraya yazmak iyi bir fikir değildir, ancak sadece işaretçiyi oluşturmak bu sistemlerde zararsızdır.

Sadece kod farkında olabilir çalıştırma ortamı değişti aldığında veya kod farklı bir ortama taşıdık edildiğinde bölünürler.


4
Tam olarak, çok bankalı bir mimaride malloc'un bir bankadaki 0. adresi vermesi ve bunun azaltılması, bir tanesi için düşük akışlı bir CPU tuzağına neden olabilir.
Vality

1
Bunun "şanslı" olduğuna katılmıyorum. Eğer tanımlanamayan davranışlar çağırdığınızda derhal çöktü derleyiciler yayılan olsaydı çok daha iyi olacağını düşünüyorum.
David Conrad

4
@DavidConrad: O zaman C sizin için bir dil değil. C'deki tanımlanmamış davranışların çoğu kolayca tespit edilemez veya sadece ciddi bir performans isabeti ile.
Bart van Ingen Schenau

"Derleyici anahtarıyla" eklemeyi düşünüyordum. Açıkçası bunu optimize edilmiş kod için istemezsiniz. Ama haklısın, bu yüzden on yıl önce C yazmayı bıraktım.
David Conrad

@BartvanIngenSchenau, 'şiddetli performans vuruşu' ile ne demek istediğinize bağlı olarak, hata ayıklama için çok yararlı olan sanatizerler (asan, tsan, ubsan, valgrind vb.) İçin C (örneğin clang + klee) için sembolik yürütme vardır.
Maciej Piechotka

10

Resmi olarak, dizinin dışında hiç işaret edilmemiş olsa bile , dizinin dışında bir işaretçi noktasına sahip olmak tanımlanmamış bir davranıştır .

Uygulamada, eğer işlemcinizin düz bellek modeline sahip (gibi garip üslerden x86-16 ) ve eğer derleyici size çalışma zamanı hatası veya yanlış optimizasyonu vermez Geçersiz bir işaretçi oluşturmak, sonra kod irade işi sadece iyi.


1
Mantıklı. Ne yazık ki, bu benim zevkime göre iki çok fazla.
wolfPack88

3
Son nokta IMHO'dur. Derleyiciler bu zamanlar sadece platform "UB" için "doğal olarak" ne yaparsa yapsın, ama optimize ediciler agresif bir şekilde sömürdüğünden , bu kadar hafif oynamam.
Matteo Italia

3

İlk olarak, tanımsız davranış. Günümüzde bazı optimize edici derleyiciler tanımsız davranış konusunda çok agresifleşmektedir. Örneğin, bu durumda a-- tanımsız bir davranış olduğundan, derleyici bir komutu ve işlemci döngüsünü kaydetmeye karar verebilir ve a'yı azaltmayabilir. Hangi resmi olarak doğru ve yasal.

Bunu göz ardı ederek, 1 veya 2 veya 1980 çıkarabilirsiniz. Örneğin, 1980-2013 yılları için finansal verilerim varsa, 1980 çıkarabilirim. Şimdi float * a = malloc (size) alırsak; a - k'nin boş gösterici olacağı kesin bir büyük sabit k vardır. Bu durumda, bir şeyin yanlış gitmesini bekliyoruz.

Şimdi büyük bir yapı alın, boyut olarak bir megabayt deyin. İki yapıya işaret eden bir işaretçi p atayın. p - 1 bir boş gösterici olabilir. p - 1 etrafına sarılabilir (bir yapı megabaytsa ve malloc bloğu adres alanının başlangıcından itibaren 900 KB ise). Yani derleyicinin kötülüğü olmadan p - 1> p olabilir. İşler ilginç olabilir.


1

... tahsis edilen aralığın dışında bir işaretçiyi azaltmak sadece bana çok kabataslak geliyor. C bu "izin verilen" davranış mı?

İzin? Evet. İyi bir fikir? Genellikle değil.

C, montaj dili için bir kısayoldur ve montaj dilinde işaretçiler yoktur, sadece bellek adresleri vardır. C'nin işaretçileri, aritmetiğe maruz kaldıklarında işaret ettiklerinin boyutuna göre artan veya azalan yan davranışa sahip bellek adresleridir. Bu, sözdizimi açısından aşağıdakilerin iyi olmasını sağlar:

double *p = (double *)0xdeadbeef;
--p;  // p == 0xdeadbee7, assuming sizeof(double) == 8.
double d = p[0];

Diziler aslında C'de bir şey değildir; sadece diziler gibi davranan bitişik bellek aralıklarına işaret ediyorlar. []Operatör böylece, işaretçi aritmetik yapıyor ve çözümleyecek bir kısaltmadır a[x]aslında aracı *(a + x).

Yukarıdakileri yapmak için geçerli birkaç neden vardır, örneğin birkaç tane G / Ç cihazı double0xdeadbee7 ve ile eşleştirilen0xdeadbeef . Bunu yapmak için çok az program gerekir.

&Operatörünü kullanmak veya aramak gibi bir şeyin adresini oluşturduğunuzdamalloc() orijinal işaretçiyi olduğu gibi tutmak istersiniz, böylece işaret ettiği şeyin aslında geçerli bir şey olduğunu bilirsiniz. İşaretçiyi küçültmek, bazı hatalı kodların kodunu kaldırmaya çalışabilir, hatalı sonuçlar elde edebilir, bir şeyleri zorlayabilir veya ortamınıza bağlı olarak bir segmentasyon ihlali gerçekleştirebilir. Bu özellikle doğrudur malloc(), çünkü free()tüm halkanın gevşemesine neden olacak değiştirilmiş bir versiyona değil, orijinal değeri geçmeyi hatırlamaya kimin çağırdığını yüklersiniz.

C'de 1 tabanlı dizilere ihtiyacınız varsa, asla kullanılmayacak bir ek öğe ayırma pahasına güvenli bir şekilde yapabilirsiniz:

double *array_create(size_t size) {
    // Wasting one element, so don't allow it to be full-sized
    assert(size < SIZE_MAX);
    return malloc((size+1) * sizeof(double));
}

inline double array_index(double *array, size_t index) {
    assert(array != NULL);
    assert(index >= 1);  // This is a 1-based array
    return array[index];
}

Bunun üst sınırı aşmaya karşı korumak için hiçbir şey yapmadığını, ancak bu işlemek için yeterince kolay olduğunu unutmayın.


Zeyilname:

C99 taslağından bazı bölüm ve ayetler (üzgünüm, bağlantı kurabileceğim tek şey bu):

§6.5.2.1.1, alt simge işleci ile kullanılan ikinci ("diğer") ifadenin tamsayı tipinde olduğunu belirtir. -1bir tamsayıdır ve bu p[-1]geçerli kılar ve dolayısıyla işaretçiyi de&(p[-1]) geçerli . Bu, o konumdaki belleğe erişmenin tanımlı davranış üreteceği anlamına gelmez, ancak imleç hala geçerli bir imleçtir.

§6.5.2.2, dizi alt simge operatörünün, öğe numarasını işaretçiye ekleme eşdeğeri olarak değerlendirdiğini söylüyor. p[-1] eşdeğerdir *(p + (-1)). Hala geçerli, ancak istenen davranışlara yol açmayabilir.

§6.5.6.8 diyor (benimki vurgu):

Bir tamsayı türüne sahip bir ifade bir işaretçiye eklendiğinde veya bir işaretçiden çıkarıldığında, sonuç işaretçi işleneninin türüne sahiptir.

... ifade eğer Pişaret iinci bir dizi nesne, ifadelerin elemanı (P)+N(eşit biçimde, N+(P)) ve (P)-N (burada Ndeğere sahiptir n, ile) alanına sırasıyla i+ninci ve i−ndizi nesne inci elemanları, bu ana kadar mesafede .

Bu, işaretçi aritmetiğinin sonuçlarının bir dizideki bir öğeye işaret etmesi gerektiği anlamına gelir. Aritmetiğin bir kerede yapılması gerektiği anlamına gelmez. Bu nedenle:

double a[20];

// This points to element 9 of a; behavior is defined.
double d = a[-1 + 10];

double *p = a - 1;  // This is just a pointer.  No dereferencing.

double e = p[0];   // Does not point at any element of a; behavior is undefined.
double f = p[1];   // Points at element 0 of a; behavior is defined.

Bir şeyleri bu şekilde yapmanızı tavsiye eder miyim? Yapmıyorum ve cevabım bunun nedenini açıklıyor.


8
-1 C standardının tanımsız sonuçlar ürettiği bildirdiği kodu içeren bir 'izin verilen' tanımı yararlı bir sonuç değildir.
Pete Kirkham

Diğerleri bunun tanımsız davranış olduğuna dikkat çekti, bu yüzden "izinli" olduğunu söylememelisiniz. Bununla birlikte, fazladan kullanılmayan bir eleman 0 tahsis etme önerisi iyidir.
200_success

Bu gerçekten doğru değil, lütfen en azından bunun C standardı tarafından yasaklandığını unutmayın.
Vality

@PeteKirkham: Kabul etmiyorum. Cevabımın ekine bakın.
Blrfl

4
@Blrfl Bir işaretçiye bir tamsayı eklenmesi durumunda ISO C11 standardının 6.5.6'sı: "Hem işaretçi işleneni hem de sonuç aynı dizi nesnesinin öğelerine veya dizi nesnesinin son öğesinin ötesine işaret ediyorsa , değerlendirme bir taşma üretmemelidir, aksi takdirde davranış tanımsızdır. "
Vality
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.