Java'da tanımlanmamış davranış


14

Ben C ++ bazı tanımsız davranış tartışıyor SO bu soruyu okuyordu ve merak ettim: Java da tanımsız davranış var mı?

Bu durumda, Java'da tanımlanmamış davranışın bazı yaygın nedenleri nelerdir?

Değilse, Java'nın hangi özellikleri bu tür davranışlardan kurtulur ve bu özelliklerle neden C ve C ++ 'nın en son sürümleri uygulanmamıştır?


4
Java çok katı bir şekilde tanımlanmıştır. Java Dil Spesifikasyonunu kontrol edin.


4
@ user1249, "tanımsız davranış" da oldukça katı bir şekilde tanımlanmıştır.
Pacerier


Bir "Sözleşmeyi" ihlal ettiğinizde Java ne diyor? Böyle .hashCode ile uyumsuz olmak için .equals aşırı yüklediğinizde olur? docs.oracle.com/javase/7/docs/api/java/lang/… Bu konuşma dili tanımsız, ancak teknik olarak C ++ ile aynı şekilde değil mi?
Mooing Ördek

Yanıtlar:


18

Java'da, yanlış senkronize edilmiş programın davranışını tanımsız olarak düşünebilirsiniz.

Java 7 JLS, 17.4.8'de bir kez "tanımsız" kelimesini kullanır . İnfazlar ve Nedensellik Gereksinimleri :

Biz kullanmak f|detki alanını kısıtlayarak verilen işlevi belirtmek için fiçin d. Hepsi için xin d, f|d(x) = f(x)herkes için, ve xdeğil d, f|d(x)bir tanımsız ...

Java API belgeleri, sonuçların tanımsız olduğu bazı durumları belirtir (örneğin, (kullanımdan kaldırıldı) yapıcı Tarihinde (int yıl, int ay, int gün) :

Belirli bir argüman sınırların dışındaysa sonuç tanımsızdır ...

ExecutorService.invokeAll (Koleksiyon) durumu için Javadocs :

Bu işlem devam ederken verilen koleksiyon değiştirilirse , bu yöntemin sonuçları tanımlanmamıştır ...

API biçiminin "en iyi çaba" terimini kullandığı ConcurrentModificationException içinde daha az biçimsel "tanımsız" davranış türü bulunabilir :

Başarısız davranışların, senkronize olmayan eşzamanlı modifikasyon varlığında herhangi bir kesin garanti vermenin imkansız olduğu için garanti edilemeyeceğini unutmayın. Fail-hızlı operasyonlar atmak ConcurrentModificationExceptionbir üzerinde en çok çaba bazında. Bu nedenle, doğruluğu nedeniyle bu istisnaya bağlı bir program yazmak yanlış olur ...


apandis

Soru yorumlarından biri, Eric Lippert'in konu konularına yararlı bir giriş sağlayan bir makalesine atıfta bulunur: Uygulama tanımlı davranış .

Bu makaleyi dil-agnostik muhakeme için öneriyorum, ancak yazarın Java'yı değil C # 'ı hedeflediğini akılda tutmaya değer.

Geleneksel olarak, eğer bu deyimin kullanımının herhangi bir etkisi olabiliyorsa , bir programlama dili deyiminin tanımlanmamış davranışa sahip olduğunu söyleriz ; beklediğiniz şekilde çalışabilir veya sabit diskinizi silebilir veya makinenizi kilitleyebilir. Ayrıca, derleyici yazarı, tanımlanmamış davranış konusunda sizi uyarmakla yükümlü değildir. (Ve aslında, "tanımlanmamış davranış" deyimlerini kullanan programların, dil belirtimi tarafından derleyiciyi çökmesine izin verdiği bazı diller vardır!) ...

Buna karşılık, uygulama tanımlı davranışa sahip bir deyim , derleyici yazarının özelliğin nasıl uygulanacağı konusunda çeşitli seçeneklere sahip olduğu ve bir tanesini seçmesi gereken davranıştır. Adından da anlaşılacağı gibi, uygulama tanımlı davranış en azından tanımlanır. Örneğin, C #, bir tamsayı bölümü taştığında bir uygulamanın bir istisna atmasına veya bir değer üretmesine izin verir, ancak uygulamanın bir tane seçmesi gerekir. Sabit diskinizi silemez ...

Bir dil tasarım komitesinin belirli dil deyimlerini tanımsız veya uygulama tanımlı davranışlar olarak bırakmasına neden olan faktörlerden bazıları nelerdir?

İlk önemli faktör: pazarda belirli bir programın davranışına katılmayan iki dil uygulaması var mı? ...

Bir sonraki önemli faktör: özellik doğal olarak bazıları diğerlerinden açıkça daha iyi olan birçok farklı uygulama olanağı sunuyor mu? ...

Üçüncü faktör: özellik o kadar karmaşık mı? ...

Dördüncü faktör: özellik derleyici üzerinde analiz etmek için yüksek bir yük oluşturuyor mu? ...

Beşinci faktör şudur: özellik çalışma zamanı ortamına yüksek bir yük getirir mi? ...

Altıncı faktör şudur: Tanımlanan davranışın yapılması bazı büyük optimizasyonları engelliyor mu? ...

Bunlar akla gelen birkaç faktördür; Elbette, dil tasarım komitelerinin "uygulama tanımlı" veya "tanımsız" bir özellik yapmadan önce tartıştığı pek çok başka faktör vardır.

Yukarıda sadece çok kısa bir kapsam vardır; makalenin tamamı bu alıntıda belirtilen noktalar için açıklamalar ve örnekler içermektedir; öyle çok okunmaya değer. Örneğin, "altıncı faktör" için verilen ayrıntılar, Java Bellek Modeli'ndeki ( JSR 133 ) birçok ifade için motivasyon hakkında bir fikir verebilir ve bazı optimizasyonlara neden izin verildiğini anlamaya yardımcı olurken, diğerleri yasaklanırken tanımlanmamış davranışa neden olur. önceden gerçekleşme ve nedensellik gereksinimleri gibi sınırlamalar .

Makale malzemelerinin hiçbiri benim için özellikle yeni değil, ancak böyle zarif, özlü ve anlaşılır bir şekilde sunulduğunu görürsem lanetleneceğim. İnanılmaz.


JMM! = Temel donanımın ve eşzamanlılık ile ilgili bir yürütme programının sonucunun WinIntel ile Solaris
Martijn Verburg

2
@MartijnVerburg bu oldukça iyi bir nokta. Bunu "tanımsız" olarak etiketlemekten çekinmemin tek nedeni, bellek modelinin doğru şekilde senkronize edilmiş programın yürütülmesinde önceki gibi gerçekleşme ve nedensellik gibi kısıtlamalar oluşturmasıdır
gnat

Doğru, teknik özellik JMM altında nasıl davranması gerektiğini tanımlar, ancak Intel ve diğerleri her zaman aynı fikirde değildir ;-)
Martijn Verburg

@MartijnVerburg JMM'nin asıl amacı, işlemci üreticilerinin "katılmıyorum" sızıntılarının aşırı optimize edilmesini önlemektir . 5.0'dan önce Java'nın DEC Alpha ile bu tür bir baş ağrısına sahip olduğunu anladığım kadarıyla, başlık altında yapılan spekülatif yazılar "ince havadan dışarı" gibi bir programa sızabildiğinde - dolayısıyla nedensellik gereksinimi JSR 133'e (JMM) girdi
gnat

9
@MartinVerburg - JVM'nin desteklenen herhangi bir donanım platformundaki JLS / JMM spesifikasyonlarına göre davrandığından emin olmak bir JVM uygulayıcının işi. Farklı donanımlar farklı davranıyorsa, JVM uygulayıcının işi onunla başa çıkmak ve çalışmasını sağlamaktır.
Stephen C

10

Başımın üstünden, Java'da tanımlanmamış bir davranış olduğunu düşünmüyorum, en azından C ++ ile aynı anlamda değil.

Bunun nedeni Java'nın arkasında C ++ 'dan farklı bir felsefe olmasıdır. Java'nın temel tasarım hedefi, programların platformlar arasında değişmeden çalışmasına izin vermekti, bu nedenle şartname her şeyi çok açık bir şekilde tanımlar.

Buna karşılık, C ve C ++ 'ın temel tasarım amacı verimliliktir: İhtiyacınız olmasa bile performansa mal olan hiçbir özellik (platform bağımsızlığı dahil) olmamalıdır. Bu amaçla, şartname kasıtlı olarak bazı davranışları tanımlamamaktadır, çünkü bunların tanımlanması bazı platformlarda fazladan çalışmaya neden olur ve böylece özellikle bir platform için program yazan ve tüm kendine özgü özelliklerinin farkında olan kişiler için bile performansı düşürür.

Java'nın tam olarak bu nedenle sınırlı bir şekilde tanımlanamayan bir davranış biçimi sunmaya zorlandığı bir örnek bile vardır: yüzer nokta hesaplamalarının spesifikasyonun daha önce talep ettiği gibi IEEE 754 standardını tam olarak sapmaktan kaçınmasına izin vermek için Java 1.2'de katıfp anahtar sözcüğü eklendi. çünkü bunu yapmak ekstra çalışma gerektirdi ve bazı ortak CPU'larda tüm kayan nokta hesaplamalarını yavaşlattı ve bazı durumlarda daha kötü sonuçlar üretti.


2
Bence Java'nın diğer ana hedefine dikkat etmek önemlidir: güvenlik ve izolasyon. Bu da 'tanımsız' davranış eksikliği (C ++ gibi) için bir neden olduğunu düşünüyorum.
K.Steff

3
@ K.Steff: Hyper-modern C / C ++ uzaktan güvenlikle ilgili her şey için tamamen uygun değildir. Verilen int x=-1; foo(); x<<=1;hiper-modern felsefe yeniden yazmayı tercih eder, fooböylece çıkmayan yollara ulaşılamaz. Bu, eğer bir derleyici fooise if (should_launch_missiles) { launch_missiles(); exit(1); }(ve bazı insanlara göre) bunu basitleştirebilir launch_missiles(); exit(1);. Geleneksel UB rastgele kod uygulamasıydı, ama eskiden zaman ve nedensellik yasalarına bağlıydı. Yeni geliştirilmiş UB hiçbirine bağlı değildir.
supercat

3

Java, tam olarak önceki dillerin dersleri nedeniyle, tanımlanmamış davranışı yok etmek için oldukça uğraşmaktadır. Örneğin, sınıf düzeyi değişkenleri otomatik olarak başlatılır; yerel değişkenler performans nedenleriyle otomatik olarak başlatılmaz, ancak herhangi birinin bunu algılayabilecek bir program yazmasını önlemek için karmaşık veri akışı analizi vardır. Başvurular işaretçiler değildir, bu nedenle geçersiz başvurular olamaz ve kayıttan kaldırma nullbelirli bir özel duruma neden olur.

Tabii ki tam olarak belirtilmeyen bazı davranışlar var ve eğer varsayalım güvenilmez programlar yazabilirsiniz. Örneğin, normal (sıralanmamış) üzerinde yineleme yaparsanız Set, dil her öğeyi tam olarak bir kez göreceğinizi garanti eder, ancak hangi sırayla göreceğinizi garanti etmez. Sıralı çalıştırmalarda sipariş aynı olabilir veya değişebilir; veya başka hiçbir ayırma gerçekleşmediği sürece veya JDK'nızı vb. güncellemediğiniz sürece aynı kalabilir. Tüm bu efektlerden kurtulmak neredeyse imkansızdır ; örneğin, tüm Koleksiyonlar işlemlerini açıkça sipariş etmeniz veya rastgele ayarlamanız gerekir ve bu da küçük ek tanımsızlığa değmez.


Kaynaklar başka bir isim altında işaretçiler
curiousguy

@curiousguy - "referanslar" genellikle sayısal değerlerinin aritmetik manipülasyonunun kullanılmasına izin vermediği varsayılır ve bu genellikle "işaretçiler" için izin verilir. Bu nedenle birincisi, ikincisinden daha güvenli bir yapıdır; geçerli bir başvuru varken bir nesnenin depolamasının yeniden kullanılmasına izin vermeyen bir bellek yönetim sistemiyle birleştirildiğinde, başvurular bellek kullanım hatalarını önler. İşaretçiler, uygun bellek yönetimi kullanıldığında bile bunu yapamaz.
Jules

@Jules O zaman bu bir terminoloji meselesidir: bir şeyi işaretçi veya referans olarak adlandırabilir ve "güvenli" dillerde "referans" ve işaretçi aritmetik ve manuel bellek yönetimi kullanımına izin veren dillerde "işaretçi" kullanmaya karar verebilirsiniz. (AFAIK "işaretçi aritmetiği" yalnızca C / C ++ ile yapılır.)
curiousguy

2

"Tanımsız Davranış" ı ve kökenini anlamalısınız.

Tanımsız Davranış , standartlar tarafından tanımlanmayan bir davranış anlamına gelir. C / C ++ çok farklı derleyici uygulamaları ve ek özelliklere sahiptir. Bu ek özellikler kodu derleyiciye bağladı. Bunun nedeni, merkezi bir dil gelişimi olmamasıydı. Böylece, bazı derleyicilerin gelişmiş özelliklerinden bazıları "tanımlanmamış davranışlar" haline geldi.

Java'da dil belirtimi Sun-Oracle tarafından kontrol edilir ve spesifikasyon yapmaya çalışan kimse ve dolayısıyla tanımlanmamış davranış yoktur.

Düzenlendi Soruyu Özel Olarak Yanıtlama

  1. Derleyiciler derlenmeden önce standartlar oluşturulduğundan Java tanımsız davranışlardan uzak
  2. Modern C / C ++ derleyicileri uygulamaları daha fazla / daha az standartlaştırmıştır, ancak standardizasyondan önce uygulanan özellikler hala "tanımsız davranış" olarak etiketlenmeye devam etmektedir, çünkü ISO bu yönlerden uzak durmaktadır.

2
Java'da UB olmadığından emin olabilirsiniz, ancak bir varlık her şeyi kontrol ettiğinde bile UB'ye sahip olmanın nedenleri olabilir, bu nedenle verdiğiniz neden sonuca yol açmaz.
AProgrammer

2
Ayrıca, hem C hem de C ++ ISO tarafından standartlaştırılmıştır. Birden fazla derleyici olsa da, aynı anda sadece bir standart vardır.
MSalters

1
@SarvexJatasra, bunun tek UB kaynağı olduğu konusunda hemfikir değilim. Örneğin, bir UB sökümlü işaretçi ve şimdi spesifikasyonunuzu başlatsanız bile, GC olmayan herhangi bir dilde bir UB bırakmak için iyi nedenler var. Ve bu nedenlerin mevcut uygulama veya mevcut derleyicilerle hiçbir ilgisi yoktur.
AProgrammer

2
@SarvexJatasra, imzalı taşma UB, çünkü standart açıkça söylüyor (UB'nin tanımı ile verilen örnek bile). Standart geçersiz bir işaretçiyi silmek de aynı nedenden ötürü bir UB'dir.
AProgrammer

2
@ bames53: Belirtilen avantajların hiçbiri, hipermodern derleyicilerin UB ile aldıkları enlem seviyesini gerektirmez. "Doğal olarak" rasgele kod yürütmesini indükleyebilen sınırların dışındaki bellek erişimleri ve yığın taşmaları hariç, çoğu UB-ish işleminin belirsiz olduğunu söylemek daha geniş bir enlem gerektirecek herhangi bir yararlı optimizasyon düşünemiyorum değerler ("ekstra bitleri varmış gibi davranabilirler") ve bunun ötesinde, ancak bir uygulamanın dokümanları böyle bir empoze etme hakkını açıkça saklı tutarsa ​​bunun sonuçları olabilir; dokümanlar "Kısıtsız davranış" verebilir ...
supercat

1

Java, C / C ++ 'da bulunan tüm tanımlanmamış davranışları ortadan kaldırır. (Örneğin: İmzalı tamsayı taşması, sıfıra bölme, başlatılmamış değişkenler, boş işaretçi dereference, bit genişliğinden daha fazla kaydırma, çift serbest, hatta "kaynak kodun sonunda yeni satır yok".) Ancak Java'nın birkaç belirsiz tanımlanmamış davranışı var programcılar tarafından nadiren karşılaşılır.

  • Java'nın C veya C ++ kodunu çağırmasının bir yolu olan Java Yerel Arabirimi (JNI). İşlev imzasını yanlış almak, JVM hizmetlerine geçersiz çağrı yapmak, hafızayı bozmak, yanlış şeyler ayırmak / boşaltmak ve daha fazlası gibi JNI'de sıkıştırmanın birçok yolu vardır. Daha önce bu hataları yaptım ve JNI kodunu yürüten herhangi bir iş parçacığı bir hata verdiğinde genellikle tüm JVM çöküyor.

  • Thread.stop()kullanımdan kaldırılmıştır. Alıntı:

    Neden Thread.stopreddedildi?

    Çünkü doğal olarak güvensizdir. Bir iş parçacığının durdurulması, iş parçacığının kilitlediği tüm monitörlerin kilidini açmasına neden olur. (Kural dışı ThreadDeathdurum yığını yukarı doğru ilerletirken monitörlerin kilidi açılır .) Bu monitörler tarafından önceden korunan nesnelerden herhangi biri tutarsız bir durumdaysa, diğer iş parçacıkları artık bu nesneleri tutarsız bir durumda görüntüleyebilir. Bu tür nesnelerin hasar gördüğü söylenir. İş parçacıkları hasarlı nesneler üzerinde çalıştığında, rasgele davranışlar ortaya çıkabilir. Bu davranış ince ve algılanması zor olabilir veya telaffuz edilebilir. Kontrol edilmeyen diğer istisnaların aksine, ThreadDeathiplikleri sessizce öldürür; bu nedenle, kullanıcının programının bozuk olabileceği konusunda hiçbir uyarısı yoktur. Yolsuzluk, gerçek hasar meydana geldikten sonra, ileride saatler veya günler bile olsa, herhangi bir zamanda kendini gösterebilir.

    https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

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.