"C ++ Programlama Dili" 4. baskı bölüm 36.3.6'daki bu kod iyi tanımlanmış davranışa sahip mi?


94

Bjarne Stroustrup'un The C ++ Programming Language 4. basım bölümünde 36.3.6 STL-like Operations bölümünde zincirleme örneği olarak aşağıdaki kod kullanılmıştır :

void f2()
{
    std::string s = "but I have heard it works even if you don't believe in it" ;
    s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
        .replace( s.find( " don't" ), 6, "" );

    assert( s == "I have heard it works only if you believe in it" ) ;
}

Assert başarısız olur gcc( canlı görün ) ve Visual Studio( canlı görün ), ancak Clang'ı kullanırken başarısız olmaz ( canlı görün ).

Neden farklı sonuçlar alıyorum? Bu derleyicilerden herhangi biri zincirleme ifadesini yanlış bir şekilde değerlendiriyor mu yoksa bu kod bir tür belirtilmemiş veya tanımlanmamış davranış mı sergiliyor ?


Daha iyi:s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );
Ben Voigt

20
hata bir yana, böyle çirkin bir kodun kitapta olmaması gerektiğini düşünen tek kişi ben miyim?
Karoly Horvath

5
O @KarolyHorvath Not cout << a << b << coperator<<(operator<<(operator<<(cout, a), b), c)sadece marjinal az çirkin.
Oktalist

1
@Oktalist: :) En azından orada bir niyetim var. bağımsız değişkene bağlı ad aramayı ve operatör sözdizimini aynı zamanda kısa bir biçimde öğretir ... ve gerçekten böyle bir kod yazmanız gerektiği izlenimini vermez.
Karoly Horvath

Yanıtlar:


104

Kod, alt ifadelerin belirlenmemiş değerlendirme sırasına bağlı olarak tanımlanmamış davranış sergiler, ancak tanımlanmamış davranışa neden olmaz, çünkü tüm yan etkiler bu durumda yan etkiler arasında bir sıralama ilişkisi getiren işlevler içinde yapılır .

Bu örnek, sorudaki kodla ilgili olarak aşağıdakileri söyleyen N4228: Deyimsel C ++ İçin İfade Değerlendirme Sırasını İyileştirme Teklifinde belirtilmiştir :

[...] Bu kod (Dil, 4 Programlama C ++ dünya çapında C ++ uzmanlar tarafından incelendi ve yayınlanmıştır inci baskı.) Oysa değerlendirmenin belirtilmemiş düzene savunmasızlığından bir araç [tarafından ancak son zamanlarda keşfedilmiştir .. .]

Detaylar

İşlevlere yönelik argümanların belirlenmemiş bir değerlendirme sırasına sahip olduğu birçok kişi için açık olabilir, ancak muhtemelen bu davranışın zincirleme işlev çağrılarıyla nasıl etkileştiği açık değildir. Bu vakayı ilk analiz ettiğimde bana açık değildi ve görünüşe göre tüm uzman eleştirmenler için de değildi .

İlk bakışta, her replacebirinin soldan sağa değerlendirilmesi gerektiğinden, karşılık gelen fonksiyon argüman gruplarının soldan sağa gruplar halinde değerlendirilmesi gerektiği görünebilir .

Bu yanlıştır, işlev bağımsız değişkenleri belirlenmemiş bir değerlendirme sırasına sahiptir, ancak zincirleme işlev çağrıları her işlev çağrısı için soldan sağa bir değerlendirme sırası getirse de, her işlev çağrısının argümanları yalnızca parçası oldukları üye işlev çağrısına göre önceden sıralanır nın-nin. Bu özellikle aşağıdaki çağrıları etkiler:

s.find( "even" )

ve:

s.find( " don't" )

şunlara göre belirsiz şekilde sıralanır:

s.replace(0, 4, "" )

iki findçağrı , sonucunu değiştirecek replacebir yan etkiye sahip solduğu için find, uzunluğunu değiştirdiği için önemlidir s. Bu nedenle replace, iki findçağrıya göre ne zaman değerlendirildiğine bağlı olarak sonuç farklı olacaktır.

Zincirleme ifadesine bakarsak ve bazı alt ifadelerin değerlendirme sırasını incelersek:

s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^       ^  ^  ^    ^        ^                 ^  ^
A B       |  |  |    C        |                 |  |
          1  2  3             4                 5  6

ve:

.replace( s.find( " don't" ), 6, "" );
 ^        ^                   ^  ^
 D        |                   |  |
          7                   8  9

Unutmayın, bunu görmezden geliyoruz 4ve 7daha fazla alt ifadeye bölünebiliriz. Yani:

  • Aöncesinde Bdizilenir, daha önce CdizilenirD
  • 1için 9belirsiz aşağıda belirtilenler dışında bazı diğer alt ifadeleri bakımından sıralanamadı
    • 1daha 3önce sıralanacakB
    • 4daha 6önce sıralanacakC
    • 7daha 9önce sıralanacakD

Bu sorunun anahtarı şudur:

  • 4için 9belirsiz göre dizilirB

İçin 4ve buna 7göre olası değerlendirme sırası, değerlendirme Barasındaki clangve gccdeğerlendirme sırasındaki sonuçlardaki farkı açıklar f2(). Benim testlerde clangdeğerlendirir Bdeğerlendirmeden önce 4ve 7sırasında gccdeğerlendirir onu sonra. Her durumda neler olduğunu göstermek için aşağıdaki test programını kullanabiliriz:

#include <iostream>
#include <string>

std::string::size_type my_find( std::string s, const char *cs )
{
    std::string::size_type pos = s.find( cs ) ;
    std::cout << "position " << cs << " found in complete expression: "
        << pos << std::endl ;

    return pos ;
}

int main()
{
   std::string s = "but I have heard it works even if you don't believe in it" ;
   std::string copy_s = s ;

   std::cout << "position of even before s.replace(0, 4, \"\" ): " 
         << s.find( "even" ) << std::endl ;
   std::cout << "position of  don't before s.replace(0, 4, \"\" ): " 
         << s.find( " don't" ) << std::endl << std::endl;

   copy_s.replace(0, 4, "" ) ;

   std::cout << "position of even after s.replace(0, 4, \"\" ): " 
         << copy_s.find( "even" ) << std::endl ;
   std::cout << "position of  don't after s.replace(0, 4, \"\" ): "
         << copy_s.find( " don't" ) << std::endl << std::endl;

   s.replace(0, 4, "" ).replace( my_find( s, "even" ) , 4, "only" )
        .replace( my_find( s, " don't" ), 6, "" );

   std::cout << "Result: " << s << std::endl ;
}

Sonuç gcc( canlı görün )

position of even before s.replace(0, 4, "" ): 26
position of  don't before s.replace(0, 4, "" ): 37

position of even after s.replace(0, 4, "" ): 22
position of  don't after s.replace(0, 4, "" ): 33

position  don't found in complete expression: 37
position even found in complete expression: 26

Result: I have heard it works evenonlyyou donieve in it

Sonuç clang( canlı görün ):

position of even before s.replace(0, 4, "" ): 26
position of  don't before s.replace(0, 4, "" ): 37

position of even after s.replace(0, 4, "" ): 22
position of  don't after s.replace(0, 4, "" ): 33

position even found in complete expression: 22
position don't found in complete expression: 33

Result: I have heard it works only if you believe in it

Sonuç Visual Studio( canlı görün ):

position of even before s.replace(0, 4, "" ): 26
position of  don't before s.replace(0, 4, "" ): 37

position of even after s.replace(0, 4, "" ): 22
position of  don't after s.replace(0, 4, "" ): 33

position  don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it

Standarttan detaylar

Alt ifadelerin değerlendirmelerinin sıralanmamış olduğunu belirtmedikçe, bunun taslak C ++ 11 standart bölüm 1.9 Program yürütme bölümünden olduğunu biliyoruz :

Belirtilenler dışında, tek tek operatörlerin işlenenlerinin ve tek tek ifadelerin alt ifadelerinin değerlendirmeleri sıralı değildir. [...]

ve bir fonksiyon çağrısının, fonksiyonun postfix ifadesi ve fonksiyon gövdesine ilişkin argümanlar ile ilişkisinden önce sıralı bir dizi getirdiğini biliyoruz, bölüm 1.9:

[...] Bir işlev çağrılırken (işlev satır içi olsun ya da olmasın), herhangi bir bağımsız değişken ifadesiyle veya çağrılan işlevi belirten sonek ifadesiyle ilişkili her değer hesaplaması ve yan etki, her ifadenin veya ifadenin çalıştırılmasından önce sıralanır. çağrılan işlevin gövdesinde. [...]

Ayrıca, sınıf üyesi erişiminin ve dolayısıyla zincirlemenin, 5.2.5 Sınıf üyesi erişimi bölümünden soldan sağa doğru değerlendirileceğini biliyoruz :

[...] Nokta veya oktan önceki sonek ifadesi değerlendirilir; 64 bu değerlendirmenin sonucu, id ifadesi ile birlikte tüm sonek ifadesinin sonucunu belirler.

İd ifadesinin statik olmayan bir üye işlev olarak sona ermesi durumunda , bu ayrı bir alt ifade olduğundan, içindeki ifade listesinin değerlendirme sırasını belirtmez (). 5.2 Postfix ifadelerinden ilgili dilbilgisi :

postfix-expression:
    postfix-expression ( expression-listopt)       // function call
    postfix-expression . templateopt id-expression // Class member access, ends
                                                   // up as a postfix-expression

C ++ 17 değişiklikleri

P0145r3: Deyimsel C ++ için İfade Değerlendirme Sırasını İyileştirme önerisi birkaç değişiklik yaptı. Sonek ifadeleri ve bunların ifade listesi için değerlendirme kurallarının sırasını güçlendirerek koda iyi tanımlanmış davranış veren değişiklikleri içerir .

[ifade. çağrı] s5 diyor ki:

Sonek ifadesi, ifade listesindeki her ifadeden ve herhangi bir varsayılan bağımsız değişkenden önce sıralanır . Her ilişkili değer hesaplaması ve yan etki dahil olmak üzere bir parametrenin başlatılması, diğer herhangi bir parametreninkine göre belirsiz bir şekilde sıralanır. [Not: Bağımsız değişken değerlendirmelerinin tüm yan etkileri, işleve girilmeden önce sıralanır (bkz. 4.6). —End note] [Örnek:

void f() {
std::string s = "but I have heard it works even if you don’t believe in it";
s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(" don’t"), 6, "");
assert(s == "I have heard it works only if you believe in it"); // OK
}

—Son örnek]


7
"Pek çok uzmanın" sorunu gözden kaçırdığını görmek beni biraz şaşırttı , bir işlev çağrısının sonek ifadesinin değerlendirilmesinin, argümanları değerlendirmeden önce sıralı olmadığı iyi biliniyor (C ve C ++ 'nın tüm sürümlerinde).
MM

@ShafikYaghmour İşlev çağrıları, not ettiğiniz önceden sıralı ilişkiler haricinde, birbirlerine ve diğer her şeye göre belirsiz bir şekilde sıralanır. Bununla birlikte, değerlendirme için 1, 2, 3, 5, 6, 8, 9, "even", "don't"ve çeşitli örneklerini sbirbirlerine göre dizilenmemiş bulunmaktadır.
TC

4
@TC hayır değil (bu "hata" nasıl ortaya çıkıyor). Örneğin foo().func( bar() ), aramadan foo()önce veya sonra arayabilir bar(). Sonek ifade olan foo().func. Bağımsız değişkenler ve sonek ifadesi, gövdesinden önce func()sıralanır, ancak birbirine göre sıralanmaz.
MM

@MattMcNabb Ah, doğru, yanlış okudum. Çağrı yerine sonek ifadesinin kendisinden bahsediyorsunuz . Evet, bu doğru, sıralanmamışlardır (tabii başka bir kural uygulanmadıkça).
TC

6
Ayrıca, bir B.Stroustrup kitabında kodun göründüğünü varsayma eğiliminde olma faktörü de vardır, aksi takdirde birisi kesinlikle zaten fark ederdi! (ilgili; SO kullanıcıları K & R'de hala yeni hatalar buluyor)
MM

4

Bu, C ++ 17 ile ilgili konu hakkında bilgi eklemeyi amaçlamaktadır. Yukarıdaki koda atıfta bulunulan konuyu ele almak için teklif ( Deyimsel C ++ Revizyon 2 için İfade Değerlendirme Sırasını İyileştirme ) C++17örnek olarak yapıldı.

Önerildiği gibi, tekliften ilgili bilgileri ekledim ve alıntı yapmak için (benimkini vurgular):

Şu anda standartta belirtildiği şekliyle ifade değerlendirme sıralaması, tavsiyeleri, popüler programlama deyimlerini veya standart kütüphane olanaklarının göreceli güvenliğini zayıflatmaktadır. Tuzaklar sadece acemiler veya dikkatsiz programcılar için değil. Kuralları bilsek bile, ayrım gözetmeksizin hepimizi etkilerler.

Aşağıdaki program parçasını düşünün:

void f()
{
  std::string s = "but I have heard it works even if you don't believe in it"
  s.replace(0, 4, "").replace(s.find("even"), 4, "only")
      .replace(s.find(" don't"), 6, "");
  assert(s == "I have heard it works only if you believe in it");
}

İddia, programcının amaçladığı sonucu doğrulaması beklenir. Ortak bir standart uygulama olan üye işlev çağrılarının "zincirlenmesini" kullanır. Bu kod, dünya çapındaki C ++ uzmanları tarafından gözden geçirilmiş ve yayınlanmıştır (The C ++ Programming Language, 4. baskı.) Yine de, belirlenmemiş değerlendirme sırasına karşı savunmasızlığı ancak yakın zamanda bir araçla keşfedilmiştir.

Makale C++17, etkilenen Cve otuz yılı aşkın süredir var olan ifade değerlendirme sırasına ilişkin ön kuralın değiştirilmesini önerdi . Dilin çağdaş deyimleri garanti etmesi veya yukarıdaki kod örneğinde olduğu gibi "tuzakları ve belirsiz, bulunması zor hataları" riske atması gerektiğini öne sürdü .

Teklif , her ifadenin iyi tanımlanmış bir değerlendirme sırasına sahipC++17 olmasını gerektirmektir :

  • Sonek ifadeleri soldan sağa doğru değerlendirilir. Bu, işlev çağrılarını ve üye seçim ifadelerini içerir.
  • Atama ifadeleri sağdan sola doğru değerlendirilir. Bu, bileşik atamaları içerir.
  • Operatörleri kaydırmak için işlenenler soldan sağa doğru değerlendirilir.
  • Aşırı yüklenmiş bir işleci içeren bir ifadenin değerlendirme sırası, işlev çağrılarının kuralları ile değil, karşılık gelen yerleşik işleçle ilişkili sırayla belirlenir.

Yukarıdaki kod, GCC 7.1.1ve kullanarak başarıyla derler Clang 4.0.0.

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.