İşaretçilerin bu şekilde kullanımını tahmin edilemez kılan nedir?


108

Şu anda ipuçları öğreniyorum ve profesörüm bu kodu örnek olarak verdi:

//We cannot predict the behavior of this program!

#include <iostream>
using namespace std;

int main()
{
    char * s = "My String";
    char s2[] = {'a', 'b', 'c', '\0'};

    cout << s2 << endl;

    return 0;
}

Yorumlara, programın davranışını tahmin edemeyeceğimizi yazdı. Bunu tam olarak öngörülemez yapan nedir? Ben yanlış bir şey görmüyorum.


2
Profesörün kodunu doğru şekilde kopyaladığınızdan emin misiniz? Bu programın "öngörülemeyen" davranışlar üretebileceğini iddia etmek resmi olarak mümkün olsa da, bunu yapmak hiçbir anlam ifade etmiyor. Ve herhangi bir profesörün öğrencilere "öngörülemez" ifadesini göstermek için bu kadar ezoterik bir şey kullanacağından şüpheliyim.
AnT

1
@ Orbit'te Hafiflik Yarışları: Derleyicilerin, gerekli tanılama mesajlarını verdikten sonra bozuk biçimli kodu "kabul etmelerine" izin verilir. Ancak dil belirtimi, kodun davranışını tanımlamaz. Yani, başlatmadaki hatadan dolayı s, program, bazı derleyiciler tarafından kabul edilirse, resmi olarak öngörülemeyen davranışlara sahiptir.
AnT

2
@TheParamagneticCroissant: Hayır. Başlatma, modern zamanlarda kötü biçimlendirilmiş.
Orbit'te Hafiflik Yarışları

2
@ The Paramagnetic Croissant: Yukarıda söylediğim gibi, dilin "derlenememesi" için kötü biçimlendirilmiş kod gerektirmez. Derleyicilerin sadece bir tanılama yapması gerekir. Bundan sonra devam etmelerine ve kodu "başarıyla" derlemelerine izin verilir. Ancak, bu tür bir kodun davranışı, dil spesifikasyonu tarafından tanımlanmamıştır.
AnT

2
Profesörünüzün size verdiği cevabın ne olduğunu bilmek isterim.
Daniël W. Crompton

Yanıtlar:


125

Programın davranışı, kötü biçimlendirilmiş olduğu için mevcut değildir.

char* s = "My String";

Bu yasa dışıdır. 2011'den önce 12 yıldır kullanımdan kaldırılmıştı.

Doğru satır:

const char* s = "My String";

Bunun dışında program gayet iyi. Profesörünüz daha az viski içmeli!


10
-pedantic ile yapar: main.cpp: 6: 16: uyarı: ISO C ++, bir dize sabitini 'char *' [-Wpedantic] 'e dönüştürmeyi yasaklar
marcinj

17
@black: Hayır, dönüştürme işleminin yasadışı olması programı kötü biçimlendiriyor. Geçmişte kullanımdan kaldırıldı . Artık geçmişte değiliz.
Orbit'te Hafiflik Yarışları

17
(Bu aptalca çünkü 12 yıllık kullanımdan
kaldırmanın

17
@black: kötü oluşan bir programın davranıştır değil "mükemmel tanımlanan".
Orbit'te Hafiflik Yarışları

11
Ne olursa olsun, soru C ++ ile ilgili, GCC'nin belirli bir sürümüyle ilgili değil.
Orbit'te Hafiflik Yarışları

81

Cevap şudur: hangi C ++ standardına karşı derlediğinize bağlıdır. Tüm kod, bu satır haricinde tüm standartlarda mükemmel şekilde biçimlendirilmiştir:

char * s = "My String";

Şimdi, dize değişmezinin türü var const char[10]ve biz ona const olmayan bir gösterici başlatmaya çalışıyoruz. charDize değişmezleri ailesi dışındaki tüm diğer türler için , böyle bir başlatma her zaman yasadışıdır. Örneğin:

const int arr[] = {1};
int *p = arr; // nope!

Bununla birlikte, C ++ 11 öncesi, dize değişmezleri için, §4.2 / 2'de bir istisna vardı:

Geniş dize değişmezi olmayan bir dizge değeri (2.13.4), " işaretçi karakter " türünde bir rvalue değerine dönüştürülebilir ; [...]. Her iki durumda da sonuç, dizinin ilk elemanına bir göstericidir. Bu dönüştürme, yalnızca açık bir uygun işaretçi hedef türü olduğunda ve bir l değerinden bir r değerine dönüştürmeye genel bir ihtiyaç olduğunda dikkate alınmaz. [Not: bu dönüşüm kullanımdan kaldırıldı . Ek D'ye bakınız. ]

Dolayısıyla C ++ 03'te kod mükemmeldir (kullanımdan kaldırılmış olsa da) ve net, öngörülebilir davranışa sahiptir.

C ++ 11'de, bu blok mevcut değildir - dönüştürülen dize değişmezleri için böyle bir istisna yoktur. char* ve bu nedenle kod, az önce int*verdiğim örnek kadar biçimsizdir . Derleyici bir tanılama yayınlamak zorundadır ve ideal olarak, bunun gibi C ++ tipi sistemin açık ihlalleri olan durumlarda, iyi bir derleyicinin sadece bu konuda uyumlu olmasını değil (örneğin bir uyarı yayınlayarak) başarısız olmasını bekleriz. düpedüz.

Kod ideal olarak derlenmemelidir - ancak hem gcc hem de clang'da işe yarar (sanırım, bu tür sistem deliğinin on yıldan fazla bir süredir kullanımdan kaldırılmasına rağmen, muhtemelen az kazançla kırılacak çok sayıda kod var). Kod kötü biçimlidir ve bu nedenle kodun davranışının ne olabileceği konusunda mantık yürütmek mantıklı değildir. Ancak bu özel durumu ve daha önce izin verilen geçmişini göz önünde bulundurarak, ortaya çıkan kodu örtük bir const_castşeymiş gibi yorumlamanın mantıksız bir uzantı olduğuna inanmıyorum, şöyle bir şey:

const int arr[] = {1};
int *p = const_cast<int*>(arr); // OK, technically

Bununla, programın geri kalanı mükemmel bir şekilde, çünkü bir daha asla dokunmuyorsunuz s. Oluşturulmuş bir nesneyi işaretçi olmayan bir şekilde okumak tamamen tamamdır. Oluşturulmuş bir nesneyi böyle bir işaretçi aracılığıyla yazmak tanımsız bir davranıştır:constconstconst

std::cout << *p; // fine, prints 1
*p = 5;          // will compile, but undefined behavior, which
                 // certainly qualifies as "unpredictable"

sKodunuzun herhangi bir yerinde herhangi bir değişiklik olmadığından, program C ++ 03'te iyidir, C ++ 11'de derlenemez, ancak yine de yapar - ve derleyicilerin buna izin verdiği göz önüne alındığında, içinde hala tanımlanmamış bir davranış yoktur † . Derleyicilerin hala C ++ 03 kurallarını [yanlış] yorumladıkları izinlerle, "öngörülemeyen" davranışa yol açacak hiçbir şey görmüyorum. sYine de yazın ve tüm bahisler kapanır. Hem C ++ 03 hem de C ++ 11'de.


† Yine de, tanım gereği kötü biçimlendirilmiş kod makul davranış beklentisi
sağlamaz not Olmazsa , Matt McNabb'ın cevabına bakın


Sanırım burada "öngörülemez", profesör tarafından standardın, bir derleyicinin kötü biçimlendirilmiş kodla ne yapacağını tahmin etmek için kullanamayacağı anlamına geldiğini düşünüyorum (bir tanılamanın ötesinde). Evet, C ++ 03'ün tedavi edilmesi gerektiğini söylediği gibi davranabilir ve ("Gerçek İskoç Yok" yanılgısı riski altında) sağduyu, mantıklı bir derleyici-yazarın tek şeyin bu olduğunu biraz güvenle tahmin etmemizi sağlar. hiç bir zaman kodun derlenip derlenmeyeceğini seçecektir. Sonra yine, dizgeyi const olmayana dönüştürmeden önce değişmez dizeyi tersine çevirmenin anlamı olarak değerlendirebilir. Standart C ++ umursamıyor.
Steve Jessop

2
@SteveJessop Bu yorumu almam. Bu, ne tanımlanmamış bir davranış ne de standartların teşhis gerektirmediği için etiketlediği kötü biçimlendirilmiş kod kategorisidir. Bu çok öngörülebilir olması gereken basit bir tür sistem ihlalidir (C ++ 03'te derler ve normal şeyler yapar, C ++ 11'de derleme başarısız olur). Kodun tahmin edilemez olduğunu öne sürmek için derleyici hatalarını (veya sanatsal lisansları) gerçekten kullanamazsınız - aksi takdirde tüm kodlar totolojik olarak öngörülemez olur.
Barry

Derleyici hatalarından bahsetmiyorum, standardın kodun davranışını (varsa) tanımlayıp tanımlamadığından bahsediyorum. Profesörün de aynı şeyi yaptığından şüpheleniyorum ve "öngörülemez" sadece mevcut standardın davranışı tanımlamadığını söylemenin ham yumruklu bir yoludur. Her neyse, bana öyle geliyor ki, profesör yanlış bir şekilde bunun tanımlanmamış davranışları olan iyi biçimlendirilmiş bir program olduğuna inanıyor.
Steve Jessop

1
Hayır değil. Standart, kötü biçimlendirilmiş programların davranışını tanımlamaz.
Steve Jessop

1
@supercat: Bu adil bir nokta, ancak bunun ana neden olduğuna inanmıyorum. Sanırım standardın kötü biçimlendirilmiş programların davranışını belirtmemesinin ana nedeni, derleyicilerin iyi biçimlendirilmemiş sözdizimi ekleyerek dile uzantıları destekleyebilmesidir (Objective C'nin yaptığı gibi). Uygulamanın başarısız bir derlemeden sonra temizlemeden tam bir korku yaratmasına izin vermek sadece bir bonus :-)
Steve Jessop

20

Diğer cevaplar, bir const chardizinin bir .net dosyasına atanması nedeniyle bu programın C ++ 11'de kötü biçimlendirildiğini kapsamaktadır char *.

Ancak program C ++ 11 öncesinde de kötü biçimlendirilmişti.

operator<<Aşırı yükler vardır <ostream>. Şartı iostreamdahil etmekostream C ++ 11 ilave edildi.

Geçmişte çoğu uygulama iostreamşunları içeriyordu:ostream belki Uygulama kolaylığı açısından ya da belki daha iyi bir QoI sağlamak amacıyla, her neyse.

Ancak aşırı yüklemeleri tanımlamadan iostreamyalnızca ostreamsınıfı tanımlamak uygun olacaktır operator<<.


13

Bu programda gördüğüm ufak tefek yanlış olan tek şey, bir değişkene değişmez bir dize atamamanız gerektiğidir. char , göstericiye , ancak bu genellikle bir derleyici uzantısı olarak kabul edilir.

Aksi takdirde, bu program bana iyi tanımlanmış görünüyor:

  • Karakter dizilerinin parametre olarak aktarıldığında nasıl karakter işaretçisi haline geleceğini belirleyen kurallar (örneğin, cout << s2 ) iyi tanımlanmıştır.
  • Dizi, operator<<a char*(veya a const char*) için bir koşul olan boş sonlandırılmıştır .
  • #include <iostream>içerir <ostream>, sırayla tanımlar operator<<(ostream&, const char*), böylece her şey yerinde görünür.

12

Yukarıda belirtilen nedenlerden dolayı derleyicinin davranışını tahmin edemezsiniz. ( Olmalı derlemek için başarısız, ama olmayabilir de.)

Derleme başarılı olursa, davranış iyi tanımlanmıştır. Kesinlikle programın davranışını tahmin edebilirsiniz.

Derleme başarısız olursa, program yoktur. Derlenmiş bir dilde, program kaynak kodu değil yürütülebilirdir. Yürütülebilir bir dosyanız yoksa, bir programınız da yoktur ve var olmayan bir şeyin davranışından bahsedemezsiniz.

Bu yüzden hocanızın açıklamasının yanlış olduğunu söyleyebilirim. Bu kodla karşılaştığınızda derleyicinin davranışını tahmin edemezsiniz, ancak bu, programın davranışından farklıdır . Yani nit seçecekse, haklı olduğundan emin olsa iyi olur. Ya da elbette onu yanlış aktarmış olabilirsiniz ve hata onun söylediklerini çevirmenizdedir.


10

Diğerlerinin de belirttiği gibi, önceki sürümlerde geçerli olmasına rağmen, kod C ++ 11 kapsamında yasadışıdır. Sonuç olarak, C ++ 11 için bir derleyicinin en az bir tanılama yayınlaması gerekir, ancak derleyicinin davranışı veya derleme sisteminin geri kalanı bunun ötesinde belirtilmez. Standarttaki hiçbir şey, bir derleyicinin bir hataya yanıt olarak aniden çıkmasını engellemez, bir bağlayıcının geçerli olduğunu düşündüğü kısmen yazılmış bir nesne dosyası bırakarak, bozuk bir yürütülebilir dosya verir.

İyi bir derleyici çıkmadan önce, üretmiş olması beklenen herhangi bir nesne dosyasının ya geçerli, yok ya da geçersiz olarak tanınabilir olmasını her zaman sağlamalıdır, ancak bu tür sorunlar Standardın yargı alanının dışında kalır. Tarihsel olarak başarısız bir derlemenin, yüklendiklerinde keyfi bir şekilde kilitlenen yasal görünen yürütülebilir dosyalara neden olabileceği bazı platformlar varken (ve hala olabilir) (ve bağlantı hatalarının genellikle bu tür davranışlara sahip olduğu sistemlerle çalışmak zorunda kaldım) Sözdizimi hatalarının sonuçlarının genellikle tahmin edilemez olduğunu söylemem. İyi bir sistemde, denenen bir derleme genellikle ya bir derleyicinin kod üretme konusundaki en iyi çabasıyla bir yürütülebilir dosya üretir ya da bir yürütülebilir dosya üretmez. Bazı sistemler, başarısız bir derlemeden sonra eski yürütülebilir dosyayı geride bırakır,

Kişisel tercihim, disk tabanlı sistemlerin çıktı dosyasını yeniden adlandırması, bu yürütülebilir dosyanın yanlışlıkla yeni kod çalıştırdığına inanmaktan kaynaklanabilecek kafa karışıklığını önlerken yararlı olacağı nadir durumlara izin vermesi ve gömülü programlama olacaktır. Sistemler, bir programcının her proje için, normal ad altında geçerli bir yürütülebilir dosya yoksa yüklenmesi gereken bir programı belirlemesine izin verir [ideal olarak, kullanılabilir bir programın eksikliğini güvenli bir şekilde gösteren bir şey]. Gömülü sistemler araç seti, genellikle böyle bir programın ne yapması gerektiğini bilmenin hiçbir yolu yoktur, ancak çoğu durumda, bir sistem için "gerçek" kod yazan biri, kolayca uyarlanabilen bazı donanım testi koduna erişebilir. amaç. Yeniden adlandırma davranışını gördüğümü bilmiyorum, ancak

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.