Pernyataan pengembalian yang hilang dalam kompilasi metode non-void

189

Saya mengalami situasi di mana metode non-void kehilangan pernyataan kembali dan kode masih dikompilasi. Saya tahu bahwa pernyataan setelah loop sementara tidak dapat dijangkau (kode mati) dan tidak akan pernah dieksekusi. Tetapi mengapa kompiler bahkan tidak memperingatkan tentang mengembalikan sesuatu? Atau mengapa suatu bahasa memungkinkan kita untuk memiliki metode non-void yang memiliki loop tak terbatas dan tidak mengembalikan apa pun?

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

Jika saya menambahkan pernyataan break (bahkan yang kondisional) di loop sementara, kompiler mengeluh tentang kesalahan terkenal: Method does not return a valuedi Eclipse dan Not all code paths return a valuedi Visual Studio.

public int doNotReturnAnything() {
    while(true) {
        if(mustReturn) break;
        //do something
    }
    //no return statement
}

Ini berlaku untuk Java dan C #.

cPu1
sumber
3
Pertanyaan yang bagus Saya akan tertarik dengan alasan ini.
Erik Schierboom
18
Tebakan: itu adalah loop tak terbatas, jadi aliran kontrol balik tidak relevan?
Lews Therin
5
"Mengapa suatu bahasa memungkinkan kita untuk memiliki metode non-void yang memiliki loop tak terbatas dan tidak mengembalikan apa pun?" <- sementara kelihatannya bodoh, pertanyaannya juga bisa dibalik: mengapa ini tidak diizinkan? Apakah ini kode yang sebenarnya?
fge
3
Untuk Java, Anda dapat menemukan penjelasan yang bagus dalam jawaban ini .
Keppil
4
Seperti yang telah dijelaskan orang lain, itu karena kompiler cukup pintar untuk mengetahui loop tidak terbatas. Perhatikan bahwa kompiler tidak hanya memungkinkan pengembalian hilang, tetapi juga memaksanya karena ia mengetahui apa pun setelah loop tidak dapat dijangkau. Setidaknya di Netbeans, itu benar-benar akan mengeluh unreachable statementjika ada sesuatu setelah loop.
Supr

Jawaban:

240

Mengapa bahasa memungkinkan kita memiliki metode non-void yang memiliki loop tak terbatas dan tidak mengembalikan apa pun?

Aturan untuk metode non-void adalah setiap jalur kode yang mengembalikan harus mengembalikan nilai , dan aturan itu puas dalam program Anda: nol dari jalur kode nol yang kembali memang mengembalikan nilai. Aturannya bukan "setiap metode non-void harus memiliki jalur kode yang mengembalikan".

Ini memungkinkan Anda untuk menulis metode rintisan seperti:

IEnumerator IEnumerable.GetEnumerator() 
{ 
    throw new NotImplementedException(); 
}

Itu metode yang tidak berlaku. Itu harus menjadi metode non-void untuk memenuhi antarmuka. Tetapi tampaknya konyol untuk membuat implementasi ini ilegal karena tidak mengembalikan apa pun.

Bahwa metode Anda memiliki titik akhir yang tidak terjangkau karena goto(ingat, a while(true)adalah cara yang lebih menyenangkan untuk menulis goto) daripada throw(yang merupakan bentuk lain dari goto) tidak relevan.

Mengapa kompiler bahkan tidak memperingatkan tentang mengembalikan sesuatu?

Karena kompiler tidak memiliki bukti yang baik bahwa kode tersebut salah. Seseorang menulis while(true)dan sepertinya orang yang melakukan itu tahu apa yang mereka lakukan.

Di mana saya bisa membaca lebih lanjut tentang analisis keterjangkauan dalam C #?

Lihat artikel saya tentang masalah ini, di sini:

ATBG: reachability de facto dan de jure

Dan Anda mungkin juga mempertimbangkan membaca spesifikasi C #.

Eric Lippert
sumber
Jadi mengapa kode ini memberikan kesalahan waktu kompilasi: public int doNotReturnAnything() { boolean flag = true; while (flag) { //Do something } //no return } Kode ini juga tidak memiliki titik istirahat. Jadi sekarang bagaimana kompiler mengetahui kode itu salah.
Sandeep Poonia
@SandeepPoonia: Anda mungkin ingin membaca jawaban lain di sini ... Kompilator hanya dapat mendeteksi kondisi tertentu.
Daniel Hilgarth
9
@SandeepPoonia: flag boolean dapat diubah pada saat runtime, karena itu bukan const, oleh karena itu loop tidak selalu tak terbatas.
Schmuli
9
@SandeepPoonia: Dalam C # spesifikasi mengatakan bahwa if, while, for, switch, dll, bercabang konstruksi yang beroperasi pada konstanta diperlakukan sebagai cabang tanpa syarat oleh compiler. Definisi tepat dari ekspresi konstan ada di spec. Jawaban atas pertanyaan Anda ada dalam spesifikasi; saran saya adalah Anda membacanya.
Eric Lippert
1
Every code path that returns must return a valueJawaban Terbaik. Jelas menjawab kedua pertanyaan itu. Terima kasih
cPu1
38

Kompiler Java cukup pintar untuk menemukan kode yang tidak terjangkau (kode setelah whileputaran)

dan karena tidak dapat dijangkau , tidak ada gunanya menambahkan returnpernyataan di sana (setelah whileberakhir)

hal yang sama berlaku dengan kondisional if

public int get() {
   if(someBoolean) {   
     return 10;
   }
   else {
     return 5;
   }
   // there is no need of say, return 11 here;
}

karena kondisi boolean someBooleanhanya dapat mengevaluasi salah satu trueatau false, tidak perlu memberikan secara return eksplisit setelah if-else, karena kode itu tidak dapat dijangkau , dan Java tidak mengeluh tentang hal itu.

sanbhat
sumber
4
Saya tidak berpikir ini benar-benar menjawab pertanyaan itu. Ini menjawab mengapa Anda tidak memerlukan returnpernyataan dalam kode yang tidak dapat dijangkau, tetapi tidak ada hubungannya dengan mengapa Anda tidak memerlukan pernyataan apa pun return dalam kode OP.
Bobson
Jika kompiler Java cukup pintar untuk menemukan kode yang tidak terjangkau (kode setelah loop sementara) lalu mengapa kode-kode di bawah ini mengkompilasi, keduanya memiliki kode yang tidak dapat dijangkau tetapi metode dengan jika memerlukan pernyataan kembali tetapi metode dengan sementara tidak. public int doNotReturnAnything() { if(true){ System.exit(1); } return 11; } public int doNotReturnAnything() { while(true){ System.exit(1); } return 11;// Compiler error: unreachable code }
Sandeep Poonia
@SandeepPoonia: Karena kompiler tidak tahu itu System.exit(1)akan mematikan program. Itu hanya dapat mendeteksi jenis kode tertentu yang tidak terjangkau.
Daniel Hilgarth
@ Daniel Hilgarth: Ok compiler tidak tahu System.exit(1)akan mematikan program, kita bisa menggunakan apa saja return statement, sekarang kompiler mengenali return statements. Dan perilakunya sama, pengembalian membutuhkan if conditiontetapi tidak untuk while.
Sandeep Poonia
1
Peragaan yang baik dari bagian yang tidak terjangkau tanpa menggunakan kasus khusus sepertiwhile(true)
Matius
17

Compiler tahu bahwa whileloop tidak akan pernah berhenti dieksekusi, maka metode ini tidak akan pernah selesai, maka returnpernyataan tidak diperlukan.

Daniel Hilgarth
sumber
13

Mengingat loop Anda dieksekusi pada sebuah konstanta - kompiler tahu bahwa itu adalah infinite loop - artinya metode ini tidak akan pernah bisa kembali.

Jika Anda menggunakan variabel - kompiler akan menegakkan aturan:

Ini tidak dapat dikompilasi:

// Define other methods and classes here
public int doNotReturnAnything() {
    var x = true;

    while(x == true) {
        //do something
    }
    //no return statement - won't compile
}
Dave Bish
sumber
Tetapi bagaimana jika "melakukan sesuatu" tidak melibatkan memodifikasi x dengan cara apa pun .. kompiler tidak pintar untuk mengetahuinya? Bum :(
Lews Therin
Tidak - tidak terlihat seperti itu.
Dave Bish
@Baca jika Anda memiliki metode yang ditandai sebagai pengembalian int, tetapi sebenarnya tidak kembali, maka Anda harus senang bahwa kompiler menandai ini, sehingga Anda dapat menandai metode sebagai batal atau memperbaikinya jika Anda tidak bermaksud untuk itu tingkah laku.
MikeFHay
@ MikeFHay Yap, tidak membantah itu.
Lews Therin
1
Saya mungkin salah tetapi beberapa debugger memungkinkan modifikasi variabel. Di sini, sementara x tidak dimodifikasi oleh kode dan itu akan dioptimalkan oleh JIT orang mungkin memodifikasi x ke false dan metode harus mengembalikan sesuatu (jika hal seperti itu diizinkan oleh C # debugger).
Maciej Piechotka
11

Spesifikasi Java mendefinisikan konsep yang disebut Unreachable statements. Anda tidak diizinkan memiliki pernyataan yang tidak terjangkau dalam kode Anda (ini adalah kesalahan waktu kompilasi). Anda bahkan tidak diizinkan memiliki pernyataan pengembalian setelah beberapa saat (benar); pernyataan di Jawa. Sebuah while(true);pernyataan yang membuat pernyataan berikut tidak terjangkau oleh definisi, karena itu Anda tidak perlu returnpernyataan.

Perhatikan bahwa sementara Menghentikan masalah tidak dapat diputuskan dalam kasus umum, definisi Unreachable Statement lebih ketat daripada hanya berhenti. Ini memutuskan kasus yang sangat spesifik di mana suatu program pasti tidak berhenti. Compiler secara teori tidak dapat mendeteksi semua loop tak terhingga dan pernyataan yang tidak dapat dijangkau tetapi ia harus mendeteksi case spesifik yang didefinisikan dalam spesifikasi (misalnya while(true)case)

Konstantin Yovkov
sumber
7

Kompiler cukup pintar untuk mengetahui bahwa whileloop Anda tidak terbatas.

Jadi kompiler tidak dapat berpikir untuk Anda. Tidak dapat menebak mengapa Anda menulis kode itu. Yang sama berarti nilai-nilai kembali metode. Java tidak akan mengeluh jika Anda tidak melakukan apa pun dengan nilai pengembalian metode.

Jadi, untuk menjawab pertanyaanmu:

Kompiler menganalisis kode Anda dan setelah mengetahui bahwa tidak ada jalur eksekusi yang mengarah ke akhir fungsi yang selesai dengan OK.

Mungkin ada alasan yang sah untuk loop tak terbatas. Misalnya banyak aplikasi menggunakan loop utama tanpa batas. Contoh lain adalah server web yang mungkin menunggu permintaan tanpa batas.

Adam Arold
sumber
7

Dalam teori tipe, ada sesuatu yang disebut tipe dasar yang merupakan subkelas dari setiap tipe lainnya (!) Dan digunakan untuk menunjukkan non-terminasi di antara hal-hal lain. (Pengecualian dapat dihitung sebagai jenis non-pemutusan - Anda tidak berhenti melalui jalur normal.)

Jadi dari perspektif teoretis, pernyataan-pernyataan ini yang non-terminating dapat dianggap untuk mengembalikan sesuatu dari tipe Bawah, yang merupakan subtipe dari int, sehingga Anda (jenis) mendapatkan nilai pengembalian Anda setelah semua dari perspektif jenis. Dan benar-benar oke bahwa tidak masuk akal bahwa satu jenis dapat menjadi subkelas dari semua yang lain termasuk int karena Anda tidak pernah benar-benar mengembalikan satu.

Dalam kasus apa pun, melalui teori tipe eksplisit atau tidak, kompiler (penulis kompiler) mengakui bahwa meminta nilai kembali setelah pernyataan non-terminasi adalah konyol: tidak ada kasus yang memungkinkan di mana Anda memerlukan nilai itu. (Mungkin bagus jika kompiler Anda memperingatkan Anda ketika tahu ada sesuatu yang tidak akan berakhir tetapi sepertinya Anda ingin mengembalikan sesuatu. Tapi itu lebih baik bagi style-checker ala la lint, karena mungkin Anda memerlukan jenis tanda tangan yang cara untuk beberapa alasan lain (misalnya subclassing) tetapi Anda benar-benar ingin non-terminasi.)

Rex Kerr
sumber
6

Tidak ada situasi di mana fungsi dapat mencapai tujuannya tanpa mengembalikan nilai yang sesuai. Oleh karena itu, tidak ada kompiler yang dapat dikeluhkan.

Graham Borland
sumber
5

Visual studio memiliki mesin pintar untuk mendeteksi jika Anda telah mengetik jenis kembali maka harus memiliki pernyataan kembali dengan fungsi / metode.

Seperti dalam PHP, tipe pengembalian Anda benar jika Anda belum mengembalikan apa pun. compiler dapatkan 1 jika tidak ada yang kembali.

Sampai disini

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

Compiler tahu bahwa sementara pernyataan itu sendiri memiliki sifat infinte sehingga tidak mempertimbangkannya. dan kompiler php akan secara otomatis menjadi kenyataan jika Anda menulis suatu kondisi dalam ekspresi while.

Tetapi tidak dalam kasus VS itu akan mengembalikan Anda kesalahan dalam tumpukan.

Tarun Gupta
sumber
4

Loop sementara Anda akan berjalan selamanya dan karenanya tidak akan keluar sementara; itu akan terus dijalankan. Karenanya, bagian luar sementara {} tidak dapat dijangkau dan tidak ada gunanya secara tertulis kembali atau tidak. Kompiler cukup cerdas untuk mengetahui bagian mana yang dapat dijangkau dan bagian mana yang tidak.

Contoh:

public int xyz(){
    boolean x=true;

    while(x==true){
        // do something  
    }

    // no return statement
}

Kode di atas tidak akan dikompilasi, karena mungkin ada kasus bahwa nilai variabel x dimodifikasi di dalam tubuh while loop. Jadi ini membuat bagian luar dari sementara loop dapat dijangkau! Dan karenanya kompiler akan melempar kesalahan 'tidak ada pernyataan kembali ditemukan'.

Kompiler tidak cukup pintar (atau lebih tepatnya malas;)) untuk mengetahui apakah nilai x akan dimodifikasi atau tidak. Semoga ini membersihkan semuanya.

kunal18
sumber
7
Sebenarnya dalam hal ini bukan pertanyaan tentang kompiler yang cukup pintar. Dalam C # 2.0 kompiler cukup pintar untuk mengetahui bahwa int x = 1; while(x * 0 == 0) { ... }itu adalah infinite loop, tetapi spesifikasinya mengatakan bahwa compiler hanya boleh membuat pengurangan aliran kontrol ketika ekspresi loop konstan , dan spesifikasi mendefinisikan ekspresi konstan sebagai tidak mengandung variabel . Karena itu kompilernya terlalu pintar . Dalam C # 3 saya membuat kompiler cocok dengan spesifikasi, dan sejak itu sengaja kurang pintar tentang ekspresi semacam ini.
Eric Lippert
4

"Mengapa kompiler bahkan tidak memperingatkan tentang mengembalikan sesuatu? Atau mengapa suatu bahasa memungkinkan kita untuk memiliki metode non-void yang memiliki infinite loop dan tidak mengembalikan apa pun?".

Kode ini juga berlaku dalam semua bahasa lain (mungkin kecuali Haskell!). Karena asumsi pertama adalah kita "sengaja" menulis beberapa kode.

Dan ada beberapa situasi di mana kode ini bisa benar-benar valid seperti jika Anda akan menggunakannya sebagai utas; atau jika mengembalikan Task<int>, Anda dapat melakukan beberapa pengecekan kesalahan berdasarkan nilai int yang dikembalikan - yang tidak boleh dikembalikan.

Kaveh Shahbazian
sumber
3

Saya mungkin salah tetapi beberapa debugger memungkinkan modifikasi variabel. Di sini, sementara x tidak dimodifikasi oleh kode dan itu akan dioptimalkan oleh JIT orang mungkin memodifikasi x ke false dan metode harus mengembalikan sesuatu (jika hal seperti itu diizinkan oleh C # debugger).


sumber
1

Spesifik kasus Java untuk ini (yang mungkin sangat mirip dengan kasus C #) berkaitan dengan bagaimana kompiler Java menentukan apakah suatu metode dapat kembali.

Secara khusus, aturannya adalah bahwa metode dengan tipe pengembalian tidak harus dapat diselesaikan secara normal dan sebaliknya harus selalu lengkap secara tiba-tiba (tiba-tiba di sini ditunjukkan melalui pernyataan pengembalian atau pengecualian) per JLS 8.4.7 .

Jika suatu metode dinyatakan memiliki tipe kembali, maka kesalahan waktu kompilasi terjadi jika tubuh metode dapat menyelesaikan secara normal. Dengan kata lain, metode dengan tipe pengembalian harus kembali hanya dengan menggunakan pernyataan pengembalian yang memberikan pengembalian nilai; itu tidak diizinkan untuk "menurunkan ujung tubuhnya" .

Kompilator terlihat untuk melihat apakah penghentian normal dimungkinkan berdasarkan aturan yang didefinisikan dalam JLS 14.21 Pernyataan Tidak Terjangkau karena juga mendefinisikan aturan untuk penyelesaian normal.

Khususnya, aturan untuk pernyataan yang tidak terjangkau membuat kasus khusus hanya untuk loop yang memiliki trueekspresi konstan yang didefinisikan :

Pernyataan sementara dapat diselesaikan secara normal jika setidaknya salah satu dari yang berikut ini benar:

  • Pernyataan while dapat dicapai dan ekspresi kondisi bukan ekspresi konstan (§15.28) dengan nilai true.

  • Ada pernyataan break yang bisa dicapai yang keluar dari pernyataan while.

Jadi jika whilepernyataan tersebut dapat diselesaikan secara normal , maka pernyataan kembali di bawah ini diperlukan karena kode dianggap dapat dicapai, dan setiap whileloop tanpa pernyataan break yang dapat dicapai atau trueekspresi konstan dianggap mampu menyelesaikan secara normal.

Aturan-aturan ini berarti bahwa Anda whilepernyataan dengan ekspresi yang benar konstan dan tanpa breakyang tidak pernah dianggap untuk menyelesaikan normal , dan sehingga setiap kode di bawah itu tidak pernah dianggap dicapai . Akhir dari metode berada di bawah loop, dan karena segala sesuatu di bawah loop tidak dapat dijangkau, demikian juga akhir dari metode, dan dengan demikian metode tersebut tidak mungkin dapat diselesaikan secara normal (yang dicari oleh pengumpul).

if pernyataan, di sisi lain, tidak memiliki pengecualian khusus tentang ekspresi konstan yang diberikan untuk loop.

Membandingkan:

// I have a compiler error!
public boolean testReturn()
{
    final boolean condition = true;

    if (condition) return true;
}

Dengan:

// I compile just fine!
public boolean testReturn()
{
    final boolean condition = true;

    while (condition)
    {
        return true;
    }
}

Alasan untuk pembedaan ini cukup menarik, dan karena keinginan untuk mengizinkan flag kompilasi bersyarat yang tidak menyebabkan kesalahan kompiler (dari JLS):

Seseorang mungkin mengharapkan pernyataan if ditangani dengan cara berikut:

  • Pernyataan jika-maka dapat menyelesaikan secara normal jika setidaknya salah satu dari berikut ini benar:

    • Pernyataan jika-maka dapat dicapai dan ekspresi kondisi bukanlah ekspresi konstan yang nilainya benar.

    • Pernyataan kemudian dapat diselesaikan secara normal.

    Pernyataan-kemudian dapat dicapai jika pernyataan if-maka dapat dicapai dan ekspresi kondisi bukan ekspresi konstan yang nilainya salah.

  • Pernyataan if-then-else dapat diselesaikan secara normal jika if-then-statement dapat diselesaikan secara normal atau statement-else dapat diselesaikan secara normal.

    • Pernyataan kemudian dapat dicapai jika pernyataan if-then-else dapat dicapai dan ekspresi kondisi bukanlah ekspresi konstan yang nilainya salah.

    • Pernyataan else dapat dicapai jika pernyataan if-then-else dapat dicapai dan ekspresi kondisi bukanlah ekspresi konstan yang nilainya benar.

Pendekatan ini akan konsisten dengan perawatan struktur kontrol lainnya. Namun, untuk memungkinkan pernyataan if digunakan dengan nyaman untuk tujuan "kompilasi bersyarat", aturan sebenarnya berbeda.

Sebagai contoh, pernyataan berikut ini menghasilkan kesalahan waktu kompilasi:

while (false) { x=3; }karena pernyataan x=3;itu tidak terjangkau; tetapi kasus serupa yang dangkal:

if (false) { x=3; }tidak menghasilkan kesalahan waktu kompilasi. Kompilator pengoptimal mungkin menyadari bahwa pernyataan x=3;itu tidak akan pernah dieksekusi dan dapat memilih untuk menghilangkan kode untuk pernyataan itu dari file kelas yang dihasilkan, tetapi pernyataan x=3;itu tidak dianggap sebagai "tidak terjangkau" dalam pengertian teknis yang ditentukan di sini.

Alasan untuk perlakuan berbeda ini adalah untuk memungkinkan programmer untuk mendefinisikan "variabel flag" seperti:

static final boolean DEBUG = false; lalu tulis kode seperti:

if (DEBUG) { x=3; } Idenya adalah bahwa mungkin untuk mengubah nilai DEBUG dari false ke true atau dari true ke false dan kemudian mengkompilasi kode dengan benar tanpa ada perubahan lain pada teks program.

Mengapa pernyataan break bersyarat menghasilkan kesalahan kompilator?

Seperti dikutip dalam aturan reachability loop, loop sementara juga bisa selesai secara normal jika berisi pernyataan break yang dapat dicapai. Karena aturan untuk keterjangkauan suatu ifpernyataan maka klausa tidak mempertimbangkan kondisi itu ifsama sekali, maka klausa ifpernyataan bersyarat seperti itu selalu dianggap dapat dijangkau.

Jika breakterjangkau, maka kode setelah loop sekali lagi juga dianggap dapat dijangkau. Karena tidak ada kode yang dapat dijangkau yang menghasilkan penghentian mendadak setelah loop, metode ini kemudian dianggap dapat menyelesaikan secara normal, dan kompilator menandainya sebagai kesalahan.

Trevor Freeman
sumber