Tanımsız davranış ve sıra noktaları yeniden yüklendi


84

Bu konuyu aşağıdaki konunun bir devamı olarak düşünün:

Önceki taksit
Tanımlanmamış davranış ve sıra noktaları

Bu komik ve kıvrımlı ifadeyi tekrar gözden geçirelim (italik ifadeler yukarıdaki konudan alınmıştır * gülümseme *):

i += ++i;

Bunun tanımsız davranışı çağırdığını söylüyoruz. Bunu derken, biz örtülü farz farz türü arasında iyerleşik türlerinden biridir.

Ya tip ait ikullanıcı tanımlı türüdür? IndexBu yazıda daha sonra tanımlanan türünün olduğunu söyleyin (aşağıya bakın). Yine de tanımsız davranışa neden olur mu?

Cevabınız evet ise neden? Yazmakla eşdeğer, i.operator+=(i.operator++());hatta sözdizimsel olarak daha basit değil i.add(i.inc());mi? Yoksa onlar da tanımlanmamış davranışa mı başvururlar?

Hayır ise, neden olmasın? Sonuçta, nesne ardışık sıralama noktaları arasında iki kezi değiştirilir . Lütfen pratik kuralı hatırlayın: Bir ifade, bir nesnenin değerini ardışık "sıra noktaları arasında yalnızca bir kez değiştirebilir . Ve eğer bir ifade ise, o zaman tanımsız davranışı çağırmalıdır. Eğer öyleyse, eşdeğerleri ve ayrıca tanımsız davranışı çağırmalıdır. doğru değil gibi görünüyor! (anladığım kadarıyla)i += ++ii.operator+=(i.operator++());i.add(i.inc());

Yoksa başlamak için i += ++ibir ifade değil mi? Eğer öyleyse, o zaman nedir ve ifadenin tanımı nedir?

Bir ifadeyse ve aynı zamanda davranışı da iyi tanımlanmışsa, bir ifadeyle ilişkili sıra noktalarının sayısının bir şekilde ifadede yer alan işlenenlerin türüne bağlı olduğunu ima eder . Doğru muyum (kısmen de olsa)?


Bu arada, bu ifade nasıl olur?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

Cevabınızda bunu da göz önünde bulundurmalısınız (davranışını kesin olarak biliyorsanız). :-)


Dır-dir

++++++i;

C ++ 03'te iyi tanımlanmış mı? Sonuçta, bu bu,

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};

13
Harika yanıtlara ilham veren harika soruyu +1. Daha okunaklı olması için yeniden düzenlenmesi gereken hala korkunç bir kod olduğunu söylemem gerektiğini hissediyorum, ama muhtemelen bunu zaten biliyorsunuz :)
Philip Potter

4
@ Soru nedir: aynı olduğunu kim söyledi? ya da aynı olmadığını kim söyledi? Bunları nasıl uyguladığınıza bağlı değil mi? (Not: Türünün skullanıcı tanımlı bir tür olduğunu varsayıyorum !)
Nawaz

5
İki sıra noktası arasında iki kez değiştirilen bir skaler nesne görmüyorum ...
Johannes Schaub - litb

3
@Johannes: o zaman mesele skaler nesne. Bu ne? Neden daha önce hiç duymadım merak ediyorum. Belki, öğreticiler / C ++ - sss bundan bahsetmediği veya vurgulamadığı için mi? O nesneler farklı mı yerleşik tip?
Nawaz

3
@Phillip: Açıkçası, gerçek hayatta böyle bir kod yazmayacağım; aslında bunu hiçbir aklı başında programcı yazmayacak. Bu sorular genellikle tanımlanmamış davranış ve sıra noktaları işinin tamamını daha iyi anlayabilmemiz için tasarlanmıştır! :-)
Nawaz

Yanıtlar:


48

Kod gibi görünüyor

i.operator+=(i.operator ++());

Sıralama noktalarına göre mükemmel şekilde çalışır. C ++ ISO standardının 1.9.17 Bölümü, sıra noktaları ve işlev değerlendirmesi hakkında şunları söyler:

Bir işlevi çağırırken (işlev satır içi olsun ya da olmasın), tüm işlev bağımsız değişkenlerinin (varsa) değerlendirilmesinden sonra işlev gövdesindeki herhangi bir ifade veya deyimin yürütülmesinden önce gerçekleşen bir sıra noktası vardır. Ayrıca, döndürülen bir değerin kopyalanmasından sonra ve işlevin dışındaki herhangi bir ifadenin yürütülmesinden önce bir sıra noktası vardır.

Bu, örneğin, i.operator ++()parametrenin operator +=değerlendirilmesinden sonra bir sıra noktasına sahip olduğunu gösterir . Kısaca, aşırı yüklenmiş operatörler işlevler olduğundan, normal sıralama kuralları geçerlidir.

Bu arada harika bir soru! Beni zaten bildiğimi sandığım (ve bildiğimi düşündüğümü düşündüğüm) bir dilin tüm nüanslarını anlamaya nasıl zorladığınızı gerçekten seviyorum. :-)



11

Başkalarının dediği gibi, senin i += ++i örneğin işlevleri çağırdığınız için kullanıcı tanımlı türle çalışır ve işlevler sıra noktalarını içerir.

Öte yandan, a[++i] = ibunun asizin temel dizi türünüz, hatta kullanıcı tanımlı bir dizi olduğunu varsayarsak o kadar da şanslı sayılmaz . Burada sahip olduğunuz sorun, ifadenin hangi kısmının iönce değerlendirildiğini bilmememizdir . Bu ++i, değerlendirilebilir, operator[]nesneyi oraya geri getirmek için (veya ham sürüme) iaktarılabilir ve daha sonra değerin ona aktarılması (ki bu, i artırıldıktan sonra) olabilir. Öte yandan, belki de ikinci taraf önce değerlendirilir, daha sonraki ödev için saklanır ve ardından ++iparça değerlendirilir.


Öyleyse ... ifadelerin değerlendirme sırası belirtilmediğinden sonuç UB yerine belirtilmemiş mi?
Philip Potter

@Philip: belirtilmemiş, derleyicinin davranışı belirlemesini beklediğimiz anlamına gelir, ancak tanımlanmamışsa böyle bir zorunluluk yoktur. Derleyicilere optimizasyonlar için daha fazla alan sağlamak için burada tanımsız olduğunu düşünüyorum.
Matthieu M.

@Noah: Ayrıca bir yanıt da gönderdim. Lütfen kontrol edin ve düşüncelerinizi bana bildirin. :-)
Nawaz

1
@Philip: 5 / 4'teki kural nedeniyle sonuç UB'dir: "Bu paragrafın gereklilikleri, tam ifadenin alt ifadelerinin izin verilen her sıralaması için karşılanacaktır; aksi takdirde davranış tanımsızdır." İzin verilen tüm sıralamaların, değişiklik ile atamanın sağ tarafındaki ++iokunması arasında sıra noktaları ivarsa, sipariş belirtilmemiş olacaktır. İzin verilen sıralamalardan biri bu iki şeyi araya giren sıra noktası olmadan yaptığından, davranış tanımsızdır.
Steve Jessop

1
@Philip: Belirsiz davranışı tanımlanmamış davranış olarak tanımlamıyor. Yine, eğer belirsiz davranış aralığı tanımlanmamış olan bazı içeren, daha sonra genel davranış tanımlanmamıştır. Eğer tanımlanmamış davranış aralığı tüm olasılıklar tanımlanır, daha sonra genel davranış belirtilmemiştir. Ama ikinci noktada haklısın, kullanıcı tanımlı ave yerleşik bir düşünüyordum i.
Steve Jessop

8

Bence iyi tanımlanmış:

C ++ taslak standardından (n1905) §1.9 / 16:

"Döndürülen bir değerin kopyalanmasından sonra ve işlevin dışındaki herhangi bir ifadenin yürütülmesinden önce de bir sıra noktası vardır13). C ++ 'daki çeşitli bağlamlar, çeviri biriminde karşılık gelen işlev çağrısı sözdizimi görünmese bile, bir işlev çağrısının değerlendirilmesine neden olur. [ Örnek : yeni bir ifadenin değerlendirilmesi, bir veya daha fazla ayırma ve yapıcı işlevi çağırır; bkz. 5.3.4. Başka bir örnek için, işlev çağrısı sözdiziminin görünmediği bağlamlarda bir dönüştürme işlevinin (12.3.2) çağrılması ortaya çıkabilir. - end örnek ] İşlev girişindeki ve işlev çıkışındaki sıra noktaları (yukarıda açıklandığı gibi) , ifadenin sözdizimi ne olursa olsun değerlendirilen işlev çağrılarının özellikleridir.işlevi çağıran olabilir. "

Kalın yazdığım kısma dikkat edin. Bu, gerçekten de artış fonksiyonu çağrısından ( i.operator ++()) sonra, ancak bileşik atama çağrısından ( i.operator+=) önce bir sıra noktası olduğu anlamına gelir .


6

Peki. Önceki cevapları okuduktan sonra, kendi sorumu, özellikle de sadece Noah'ın cevap vermeye çalıştığı, ancak ona tamamen ikna olmadığım kısmı yeniden düşündüm .

a[++i] = i;

Dava 1:

If abir yerleşik tür dizisidir. O halde Noah'ın söylediği doğru. Yani,

a [++ i] = i, a'nın temel dizi türünüz olduğunu varsayarsak o kadar şanslı değil, hatta bir kullanıcı tanımlı . Buradaki sorun, i içeren ifadenin hangi kısmının önce değerlendirildiğini bilmememizdir.

Yani a[++i]=itanımsız davranışı çağırır veya sonuç belirsizdir. Her ne ise, iyi tanımlanmış değil!

Not: Yukarıdaki alıntıda, üstü çizili tabii ki benim.

Durum 2:

Eğer aaşırı yükler kullanıcı tanımlı tür bir amacı operator[], daha sonra yine iki durum vardır.

  1. Aşırı yüklenmiş operator[]işlevin dönüş türü yerleşik tür ise, yeniden a[++i]=itanımsız davranışı çağırır veya sonuç belirtilmez.
  2. Aşırı dönüş tipi Ama eğer operator[]fonksiyonunun daha sonra davranışı kullanıcı tanımlı türüdür a[++i] = i(bildiğim kadarıyla anladığım kadarıyla) iyi tanımlanmış olup, bu durumda bu yana a[++i]=iyazılı eşdeğerdir a.operator[](++i).operator=(i);, aynıdır a[++i].operator=(i);. Yani, çok iyi tanımlanmış gibi görünen operator=, döndürülen nesnede atama çağrılır a[++i], çünkü zaman a[++i]geri dönüşleri ++izaten değerlendirilmiştir ve sonra döndürülen nesne operator=, güncellenmiş değerini iargüman olarak ona ileten işlevi çağırır . Bu iki çağrı arasında bir sıra noktası olduğuna dikkat edin, . Ve sözdizimi bu iki çağrı arasında rekabet olmamasını sağlar veoperator[]önce çağrılırdı ve ard arda, ++iona aktarılan argüman da önce değerlendirilirdi.

Bunu, someInstance.Fun(++k).Gun(10).Sun(k).Tun();her ardışık işlev çağrısının kullanıcı tanımlı türde bir nesne döndürdüğü gibi düşünün . Bana göre bu durum daha çok şuna benziyor: eat(++k);drink(10);sleep(k)çünkü her iki durumda da, her işlev çağrısından sonra sıra noktası var.

Yanılıyorsam lütfen beni düzeltin. :-)


1
@Nawaz k++ve kedilmektedir olmayan dizi nokta ile ayrılmış. Her ikisi de değerlendirilmeden önce değerlendirilebilir Sunveya Fundeğerlendirilebilir. Dil sadece bunun Fundaha önce değerlendirilmesini gerektirir Sun, bu Funargümanların argümanlarından önce değerlendirilmesini gerektirmez Sun. Referans sağlayamadan aynı şeyi tekrar açıklıyorum, bu yüzden buradan ilerlemeyeceğiz.
Philip Potter

1
@Nawaz: Çünkü onları ayıran bir dizi noktasını tanımlayan hiçbir şey yok. SunYürütmeden önce ve sonra sıra noktaları vardır , ancak Funargümanı ++kbundan önce veya sonra değerlendirilebilir. FunYürütmeden önce ve sonra sıra noktaları vardır , ancak Sunargümanı kbundan önce veya sonra değerlendirilebilir. Bu nedenle, tek bir olası durumda, her iki olduğunu kve ++kya önce değerlendirilir Sunveya Fundeğerlendirilir ve her ikisi de bu nedenle fonksiyonu çağrı dizisi noktalarından önce, ve bu yüzden birbirinden ayıran bir dizi nokta yoktur kve ++k.
Philip Potter

1
@Philip: Tekrar ediyorum: Bu durum nasıl farklı eat(i++);drink(10);sleep(i);? ... şimdi bile, bundan i++önce veya sonra değerlendirilebilir diyebilirsiniz ?
Nawaz

1
@Nawaz: Kendimi nasıl daha net hale getirebilirim? Fun / Sun örneğinde, ve arasında sıra noktası yoktur . Yemek / içecek örneğinde var olan arasındaki dizi noktası ve . k++kii++
Philip Potter

3
@Philip: Bu hiç mantıklı değil. Fun () ve Sun () arasında bir sıra noktası vardır, ancak argümanları arasında sıra noktaları yoktur. Sıra noktalarının arasında eat()ve sleep()var olduğunu söylemek gibidir , ancak aralarında argümanlar bir tane bile yoktur. Sıra noktalarıyla ayrılmış iki işlev çağrısının argümanları nasıl aynı sıra noktalarına ait olabilir?
Nawaz
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.