Neden bir değer döndürmeden geçersiz bir işlevin sonundan akmak derleyici hatası üretmez?


158

Yıllar önce fark ettiğimden beri, bunun varsayılan olarak bir hata üretmediğini (en azından GCC'de), neden hep merak ettim?

Bir uyarı oluşturmak için derleyici bayrakları verebileceğinizi anlıyorum, ancak her zaman bir hata olmamalı mı? Geçersiz olmayan bir fonksiyonun bir değer döndürmemesi neden mantıklı geliyor?

Yorumlarda talep edilen bir örnek:

#include <stdio.h>
int stringSize()
{
}

int main()
{
    char cstring[5];
    printf( "the last char is: %c\n", cstring[stringSize()-1] ); 
    return 0;
}

... derler.


9
Alternatif olarak, hatalar gibi önemsiz olan tüm uyarıları ele alıyorum ve yapabileceğim tüm uyarıları etkinleştiriyorum (gerekirse yerel devre dışı bırakma ile ... ama sonra neden kodda açıktır).
Matthieu M.

8
-Werror=return-typesadece bu uyarıyı bir hata olarak ele alacaktır. Uyarıyı göz ardı ettim ve geçersiz bir thisişaretçiyi takip eden birkaç dakikalık hayal kırıklığı beni buraya ve bu sonuca götürdü.
jozxyqk

Bu, bir std::optionalişlevin sonundan geri dönmeden akmanın "gerçek" bir isteğe bağlı olarak dönmesi gerçeğiyle daha da kötüleşir
Rufus

@Rufus Gerek yok. Makinenizde / derleyicinizde / OS / ay döngünüzde olan şey buydu. Tanımlanamayan davranış nedeniyle derleyicinin oluşturduğu önemsiz kod ne olursa olsun, hemen hemen 'gerçek' isteğe bağlı gibi görünüyordu, her neyse.
underscore_d

Yanıtlar:


146

C99 ve C ++ standartları değer döndürmek için işlev gerektirmez. Değer döndüren bir işlevdeki eksik döndürme deyimi, yalnızca işlevde tanımlanacak (döndürülecek 0) main.

Gerekçe, her kod yolunun bir değer döndürüp döndürmediğini kontrol etmenin oldukça zor olduğunu ve gömülü montajcı veya diğer zor yöntemlerle bir dönüş değerinin ayarlanabileceğini içerir.

Gönderen C ++ 11 taslak:

§ 6.6.3 / 2

Bir fonksiyonun sonundan akmak [...], değer döndüren bir fonksiyonda tanımlanmamış davranışa neden olur.

§ 3.6.1 / 5

Kontrol, mainbir return ifadeyle karşılaşmadan sonuna ulaşırsa , bunun etkisi

return 0;

C ++ 6.6.3 / 2'de açıklanan davranışın C'de aynı olmadığını unutmayın.


gcc, -Wreturn-type seçeneği ile çağırırsanız size bir uyarı verecektir.

-Wreturn-type Bir işlev, int olarak varsayılan bir dönüş türüyle tanımlandığında uyarır . Ayrıca, dönüş türü geçersiz olmayan bir işlevde dönüş değeri olmayan herhangi bir döndürme ifadesi hakkında (işlev gövdesinin sonundan düşme değersiz döndürme olarak kabul edilir) ve bir işlevde ifadesi olan bir döndürme ifadesi hakkında uyar dönüş türü geçersizdir.

Bu uyarı -Wall tarafından etkinleştirilir .


Bir merak olarak, bu kodun ne yaptığına bakın:

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

Bu kodun resmi olarak tanımlanmamış davranışı vardır ve pratikte kurallara ve mimariye bağımlı olarak adlandırılır. Belirli bir sistemde, belirli bir derleyiciyle, dönüş değeri, eaxo sistemin işlemcisinin kaydında saklanan son ifade değerlendirmesinin sonucudur .


13
Tanımlanmamış davranışı "izinli" olarak adlandırmaya karşı dikkatli olurum, ancak itiraf etmek gerekirse "yasak" olarak adlandırmak yanlış olur. Hata olmaması ve teşhis gerektirmemesi "izin verilen" ile aynı değildir. En azından cevabınız, yapmanın uygun olduğunu söylediğiniz gibi biraz okuyor, ki bu büyük ölçüde değil.
Orbit'te Hafiflik Yarışları

4
@Catskul, neden bu argümanı alıyorsun? Bir işlevin çıkış noktalarını belirlemek ve hepsinin bir değer (ve beyan edilen dönüş türünün bir değeri) döndürdüğünden emin olmak kolay olmazsa kolay olmaz mıydı?
BlueBomber

3
@Catskul, evet ve hayır. Statik olarak yazılan ve / veya derlenen diller muhtemelen "engelleyici derecede pahalı" olarak değerlendirebileceğiniz pek çok şey yapar, ancak bunu yalnızca bir kez yaptıkları için derleme zamanında ihmal edilebilir masrafları vardır. Bunu söylemiş olsam bile, bir fonksiyonun çıkış noktalarının tanımlanmasının neden süper doğrusal olması gerektiğini görmüyorum: Sadece fonksiyonun AST'sini geçiyorsunuz ve dönüş veya çıkış çağrıları arıyorsunuz. Kesinlikle verimli olan doğrusal zaman.
BlueBomber

3
@LightnessRacesinOrbit: Dönüş değeri olan bir işlev bazen hemen bir değerle geri dönüyorsa ve bazen throwveya üzerinden çıkan başka bir işlev çağırıyorsa longjmp, derleyici döndürülmeyen işleve çağrı yapıldıktan sonra erişilemez returnmi olmalıdır? İhtiyaç duyulmadığı dava çok yaygın değildir ve bu gibi durumlarda bile dahil edilmesi gerekliliği muhtemelen zahmetli olmaz, ancak istememe kararı mantıklıdır.
supercat

1
@supercat: Süper zeki bir derleyici böyle bir durumda uyarmaz veya hata yapmaz, ancak - yine - bu genel durum için esasen hesaplanamaz, bu nedenle genel bir başparmak kuralına bağlı kalırsınız. Bununla birlikte, işlev sonuna asla ulaşılamayacağını biliyorsanız, geleneksel işlev işlemenin anlambiliminden o kadar uzaksınız ki, evet, devam edip bunu yapabilir ve güvenli olduğunu biliyorsunuzdur. Açıkçası, bu noktada C ++ 'ın altında bir katmansınız ve bu nedenle, tüm garantileri zaten tartışmalı.
Yörüngedeki Hafiflik Yarışları

42

gcc varsayılan olarak tüm kod yollarının bir değer döndürüp döndürmediğini kontrol etmez, çünkü genel olarak bu yapılamaz. Ne yaptığınızı bildiğinizi varsayar. Numaralandırma kullanarak ortak bir örnek düşünün:

Color getColor(Suit suit) {
    switch (suit) {
        case HEARTS: case DIAMONDS: return RED;
        case SPADES: case CLUBS:    return BLACK;
    }

    // Error, no return?
}

Programcı, bir hatanın engellenmesi nedeniyle, bu yöntemin her zaman bir renk döndürdüğünü biliyorsunuz. gcc, ne yaptığınızı bildiğinize güvenir, böylece sizi işlevin altına geri döndürmeye zorlamaz.

Diğer yandan, javac, tüm kod yollarının bir değer döndürdüğünü doğrulamaya çalışır ve hepsinin yaptığını kanıtlayamazsa bir hata atar. Bu hata, Java dil belirtimi tarafından zorunlu kılınmıştır. Bazen yanlış olduğunu ve gereksiz bir iade ifadesi koymanız gerektiğini unutmayın.

char getChoice() {
    int ch = read();

    if (ch == -1 || ch == 'q') {
        System.exit(0);
    }
    else {
        return (char) ch;
    }

    // Cannot reach here, but still an error.
}

Bu felsefi bir fark. C ve C ++, Java veya C # 'dan daha izin verici ve güvenilir dillerdir ve bu nedenle yeni dillerdeki bazı hatalar C / C ++' da uyarılardır ve bazı uyarılar varsayılan olarak yoksayılır veya kapatılır.


2
Javac kod yollarını gerçekten kontrol ederse, bu noktaya asla ulaşamayacağınızı görmez miydi?
Chris Lutz

3
İlkinde, tüm numaralandırma durumlarını kapsamak için kredi vermez (varsayılan bir davaya veya anahtardan sonra bir dönüşe ihtiyacınız vardır) ve ikincisinde System.exit()asla geri dönmeyeceğini bilmez .
John Kugelman

2
Javac'ın (aksi takdirde güçlü bir derleyici) System.exit()asla geri dönmeyeceğini bilmesi kolay görünüyor . Baktım ( java.sun.com/j2se/1.4.2/docs/api/java/lang/… ) ve dokümanlar sadece "asla normalde geri dönmeyecek" derler. Bunun ne anlama geldiğini merak ediyorum ...
Paul Biggar

@Paul: Bu, bir maliye sahip olmadıkları anlamına gelir. Diğer tüm diller "asla normal şekilde geri dönmez" - yani "normal geri dönüş mekanizması kullanılarak geri dönmez" der.
Max Lybbert

1
Kesinlikle en azından bu ilk örnekle karşılaşırsa uyaran bir derleyiciyi tercih ederim, çünkü birisi enuma yeni bir değer eklediğinde mantığın doğruluğu kırılacaktı. Yüksek sesle şikayet ve / veya (muhtemelen bir iddia kullanarak) çöktü varsayılan bir dava istiyorum.
Injektilo

14

Yani, değer döndüren bir işlevin sonundan akmak (mesela açık bir şekilde returnçıkmamak) neden bir hata değildir?

İlk olarak, C'de bir işlevin anlamlı bir şey döndürüp döndürmediği yalnızca yürütme kodu gerçekten döndürülen değeri kullandığında kritik öneme sahiptir . Belki de dil, çoğu zaman onu kullanmayacağınızı bildiğinizde sizi bir şey döndürmeye zorlamak istemiyordu.

İkincisi, görünüşe göre dil spesifikasyonu, derleyici yazarlarını açık bir durumun varlığı için olası tüm kontrol yollarını tespit etmeye ve doğrulamaya zorlamak istemedi return(birçok durumda bunu yapmak o kadar da zor değildir). Ayrıca, bazı kontrol yolları geri dönen işlevlere yol açabilir - genellikle derleyici tarafından bilinmeyen özellik. Bu tür yollar can sıkıcı yanlış pozitiflerin kaynağı olabilir.

Ayrıca, C ve C ++ bu durumda davranış tanımlarında farklı olduğunu unutmayın. C ++ 'da sadece bir değer döndürme fonksiyonunun sonundan akmak her zaman tanımsız bir davranıştır (işlevin sonucunun çağrı kodu tarafından kullanılıp kullanılmadığına bakılmaksızın). C'de bu yalnızca arama kodu döndürülen değeri kullanmaya çalıştığında tanımsız davranışa neden olur.


+1 ancak C ++ returnifadeleri sonuna kadar atlayamıyor main()mu?
Chris Lutz

3
@Chris Lutz: Evet, mainbu konuda özel.
ANT

5

C / C ++ altında bir şey döndürdüğünü iddia eden bir işlevden geri dönmemek yasaldır. Arama exit(-1)veya onu çağıran veya bir istisna atan bir işlev gibi bir dizi kullanım durumu vardır .

Derleyici, istemediğiniz halde UB'ye yol açsa bile yasal C ++ 'ı reddetmeyecektir. Özellikle, herhangi bir uyarı oluşturulmasını istemiyorsunuz . (Gcc varsayılan olarak hala bazılarını açar, ancak eklendiklerinde bunlar yeni özelliklerle uyumlu görünüyor gibi görünüyor eski özellikler için yeni uyarılar değil)

Bazı uyarıları yayınlamak için varsayılan no-arg gcc'yi değiştirmek, varolan komut dosyaları veya sistemler yapmak için bir değişiklik olabilir. İyi tasarlanmış olanlar -Wallve uyarılarla ilgilenir veya bireysel uyarıları değiştirir.

Bir C ++ takım zinciri kullanmayı öğrenmek, bir C ++ programcısı olmayı öğrenmek için bir engeldir, ancak C ++ takım zincirleri genellikle uzmanlar tarafından ve uzmanlar için yazılır.


Evet, benim Makefileçalışmam var -Wall -Wpedantic -Werror, ama bu argümanları sağlamayı unuttuğum bir kerelik bir test senaryosuydu.
Monica'nın Davası

2
Örnek olarak, GCC önyüklemesinin bir -Wduplicated-condparçasını yapmak -Wall. Çoğu kodda uygun görünen bazı uyarılar tüm kodlarda uygun değildir. Bu yüzden varsayılan olarak etkin değillerdir.
uh oh birisinin

ilk cümleniz kabul ediliyor cevabındaki alıntıyla çelişiyor gibi görünüyor "Akıyor ... tanımlanmamış davranış ...". Yoksa ub "yasal" kabul edilir mi? Yoksa (döndürülmeyen) gerçekte değer kullanılmadığı sürece UB olmadığını mı kastediyorsunuz? C ++ davası btw hakkında endişeliyim
idclev 463035818

@ tobi303 int foo() { exit(-1); }dönmez int"iddiaları int dönmek için" bir işlevinden. Bu C ++ 'da yasaldır. Şimdi hiçbir şey döndürmüyor ; bu işlevin sonuna asla ulaşılmaz. Aslında sonuna kadar ulaşmakfoo tanımsız bir davranış olurdu. Süreç sonu davalarını göz ardı ederek, int foo() { throw 3.14; }geri döndüğünü iddia ediyor intama asla geri dönmüyor .
Yakk - Adam Nevraumont

1
bu yüzden void* foo(void* arg) { pthread_exit(NULL); }aynı nedenden dolayı iyi olduğunu düşünüyorum (tek kullanım yoluyla olduğunda pthread_create(...,...,foo,...);)
idclev 463035818 22:17

1

Bu eski kod (C asla gerekli iade deyimi yani C ++) nedeniyle olduğuna inanıyorum. Muhtemelen bu "özelliğe" dayanan büyük bir kod tabanı vardır. Ama en azından -Werror=return-type birçok derleyicide bayrak var (gcc ve clang dahil).


1

Bazı sınırlı ve nadir durumlarda, bir değer döndürmeden geçersiz bir işlevin sonundan akmak faydalı olabilir. Aşağıdaki MSVC'ye özel kod gibi:

double pi()
{
    __asm fldpi
}

Bu işlev x86 derleme kullanarak pi döndürür. GCC'deki montajdan farklı returnolarak, sonuçta ek yük olmadan bunu yapmak için kullanmanın bir yolunu bilmiyorum .

Bildiğim kadarıyla, ana C ++ derleyicileri görünüşte geçersiz kod için en azından uyarı yayınlamalıdır. Eğer gövdeyi pi()boş yaparsam , GCC / Clang bir uyarı bildirir ve MSVC bir hata bildirir.

İnsanlar istisnalardan ve exitbazı cevaplarda bahsetti . Bunlar geçerli nedenler değil. Bir istisna atmak veya çağırmak exit, işlev yürütme işleminin sona ermesini sağlamaz . Ve derleyiciler bunu biliyor: bir atım ifadesi yazmak veya exitboş gövdeyi çağırmak pi(), bir derleyiciden gelen uyarıları veya hataları durduracaktır.


MSVC, voidsatır içi asm, dönüş değeri kaydında bir değer bıraktıktan sonra işlev dışı kalmanın sonundan düşmeyi destekler . ( st0bu durumda x87 , tamsayı için EAX. Ve belki xmm0, st0 yerine xmm0'da float / double döndüren bir çağrı kuralında). Bu davranışın tanımlanması MSVC'ye özgüdür; -fasm-blocksaynı sözdizimini desteklemek için bile clang kullanmamak bunu güvenli kılar. Bkz. __Asm {}; eax değerini döndürmek?
Peter Cordes

0

Hangi şartlar altında hata üretmez? Bir dönüş türü bildirir ve bir şey döndürmezse, bana bir hata gibi geliyor.

Düşünebildiğim tek istisna main(), hiç bir returnifadeye ihtiyaç duymayan işlevdir (en azından C ++ 'da; C standartlarından hiçbirine sahip değilim). İade yoksa return 0;, son ifade gibi davranacaktır .


1
main()Bir ihtiyacı returnC.
Chris Lutz

@Jefromi: OP return <value>;açıklama yapmadan geçersiz bir işlev soruyor
Bill

2
main otomatik olarak C ve C ++ 'da 0 değerini döndürür. C89'un açık bir dönüşe ihtiyacı var.
Johannes Schaub - litb

1
@Chris: C99'da (ve yalnızca) return 0;sonunda bir örtük var . Ama yine de eklemek iyi bir stil . main()main()return 0;
pmg

0

Derleyici uyarılarınızı açmanız gerektiği anlaşılıyor:

$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function main’:
<stdin>:1: warning: return with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$

5
"-Açıkla" demek yanıt vermiyor. Açıkçası, uyarılar ve hatalar olarak sınıflandırılan sorunların ciddiyetinde bir fark vardır ve gcc bunu daha az şiddetli sınıf olarak ele alır.
Cascabel

2
@Jefromi: Saf dil bakış açısından, uyarılar ve hatalar arasında fark yoktur. Derleyicinin yalnızca bir "tanılama mesajı" yayınlaması gerekir. Derlemeyi durdurmaya ya da "eror" ve başka bir şeye "uyarı" demeye gerek yoktur. Bir tanısal mesaj verilir (veya herhangi bir tür), bir karar vermek tamamen size bağlıdır.
AnT

1
Sonra tekrar, söz konusu sorun UB neden olur. Derleyicilerin UB'yi yakalaması gerekmez.
ANT

1
N1256'daki 6.9.1 / 12'de "Bir işlevi sonlandıran} değere ulaşılırsa ve işlev çağrısının değeri arayan tarafından kullanılırsa, davranış tanımsızdır" yazıyor.
Johannes Schaub - litb

2
@Chris Lutz: Görmüyorum. Bir kullanmak için bir kısıtlama ihlali olduğunu açıkça boş return;olmayan bir boşluk işlevinde ve kullanmak için bir kısıtlama ihlali olduğunu return <value>;bir boşluk işlevinde. Ama bu, inanıyorum, konu değil. OP, anladığım kadarıyla, boş olmayan bir işlevden return(sadece işlevin sonundan akmasına izin vermeksizin) çıkmakla ilgilidir . Bu bir kısıtlama ihlali değil, AFAIK. Standart sadece C ++ 'da UB ve bazen C de UB olduğunu söylüyor
AnT

-2

C99'da bir kısıtlama ihlalidir, ancak c89'da değildir. Kontrast:

C89:

3.6.6.4 returnAçıklama

Kısıtlamalar

Bir returnifadesi ile deyimi bir dönüş türü olan bir işlev görünmez olacaktır void.

c99:

6.8.6.4 returnAçıklama

Kısıtlamalar

Bir returnifadesi ile deyimi bir dönüş türü olan bir işlev görünmez olacaktır void. Bir returnifadesi olmadan deyimi sadece bir dönüş türü olan bir işlevinde de görünür olacaktır void.

--std=c99Modda bile , gcc sadece bir uyarı verir ( -Wvarsayılan olarak veya c89 / 90'da gerektiği gibi ek bayrakları etkinleştirmeye gerek kalmasa da).

Bunu, c89'da " }bir işlevi sonlandıran returndeğere ulaşmak, ifadesiz ifadenin yürütülmesine eşdeğerdir " ifadesini eklemek için düzenleyin (3.6.6.4). Bununla birlikte, c99'da davranış tanımlanmamıştır (6.9.1).


4
Bunun yalnızca açık returnifadeleri kapsadığını unutmayın . Bir değer döndürmeden bir işlevin sonundan düşmeyi kapsamaz.
John Kugelman

3
C99'un "bir işlevi sonlandıran} işlevine ulaşmanın, ifade olmadan bir döndürme ifadesi yürütmeye eşdeğer olduğunu" kaçırdığından, bir sınırlama ihlali yapılmadığından tanılamaya gerek olmadığını unutmayın.
Johannes Schaub - litb
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.