Điều gì xảy ra nếu bạn static_cast giá trị không hợp lệ cho lớp enum?


146

Hãy xem xét mã C ++ 11 này:

enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(data[0]);

Giả sử rằng dữ liệu [0] thực sự là 100. Màu sắc được đặt theo tiêu chuẩn là gì? Đặc biệt, nếu sau này tôi làm

switch (color) {
    // ... red and yellow cases omitted
    default:
        // handle error
        break;
}

Có đảm bảo tiêu chuẩn mà mặc định sẽ được nhấn? Nếu không, cách thích hợp nhất, hiệu quả nhất, thanh lịch nhất để kiểm tra lỗi ở đây là gì?

BIÊN TẬP:

Là một phần thưởng, tiêu chuẩn có đảm bảo bất kỳ điều gì về điều này nhưng với enum đơn giản?

Câu trả lời:


131

Màu sắc được đặt theo tiêu chuẩn là gì?

Trả lời bằng một trích dẫn từ Tiêu chuẩn C ++ 11 và C ++ 14:

[expr.static.cast] / 10

Giá trị của loại tích phân hoặc liệt kê có thể được chuyển đổi rõ ràng thành loại liệt kê. Giá trị không thay đổi nếu giá trị ban đầu nằm trong phạm vi của các giá trị liệt kê (7.2). Mặt khác, giá trị kết quả là không xác định (và có thể không nằm trong phạm vi đó).

Hãy tìm kiếm phạm vi của các giá trị liệt kê : [dcl.enum] / 7

Đối với một phép liệt kê có kiểu cơ bản được cố định, các giá trị của phép liệt kê là các giá trị của kiểu bên dưới.

Trước CWG 1766 (C ++ 11, C ++ 14) Do đó, đối với data[0] == 100, giá trị kết quả được chỉ định (*) và không có Hành vi không xác định (UB) nào được tham gia. Tổng quát hơn, khi bạn chuyển từ loại cơ bản sang loại liệt kê, không có giá trị nào data[0]có thể dẫn đến UB cho static_cast.

Sau CWG 1766 (C ++ 17) Xem khuyết tật CWG 1766 . Đoạn p10 [expr.static.cast] đã được tăng cường, vì vậy bây giờ bạn có thể gọi UB nếu bạn đặt một giá trị nằm ngoài phạm vi có thể biểu thị của một enum cho loại enum. Điều này vẫn không áp dụng cho kịch bản trong câu hỏi, vì data[0]thuộc loại liệt kê cơ bản (xem ở trên).

Xin lưu ý rằng CWG 1766 được coi là một khiếm khuyết trong Tiêu chuẩn, do đó, nó được chấp nhận cho những người triển khai trình biên dịch áp dụng cho các chế độ biên dịch C ++ 11 và C ++ 14 của họ.

(*) charđược yêu cầu rộng tối thiểu 8 bit, nhưng không bắt buộc phải có unsigned. Giá trị lưu trữ tối đa được yêu cầu tối thiểu phải 127theo Phụ lục E của Tiêu chuẩn C99.


So sánh với [expr] / 4

Nếu trong quá trình đánh giá biểu thức, kết quả không được xác định theo toán học hoặc không nằm trong phạm vi giá trị đại diện cho loại của nó, hành vi không được xác định.

Trước CWG 1766, loại tích phân chuyển đổi -> loại liệt kê có thể tạo ra một giá trị không xác định . Câu hỏi là: một giá trị không xác định có thể nằm ngoài các giá trị đại diện cho loại của nó không? Tôi tin rằng câu trả lời là không - nếu câu trả lời là , sẽ không có sự khác biệt nào trong các đảm bảo bạn nhận được cho các thao tác trên các loại đã ký giữa "thao tác này tạo ra giá trị không xác định" và "thao tác này có hành vi không xác định".

Do đó, trước CWG 1766, thậm chí static_cast<Color>(10000)sẽ không gọi UB; nhưng sau khi CWG 1766, nó không invoke UB.


Bây giờ, switchtuyên bố:

[stmt.switch] / 2

Điều kiện phải là loại tích phân, loại liệt kê hoặc loại lớp. [...] Các chương trình khuyến mãi tích hợp được thực hiện.

[conv.prom] / 4

Một prvalue của một unscoped kiểu enumeration có tiềm ẩn loại là cố định (7.2) có thể được chuyển đổi sang một prvalue loại cơ bản của nó. Ngoài ra, nếu quảng cáo tích hợp có thể được áp dụng cho loại cơ sở của nó, thì giá trị của loại liệt kê không có phạm vi có loại cơ bản được cố định cũng có thể được chuyển đổi thành giá trị của loại cơ sở được quảng cáo.

Lưu ý: Loại cơ bản của enum có phạm vi w / o enum-baseint. Đối với các enum không có phạm vi, kiểu bên dưới được xác định theo thực thi, nhưng không được lớn hơn intnếu intcó thể chứa các giá trị của tất cả các điều tra viên.

Đối với một phép liệt kê không giới hạn , điều này dẫn chúng ta đến / 1

Một prvalue của một kiểu số nguyên khác hơn bool, char16_t, char32_t, hoặc wchar_tcó số nguyên chuyển đổi cấp bậc (4.13) là thấp hơn cấp bậc intcó thể được chuyển đổi sang một prvalue loại intnếu intcó thể đại diện cho tất cả các giá trị của các loại nguồn; mặt khác, giá trị nguồn có thể được chuyển đổi thành giá trị loại unsigned int.

Trong trường hợp của một unscoped điều tra, chúng tôi sẽ đối phó với ints đây. Đối với bảng liệt kê phạm vi ( enum classenum struct), không áp dụng khuyến mãi tích hợp. Trong bất kỳ cách nào, quảng cáo tích hợp cũng không dẫn đến UB, vì giá trị được lưu trữ nằm trong phạm vi của loại cơ bản và trong phạm vi int.

[stmt.switch] / 5

Khi switchcâu lệnh được thực thi, điều kiện của nó được ước tính và so sánh với từng hằng số trường hợp. Nếu một trong các hằng số trường hợp bằng giá trị của điều kiện, điều khiển được chuyển đến câu lệnh theo casenhãn phù hợp . Nếu không có casehằng số phù hợp với điều kiện và nếu có defaultnhãn, điều khiển sẽ chuyển đến câu lệnh được gắn nhãn bởi defaultnhãn.

Các defaultnhãn nên được nhấn.

Lưu ý: Người ta có thể có cái nhìn khác về toán tử so sánh, nhưng nó không được sử dụng rõ ràng trong "so sánh" được đề cập. Trong thực tế, không có gợi ý nào về việc giới thiệu UB cho các enum có phạm vi hoặc không có phạm vi trong trường hợp của chúng tôi.


Là một phần thưởng, tiêu chuẩn có đảm bảo bất kỳ điều gì về điều này nhưng với enum đơn giản?

Cho dù có hay không enumphạm vi không làm cho bất kỳ sự khác biệt ở đây. Tuy nhiên, nó sẽ tạo ra sự khác biệt cho dù loại cơ bản có được sửa hay không. Toàn bộ [Dec.enum] / 7 là:

Đối với một phép liệt kê có kiểu cơ bản được cố định, các giá trị của phép liệt kê là các giá trị của kiểu bên dưới. Nếu không, cho một điều tra nơi e phút là các điều tra viên và nhỏ e max là lớn nhất, các giá trị thuộc kiểu liệt kê là các giá trị trong phạm vi b phút để b max , được xác định như sau: Hãy Kthể 1cho đại diện bổ sung của hai và 0cho một bổ sung hoặc đại diện dấu hiệu của một. b max là giá trị nhỏ nhất lớn hơn hoặc bằng max (| e min | - K, | e max |) và bằng 2M - 1 , trong đóMmột số nguyên không âm. b min bằng 0 nếu e min không âm và - (b max + K) nếu không.

Chúng ta hãy xem bảng liệt kê sau:

enum ColorUnfixed /* no fixed underlying type */
{
    red = 0x1,
    yellow = 0x2
}

Lưu ý rằng chúng ta không thể định nghĩa đây là một enum có phạm vi, vì tất cả các enum có phạm vi đều có các kiểu cơ bản cố định.

May mắn thay, ColorUnfixedliệt kê nhỏ nhất là red = 0x1, vì vậy max (| e min | - K, | e max |) bằng với | e max | trong mọi trường hợp, đó là yellow = 0x2. Giá trị nhỏ nhất lớn hơn hoặc bằng 2, bằng 2 M - 1 cho số nguyên dương M3( 2 2 - 1 ). (Tôi nghĩ rằng mục đích là cho phép phạm vi mở rộng trong các bước 1 bit.) Theo sau đó b max3bmin0.

Do đó, 100sẽ nằm ngoài phạm vi ColorUnfixedstatic_castsẽ tạo ra một giá trị không xác định trước CWG 1766 và hành vi không xác định sau CWG 1766.


3
Loại cơ bản là cố định, vì vậy phạm vi của các giá trị liệt kê (§7.2 [dcl.enum] p7) là "các giá trị của loại bên dưới". 100 chắc chắn là một giá trị của char, vì vậy "Giá trị không thay đổi nếu giá trị ban đầu nằm trong phạm vi của các giá trị liệt kê (7.2)." áp dụng.
Casey

2
Tôi đã phải tìm kiếm để tìm kiếm "UB" nghĩa là gì. ('hành vi không xác định') Câu hỏi không đề cập đến khả năng hành vi không xác định; Vì vậy, nó đã không xảy ra với tôi rằng bạn có thể nói về điều đó.
karadoc

2
@karadoc Tôi đã thêm một liên kết tại lần xuất hiện đầu tiên của thuật ngữ.
dyp

1
Yêu câu trả lời này. Đối với những người lướt qua quá nhanh, lưu ý rằng câu cuối cùng "Do đó, 100 sẽ nằm ngoài phạm vi ..." chỉ áp dụng nếu mã được sửa đổi để loại bỏ đặc tả loại cơ bản (char trong trường hợp này). Tôi nghĩ đó là những gì có nghĩa là, dù sao.
Eric Seppanen

1
@Ruslan CWG 1766 (hoặc độ phân giải của nó) không phải là một phần của C ++ 14, nhưng tôi nghĩ nó sẽ là một phần của C ++ 17. Ngay cả với các quy tắc C ++ 17, tôi hoàn toàn không hiểu ý của bạn với "văn bản trả lời thêm của bạn". Các phần khác trong câu trả lời của tôi chủ yếu liên quan đến việc "phạm vi của các giá trị liệt kê" là expr.static.cast p10 đang đề cập đến.
dyp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.