Boş işaretçilerin% p ile yazdırılması tanımsız bir davranış mı?


93

%pDönüşüm tanımlayıcısıyla boş işaretçiler yazdırmak tanımsız bir davranış mı ?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

Soru C standardı için geçerlidir, C uygulamaları için geçerli değildir.


A aslında kimsenin (C komitesi dahil) bunu çok önemsediğini düşünmüyor. Bu, pratik önemi olmayan (veya neredeyse hiç) oldukça yapay bir sorundur.
P__J Polonya'daki kadınları destekliyor

printf sadece değeri gösterir ve dokunmaz (sivri uçlu nesneyi okumak veya yazmak anlamında) - UB olamaz i işaretçisi tip değeri için geçerlidir (NULL geçerli değerdir)
P__J kadınları destekler Polonya

3
@PeterJ, söylediklerinizin doğru olduğunu söyleyelim (standart aksini açıkça belirtse de), tek başına bu konuda tartışıyor olmamız soruyu geçerli ve doğru bir soru haline getiriyor, çünkü standardın aşağıda alıntılanan kısmı gibi görünüyor düzenli bir geliştirici için neler olup bittiğini anlamak çok zor .. Anlamı: soru olumsuz oylamayı hak etmiyor, çünkü bu sorunun açıklığa kavuşturulması gerekiyor!
Peter Varo


2
@PeterJ bu farklı bir hikaye, açıklama için teşekkürler :)
Peter Varo

Yanıtlar:


93

Bu, İngilizce dilinin sınırlamalarına ve standarttaki tutarsız yapıya tabi olduğumuz garip köşe durumlarından biridir. Yani en iyi ihtimalle, kanıtlamak imkansız olduğu için ikna edici bir karşı argüman yapabilirim :) 1


Sorudaki kod, iyi tanımlanmış davranış sergiliyor.

As [7.1.4] Söz temelidir, orada başlayalım:

Açıkça izleyin ayrıntılı açıklamalarda aksi belirtilmedikçe aşağıdaki ifadelerden her biri geçerlidir: bir işleve bir argüman geçersiz bir değer (varsa gibi işlev etki alanının dışında bir değere, ya da programın adres alanı dışında bir işaretçi, veya boş gösterici , [... diğer örnekler ...] ) [...] davranış tanımsız. [... diğer ifadeler ...]

Bu beceriksiz bir dildir. Yorumlardan biri, listedeki öğelerin, tek tek açıklamalar tarafından geçersiz kılınmadıkça, tüm kitaplık işlevleri için UB olduğudur. Ancak liste ayrıntılı değil açıklayıcı olduğunu belirten "gibi" ile başlar. Örneğin, dizelerin doğru boş sonlandırılmasından bahsetmez (ör strcpy. Davranışı için kritiktir ).

Bu nedenle, 7.1.4'ün amacı / kapsamı, basitçe "geçersiz bir değerin" UB'ye yol açtığıdır ( aksi belirtilmedikçe ). Neyin "geçersiz değer" olarak sayılacağını belirlemek için her işlevin açıklamasına bakmalıyız.

Örnek 1 - strcpy

[7.21.2.3] yalnızca şunu söylüyor:

strcpyDize ile gösterilen fonksiyon kopyalar s2diziye (sonlandırıcı boş karakter dahil) tarafından işaret edilen s1. Kopyalama çakışan nesneler arasında gerçekleşirse, davranış tanımsızdır.

Boş işaretçilerden açıkça bahsetmez, ancak boş sonlandırıcılardan da bahsetmez. Bunun yerine, "ile gösterilen dizeden s2" tek geçerli değerlerin dizeler olduğu çıkarılır (yani, boş sonlu karakter dizilerine işaretçiler).

Aslında, bu model bireysel tanımlamalarda görülebilir. Diğer bazı örnekler:

  • [7.6.4.1 (fenv)] Mevcut kayan noktalı ortamı depolamak gösterilen nesne tarafındanenvp

  • [7.12.6.4 (frexp)] int tamsayı depolamak gösterilen nesne tarafındanexp

  • [7.19.5.1 (fclose)] tarafından işaret edilen akışstream

Örnek 2 - printf

[7.19.6.1] şunu söylüyor %p:

p- Argüman, bir işaretçi olacaktır void. İşaretçinin değeri, uygulama tanımlı bir şekilde bir dizi yazdırma karakterine dönüştürülür.

Null geçerli bir işaretçi değeridir ve bu bölüm null değerinin özel bir durum olduğunu veya işaretçinin bir nesneyi göstermesi gerektiğini açıkça belirtmez. Böylece tanımlanmış davranış.


1. Bir standart yazarı öne çıkmadıkça veya olayları açıklığa kavuşturan bir mantık belgesine benzer bir şey bulamadıkça .


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Bhargav Rao

1
"yine de boş sonlandırıcılardan bahsetmiyor" Örnek 1'de zayıftır - strcpy belirtim " dizeyi kopyalar" dediği için . string , açıkça bir boş karaktere sahip olarak tanımlanır .
chux

1
@chux - Bu biraz benim amacım - 7.1.4'teki listenin kapsamlı olduğunu varsaymak yerine, bağlamdan neyin geçerli / geçersiz olduğu sonucuna varmalı . (Bununla birlikte, cevabımın bu kısmının varlığı, o zamandan beri silinen yorumlar bağlamında, strcpy'nin bir karşı örnek olduğunu savunarak biraz daha anlamlı hale geldi.)
Oliver Charlesworth

1
İşin kilit noktası okuyucu yorumlayacaktır nasıl gibi . Olası geçersiz değerlerin bazı örnekleri olduğu anlamına mı geliyor ? Her zaman geçersiz değerler olan bazı örnekler anlamına mı geliyor ? Kayıt için, ilk yoruma gidiyorum.
ninjalj

1
@ninjalj - Evet, katılıyorum. Buradaki cevabımda anlatmaya çalıştığım şey aslında bu, yani "bunlar geçersiz değerler olabilecek şey türlerinin örnekleridir". :)
Oliver Charlesworth

20

Kısa Cevap

Evet . Boş işaretçilerin %pdönüşüm belirticisi ile yazdırılması tanımsız davranışa sahiptir. Bunu söyledikten sonra, yanlış davranacak herhangi bir mevcut uyumlu uygulamadan habersizim.

Cevap C standartlarından herhangi biri için geçerlidir (C89 / C99 / C11).


Uzun Cevap

%pVoid tip işaretçi bir argüman bekler belirleyicisi dönüşüm, yazdırılabilir karaktere ibrenin dönüşüm uygulaması-tanımlanmıştır. Boş göstericinin beklendiğini belirtmez.

Standart kitaplık işlevlerine giriş, (standart kitaplık) işlevlerine bağımsız değişken olarak boş işaretçilerin, aksi açıkça belirtilmediği sürece geçersiz değerler olarak kabul edildiğini belirtir.

C99 / C11 §7.1.4 p1

[...] Bir işleve yönelik bağımsız değişken geçersiz bir değere sahipse ([...] boş gösterici, [...] gibi davranış tanımsızdır.

Geçerli bağımsız değişkenler olarak boş işaretçiler bekleyen (standart kitaplık) işlevlerine örnekler:

  • fflush() "tüm akışları" (geçerli olan) temizlemek için bir boş gösterici kullanır.
  • freopen() akımla "şu anda ilişkili" dosyayı belirtmek için boş bir gösterici kullanır.
  • snprintf() 'n' sıfır olduğunda boş gösterici geçmesine izin verir.
  • realloc() yeni bir nesneyi tahsis etmek için boş gösterici kullanır.
  • free() boş gösterici geçmesine izin verir.
  • strtok() sonraki çağrılar için boş gösterici kullanır.

Durumunu ele alırsak snprintf(), 'n' sıfır olduğunda boş gösterici geçişine izin vermek mantıklıdır, ancak bu, benzer bir sıfır 'n'ye izin veren diğer (standart kütüphane) işlevler için geçerli değildir. Örneğin: memcpy(), memmove(), strncpy(), memset(), memcmp().

Sadece standart kütüphanenin girişinde değil, aynı zamanda bu fonksiyonların girişinde de belirtilmiştir:

C99 §7.21.1 p2 / C11 §7.24.1 p2

size_tN olarak bildirilen bir bağımsız değişken , bir işlev için dizinin uzunluğunu belirttiğinde, n, bu işleve yapılan bir çağrıda sıfır değerine sahip olabilir. Bu alt maddede belirli bir işlevin açıklamasında açıkça aksi belirtilmediği sürece, böyle bir çağrıdaki işaretçi argümanları 7.1.4'te açıklandığı gibi yine de geçerli değerlere sahip olacaktır.


Kasıtlı mı?

%pBoş gösterici ile UB'nin aslında kasıtlı olup olmadığını bilmiyorum , ancak standart açıkça boş işaretçilerin standart kütüphane işlevlerine argümanlar olarak geçersiz değerler olarak kabul edildiğini belirttiğinden ve daha sonra gider ve null olduğu durumları açıkça belirtir. işaretçi geçerli bir argüman (snprintf, ücretsiz, vs), ve o zaman gider ve bir kez daha argümanlar bile sıfıra geçerli olabilmesi için 'n' vakası (gereksinimini tekrarlar memcpy, memmove, memset), sonra bunun olduğunu varsaymak makul olduğunu düşünüyorum C standartları komitesi bu tür şeylerin tanımlanmamış olmasıyla fazla ilgilenmez.


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Bhargav Rao

1
@JeroenMostert: Bu argümanın amacı nedir? 7.1.4'te verilen alıntı oldukça açık, değil mi? Hakkında tartışmak ne var ki "açıkça aksi belirtilmedikçe" o zaman varlık değil aksi belirtilmediği? (İlgisiz) dize işlevi kitaplığının benzer bir ifadeye sahip olduğu, bu nedenle ifadenin yanlış görünmediği gerçeği hakkında tartışılacak ne var? Bu cevabın ( pratikte gerçekten yararlı olmasa da ) olabildiğince doğru olduğunu düşünüyorum.
Damon

3
@Damon: Efsanevi donanımınız efsanevi değildir, geçerli adresleri temsil etmeyen değerlerin adres kayıtlarına yüklenemeyebileceği birçok mimari vardır. Bununla birlikte, genel bir mekanizma olarak bu platformlarda çalışmak için işlev bağımsız değişkenleri olarak boş işaretçilerin aktarılması hala gereklidir. Sadece yığına bir tane koymak işleri havaya uçurmaz.
Jeroen Mostert

1
@anatolyg: x86 işlemcilerde adresler iki bölümden oluşur - bir segment ve bir ofset. 8086'da, bir segment yazmacının yüklenmesi, başka herhangi bir makinenin yüklenmesi gibidir, ancak sonraki tüm makinelerde bir segment tanımlayıcısı getirir. Geçersiz bir tanımlayıcı yüklemek tuzağa neden olur. 80386 ve üstü işlemciler için kod bir sürü Ancak sadece bir segmentini kullanır ve böylece hiçbir zaman yükler parça kayıtlar hiç .
supercat

1
Bence boş gösterici ile yazdırmanın %ptanımsız bir davranış olmaması gerektiği konusunda herkes hemfikirdir
MM

-1

C Standardının yazarları, bir uygulamanın herhangi bir özel amaca uygun olması için karşılaması gereken tüm davranışsal gereksinimleri ayrıntılı bir şekilde listelemek için hiçbir çaba sarf etmediler. Bunun yerine, Standart'ın gerektirip gerektirmediğine bakılmaksızın, derleyiciler yazan kişilerin belirli bir miktarda sağduyu kullanacağını umuyorlardı.

Bir şeyin UB'yi çağırıp çağırmadığı sorusu nadiren kendi başına yararlıdır. Asıl önemli sorular şunlardır:

  1. Kaliteli bir derleyici yazmaya çalışan biri, öngörülebilir bir şekilde davranmasını sağlamalı mı? Açıklanan senaryo için cevap açıkça evet.

  2. Programcılar, normal platformlara benzeyen herhangi bir şey için kaliteli derleyicilerin öngörülebilir bir şekilde davranmasını bekleme hakkına sahip olmalı mı? Açıklanan senaryoda cevabın evet olduğunu söyleyebilirim.

  3. Bazı küstah derleyici yazarları, garip bir şey yapmayı haklı çıkarmak için Standardın yorumunu genişletebilir mi? Umarım olmazdı ama göz ardı etmem.

  4. Temizleyici derleyiciler davranış hakkında ciyaklamalı mı? Bu, kullanıcılarının paranoya düzeyine bağlı olacaktır; temizleyici bir derleyici muhtemelen bu tür davranışlar hakkında ciyaklamak için varsayılan olmamalıdır, ancak programların tuhaf davranan "akıllı" / aptal derleyicilere taşınması durumunda yapılacak bir yapılandırma seçeneği sunabilir.

Eğer Standardın makul bir yorumu, bir davranışın tanımlandığını ima ediyorsa, ancak bazı derleyici yazarları, aksini yapmak için yorumu esnetiyorsa, Standardın ne söylediği gerçekten önemli mi?


1. Programcıların, modern / agresif optimizasyon uzmanları tarafından yapılan varsayımların "makul" veya "kaliteli" olarak gördükleri şeylerle çelişkili bulmaları alışılmadık bir durum değildir. 2. Spesifikasyondaki belirsizlikler söz konusu olduğunda, uygulayıcıların hangi özgürlükleri üstlenebilecekleri konusunda anlaşmazlığa düşmeleri alışılmadık bir durum değildir. 3. C standartları komitesi üyelerine gelince, ne olması gerektiği bir yana , 'doğru' yorumun ne olduğu konusunda her zaman hemfikir olmasalar bile . Yukarıda belirtilenler göz önüne alındığında, kimin makul yorumunu takip etmeliyiz?
Dror K.

6
UB'nin yararlılığı veya derleyicilerin nasıl davranması gerektiği hakkında bir tezle "bu belirli kod parçası UB'yi çağırıyor mu, değil mi" sorusunu yanıtlamak, bir yanıt için zayıf bir girişimdir, özellikle de bunu şu şekilde kopyalayıp yapıştırabildiğiniz için belirli bir UB ile ilgili hemen hemen her soruya bir cevap . Retorik gelişmenize bir yanıt olarak: evet, bazı derleyici yazarlarının ne yaptığı veya bunu yapmak için onlar hakkında ne düşündüğünüz önemli değil, Standard'ın ne söylediği gerçekten önemlidir, çünkü Standart, hem programcıların hem de derleyici yazarların başlangıç ​​noktasıdır.
Jeroen Mostert

1
@JeroenMostert: "X Tanımsız davranışı çağırır mı?" Sorusunun yanıtı genellikle kişinin soru ile ne anlama geldiğine bağlı olacaktır. Bir programın Tanımsız Davranışa sahip olduğu kabul edilirse, Standart uygun bir uygulamanın davranışına herhangi bir gereksinim getirmiyorsa, neredeyse tüm programlar UB'yi çağırır. Standardın yazarları, bir uygulama Stadard'daki çeviri sınırlarını uygulayan en az bir (muhtemelen uydurulmuş) kaynak metni doğru bir şekilde işleyebildiği sürece, bir program işlevi çok derin çağırırsa, uygulamaların keyfi bir şekilde davranmasına açıkça izin verir.
supercat

@supercat: çok ilginç, ancak printf("%p", (void*) 0)Standarda göre tanımlanmamış davranış mı değil mi? Derinlemesine iç içe geçmiş işlev çağrıları, Çin'deki çay fiyatı kadar bununla ilgilidir. Ve evet, UB gerçek dünya programlarında çok yaygındır - ne olacak?
Jeroen Mostert

1
@JeroenMostert: Standart geniş kapsamlı bir uygulamanın hemen hemen her programın UB'ye sahip olduğunu kabul etmesine izin vereceğinden, önemli olan şey geniş olmayan uygulamaların davranışı olacaktır. Fark etmediyseniz, UB hakkında sadece bir kopyala / yapıştır yazmadım, aynı zamanda sorunun %polası her anlamı için hakkındaki soruyu cevapladım.
supercat
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.