Pertanyaan Kode penjaga setelah mengaktifkan enum tidak pernah tercapai


Saya baru saja memecahkan masalah yang membingungkan ketika mencoba mengkompilasi beberapa kode menggunakan g ++ 4.4.3.

Kode di bawah ini dapat dikompilasi dengan baik, tetapi alih-alih menekan pernyataan yang diharapkan ketika saya memberikan nilai enum yang 'tidak valid', fungsi tersebut hanya mengembalikan 1. Apa yang saya anggap lebih aneh adalah bahwa ketika saya menghapus tanda komentar yang berkaitan dengan nilai enum E3, hal-hal mulai bekerja seperti yang diharapkan.

Fakta bahwa tidak ada entri default di blok switch adalah berdasarkan desain. Kami mengkompilasi dengan opsi -Wall untuk mendapatkan peringatan nilai enum yang tidak tertangani.

enum MyEnum
{
    E1,
    E2, 
    //E3
};

int doSomethingWithEnum(MyEnum myEnum)
{
    switch (myEnum)
    {
        case E1: return 1;
        case E2: return 2;
        //case E3: return 3;
    }

    assert(!"Should never get here");
    return -1;
}

int main(int argc, char **argv)
{
    // Should trigger assert, but actually returns 1
    int retVal =  doSomethingWithEnum(static_cast<MyEnum>(4));
    std::cout << "RetVal=" << retVal << std::endl;

    return 0;
}

12
2017-12-30 12:39


asal


Jawaban:


Pernyataan switch Anda akan dikompilasi menjadi ini (g ++ 4.4.5):

    cmpl    $1, %eax
    je      .L3
    movl    $1, %eax
    jmp     .L4
.L3:
    movl    $2, %eax
.L4:
    leave
    ret

Seperti dapat dilihat, pernyataan dioptimalkan sepenuhnya, dan kompilator memilih untuk membandingkan terhadap E2 dan mengembalikan 1 dalam semua kasus lainnya. Dengan tiga nilai enum, itu tidak bisa dilakukan.

Bagian 5.2.9 dari standar C ++ 98 (pemeran statis) memberikan alasan untuk mengizinkan ini:

Nilai tipe integral atau enumerasi dapat secara eksplisit dikonversi ke tipe enumerasi. Nilai tidak berubah   jika nilai asli berada dalam kisaran nilai enumerasi (7.2). Jika tidak, nilai enumerasi yang dihasilkan adalah   tidak ditentukan.

Dengan kata lain, compiler bebas untuk menggunakan nilai enum apa pun yang diinginkan, (dalam hal ini E1) jika Anda mencoba menggunakan nilai ilegal. Ini termasuk melakukan hal yang intuitif dan menggunakan nilai ilegal yang disediakan atau menggunakan nilai yang berbeda tergantung pada keadaan, itulah sebabnya perilaku berubah tergantung pada jumlah nilai enum.


9
2017-12-30 13:00



Ini tidak terlihat seperti kesalahan kompilator. Ini lebih seperti perilaku yang tidak terdefinisi. Aturan mengatakan bahwa Anda dapat mengaitkan nilai tidak terdefinisi ke Enum IF nilai ini berada di kisaran enum. Dalam kasus Anda, compiler hanya membutuhkan satu bit untuk merepresentasikan enum, jadi mungkin melakukan semacam pengoptimalan.


8
2017-12-30 12:52



Ini sangat normal, kode Anda bergantung pada perilaku yang tidak ditentukan.

Dalam C ++, enum hanya seharusnya menyimpan nilai antara nilai yang lebih rendah dan lebih tinggi. Hal lain dapat diubah atau diabaikan oleh kompilator.

Misalnya, jika saya punya enum Foo { min = 10, max = 11 }; maka compiler bebas untuk merepresentasikannya dengan satu bit yang signifikan dan menambahkan 10 ketika saya meminta untuk mencetak atau mengubahnya menjadi integer.

Secara umum meskipun, kompiler tidak mengambil keuntungan dari "pemadatan" ini karena biaya kinerja, mereka hanya memilih jenis yang mendasari yang dapat mengakomodasi semua nilai dan 0. Paling sering itu adalah sebuah int, kecuali kalau int terlalu kecil.

Namun, itu tidak mencegah mereka menganggap bahwa nilai-nilai ini int berisi dibatasi untuk rentang yang Anda tetapkan. Dalam kasus Anda: [0, 2).

Dengan demikian, switch Pernyataan hanya dapat dioptimalkan ke dalam perbandingan dengan 0, karena Anda menetapkan bahwa enum Anda hanya bisa 0 atau 1.

Dalam nada yang sama, if (foo == 3) dapat dianggap sebagai perbandingan tautologis (selalu salah) dan dioptimalkan.

Bahkan, seperti yang dijelaskan dalam "bug" gcc yang ditandai oleh Hans Passat, pengoptimalan ini biasanya terjadi pada kekuatan batas 2s untuk gcc. Jadi misalnya jika Anda punya enum { zero, one, two, three };dan ditugaskan 4 atau lebih tinggi, perilaku yang sama akan terjadi.

Perhatikan bahwa nilai yang sebenarnya disimpan tidak terpengaruh! Inilah sebabnya mengapa pernyataan cetak berfungsi sebagaimana yang Anda harapkan, dan merupakan sumber kebingungan.

Saya tidak tahu apakah ada peringatan untuk menunjukkan bahwa menyimpan 4 di enum ini akan mengarah pada perilaku yang tidak ditentukan. Dalam hal apapun, itu hanya akan bekerja untuk nilai-nilai waktu-compile ...


Hans Passat menyediakan gcc "bug" yang terbuka untuk melacak "masalah" ini.

Dan Emil Styrke memberikan pembenaran (di sini adalah kutipan terbaru untuk enum scopi):

5.2.9 / 10

Nilai tipe integral atau enumerasi dapat secara eksplisit dikonversi ke tipe enumerasi. Nilai tidak berubah jika nilai asli berada dalam kisaran nilai enumerasi (7.2). Jika tidak, nilai yang dihasilkan tidak ditentukan (dan mungkin tidak dalam kisaran itu). Nilai tipe floating-point juga dapat dikonversi ke tipe enumerasi. Nilai yang dihasilkan sama dengan mengkonversi nilai asli ke tipe yang mendasari pencacahan (4.9), dan kemudian ke tipe enumerasi.


7
2017-12-30 13:25