Hằng số Enum hoạt động khác nhau trong C và C ++


81

Tại sao điều này:

#include <stdio.h>
#include <limits.h>
#include <inttypes.h>

int main() {
    enum en_e {
        en_e_foo,
        en_e_bar = UINT64_MAX,
    };
    enum en_e e = en_e_foo;
    printf("%zu\n", sizeof en_e_foo);
    printf("%zu\n", sizeof en_e_bar);
    printf("%zu\n", sizeof e);
}

in 4 8 8trong C và 8 8 8trong C ++ (trên nền tảng có 4 byte int)?

Tôi có ấn tượng rằng việc UINT64_MAXgán sẽ buộc tất cả các hằng số liệt kê ít nhất là 64 bit, nhưng en_e_foovẫn ở mức 32 trong C.

Cơ sở lý luận cho sự khác biệt là gì?


1
Trình biên dịch nào? Tôi không biết liệu nó có tạo ra sự khác biệt hay không, nhưng nó có thể.
Mark Ransom

@MarkRansom Nó được tạo ra với gcc nhưng clang hoạt động giống nhau.
PSkocik


3
"trên nền tảng có 4 byte int" Nó không chỉ là nền tảng, mà còn là trình biên dịch xác định độ rộng kiểu. Đó có thể là tất cả những điều này. (Theo câu trả lời của Keith, thực ra là không, nhưng hãy lưu ý về những khả năng như vậy nói chung)
Các cuộc đua ánh sáng trên quỹ đạo

1
@PSkocik: Không hẳn là một thay đổi, chỉ là câu hỏi này đã tìm thấy cách sử dụng hợp lệ của cả cc ++ (hỏi tại sao một số mã nhất định lại gây ra hành vi khác nhau giữa cả hai). Cũng ok: hỏi cách gọi các thư viện C từ C ++ và cách viết C ++ có thể được gọi từ C. Rất không ổn: đặt câu hỏi C và ném thẻ C ++ vào "để nó thu hút được nhiều nhãn quan hơn". Cũng không ổn: đặt một câu hỏi C ++ và như một suy nghĩ sau "hãy chắc chắn rằng bạn cũng trả lời cho C". (và cho hay phàn nàn thông thường - rất không ok: thay đổi một C ++ thẻ để thẻ C vì sử dụng mã chức năng mà tồn tại trong cả hai tiêu chuẩn)
Ben Voigt

Câu trả lời:


80

Trong C, một enumhằng số là kiểu int. Trong C ++, nó thuộc kiểu liệt kê.

enum en_e{
    en_e_foo,
    en_e_bar=UINT64_MAX,
};

Trong C, đây là một vi phạm ràng buộc , yêu cầu chẩn đoán ( nếu UINT64_MAX vượt quá INT_MAX, rất có thể xảy ra). Trình biên dịch AC có thể từ chối chương trình hoàn toàn hoặc nó có thể in cảnh báo và sau đó tạo một tệp thực thi có hành vi không được xác định. (Không rõ ràng 100% rằng một chương trình vi phạm một ràng buộc nhất thiết phải có hành vi không xác định, nhưng trong trường hợp này, tiêu chuẩn không cho biết hành vi đó là gì, vì vậy đó vẫn là hành vi không xác định.)

gcc 6.2 không cảnh báo về điều này. leng keng. Đây là một lỗi trong gcc; nó ngăn chặn không chính xác một số thông báo chẩn đoán khi macro từ các tiêu đề chuẩn được sử dụng. Cảm ơn Grzegorz Szpetkowski đã tìm thấy báo cáo lỗi: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71613

Trong C ++, mỗi kiểu liệt kê có một kiểu cơ bản , đó là một số kiểu số nguyên (không nhất thiết int). Loại cơ bản này phải có thể đại diện cho tất cả các giá trị không đổi. Vì vậy, trong trường hợp này, cả hai en_e_fooen_e_barđều thuộc loại en_e, phải rộng ít nhất 64 bit, ngay cả khi inthẹp hơn.


10
lưu ý nhanh: để UINT64_MAXkhông vượt quá INT_MAXyêu cầu intít nhất là 65 bit.
Ben Voigt

10
Điều thực sự kỳ lạ là gcc (5.3.1) phát ra một cảnh báo với -Wpedantic18446744073709551615ULLnhưng không với UINT64_MAX.
nwellnhof

4
@dascandy: Không, intphải là kiểu có dấu, vì vậy nó sẽ phải có ít nhất 65 bit để có thể đăng lại UINT64_MAX(2 ** 64-1).
Keith Thompson

1
@KeithThompson, 6.7.2.2 nói rằng "các định danh trong danh sách điều tra viên được khai báo là hằng số có kiểu int và có thể xuất hiện ở bất cứ nơi nào được phép." Sự hiểu biết của tôi là các hằng số mà một enum C đơn lẻ khai báo không sử dụng kiểu của enum, vì vậy từ đó sẽ không phải là một khoảng lớn để biến chúng thành các kiểu khác nhau (đặc biệt nếu nó được triển khai như một phần mở rộng cho tiêu chuẩn).
zneak

2
@AndrewHenle: en_e_barkhông lớn hơn enum, en_e_foonhỏ hơn. Biến enum lớn hơn hằng số lớn nhất.
Ben Voigt

25

Mã đó chỉ không hợp lệ C ngay từ đầu.

Mục 6.7.2.2 trong cả C99 và C11 nói rằng:

Ràng buộc:

Biểu thức xác định giá trị của hằng số liệt kê phải là một biểu thức hằng số nguyên có giá trị có thể biểu diễn dưới dạng an int.

Chẩn đoán trình biên dịch là bắt buộc vì nó là một vi phạm ràng buộc, xem 5.1.1.3:

Việc triển khai tuân thủ sẽ tạo ra ít nhất một thông báo chẩn đoán (được xác định theo cách được triển khai xác định) nếu một đơn vị dịch tiền xử lý hoặc đơn vị dịch có vi phạm bất kỳ quy tắc hoặc ràng buộc cú pháp nào, ngay cả khi hành vi cũng được chỉ định rõ ràng là không xác định hoặc triển khai- được xác định.


23

Trong C , trong khi a enumđược coi là một kiểu riêng biệt, thì bản thân các điều tra viên luôn có kiểu int.

C11 - 6.7.2.2 Các chỉ số liệt kê

3 Các định danh trong danh sách liệt kê được khai báo là hằng số có kiểu int ...

Do đó, hành vi bạn thấy là một phần mở rộng trình biên dịch.

Tôi muốn nói rằng chỉ nên mở rộng kích thước của một trong các điều tra viên nếu giá trị của nó quá lớn.


Mặt khác, trong C ++, tất cả các điều tra viên đều có kiểu enummà chúng được khai báo.

Do đó, quy mô của mọi điều tra viên phải như nhau. Vì vậy, kích thước của toàn bộ enumđược mở rộng để lưu trữ điều tra viên lớn nhất.


11
Nó là một phần mở rộng của trình biên dịch, nhưng không thể tạo chẩn đoán là một lỗi không tuân thủ.
Ben Voigt

16

Như những người khác đã chỉ ra, mã không hợp lệ (trong C), vì vi phạm ràng buộc.

Có lỗi GCC # 71613 (được báo cáo vào tháng 6 năm 2016), cho biết rằng một số cảnh báo hữu ích bị tắt tiếng với macro.

Cảnh báo hữu ích dường như bị tắt tiếng khi macro từ tiêu đề hệ thống được sử dụng. Ví dụ, trong ví dụ dưới đây, một cảnh báo sẽ hữu ích cho cả hai môi trường nhưng chỉ một cảnh báo được hiển thị. Điều tương tự có thể xảy ra đối với các cảnh báo khác.

Cách giải quyết hiện tại có thể là thêm macro với toán +tử một ngôi:

enum en_e {
   en_e_foo,
   en_e_bar = +UINT64_MAX,
};

dẫn đến lỗi biên dịch trên máy của tôi với GCC 4.9.2:

$ gcc -std=c11 -pedantic-errors -Wall main.c 
main.c: In function ‘main’:
main.c:9:20: error: ISO C restricts enumerator values to range ofint’ [-Wpedantic]
         en_e_bar = +UINT64_MAX

12

C11 - 6.7.2.2/2

Biểu thức xác định giá trị của hằng số liệt kê phải là biểu thức hằng số nguyên có giá trị có thể biểu diễn dưới dạng an int.

en_e_bar=UINT64_MAXlà một vi phạm ràng buộc và điều này làm cho mã trên không hợp lệ. Một thông báo chẩn đoán phải được tạo ra bằng cách xác nhận việc triển khai như đã nêu trong dự thảo C11:

Việc triển khai tuân thủ sẽ tạo ra ít nhất một thông báo chẩn đoán (được xác định theo cách thức do triển khai xác định) nếu một đơn vị dịch tiền xử lý hoặc đơn vị dịch có vi phạm bất kỳ quy tắc hoặc ràng buộc cú pháp nào, [...]

Có vẻ như GCC có một số lỗi và nó không tạo được thông báo chẩn đoán. (Lỗi được chỉ ra trong câu trả lời của Grzegorz Szpetkowski


8
"hành vi không xác định" là một hiệu ứng thời gian chạy. sizeoflà một toán tử thời gian biên dịch. Không có UB ở đây, và ngay cả khi có, nó cũng không thể ảnh hưởng sizeof.
Ben Voigt

2
Bạn nên tìm câu trích dẫn tiêu chuẩn mà người điều tra không thể phù hợp với int là UB. Tôi rất nghi ngờ tuyên bố đó và phiếu bầu của tôi sẽ duy trì ở mức -1 cho đến khi điều này được làm sáng tỏ.
zneak

3
@Sergey: Tiêu chuẩn C thực sự nói rằng "Biểu thức xác định giá trị của một hằng số liệt kê sẽ là một biểu thức hằng số nguyên có giá trị có thể biểu diễn dưới dạng int." nhưng vi phạm điều này sẽ là vi phạm ràng buộc, yêu cầu chẩn đoán, không phải UB.
Ben Voigt

3
@haccks: Vâng? Đó là vi phạm ràng buộc và "Việc triển khai tuân thủ sẽ tạo ra ít nhất một thông báo chẩn đoán (nhận dạng theo cách thực thi) nếu đơn vị dịch tiền xử lý hoặc đơn vị dịch có vi phạm bất kỳ quy tắc cú pháp hoặc ràng buộc nào, ngay cả khi hành vi đó cũng xác định rõ ràng là unde fi ned hoặc implement-de fi ned. "
Ben Voigt

2
Có một sự khác biệt giữa tràn và cắt ngắn. Tràn là khi bạn có một phép toán số học tạo ra giá trị quá lớn cho loại kết quả mong đợi và tràn có dấu là UB. Cắt ngắn là khi bạn có một giá trị quá lớn đối với loại mục tiêu bắt đầu bằng (như short s = 0xdeadbeef) và hành vi được xác định bởi việc triển khai.
zneak

5

Tôi đã xem xét các tiêu chuẩn và chương trình của tôi dường như vi phạm ràng buộc trong C vì 6.7.2.2p2 :

Ràng buộc: Biểu thức xác định giá trị của một hằng số phải là một biểu thức hằng số nguyên có giá trị có thể biểu diễn dưới dạng int.

và được định nghĩa trong C ++ vì 7.2.5:

Nếu kiểu cơ bản không cố định, kiểu của mỗi liệt kê là kiểu giá trị khởi tạo của nó: - Nếu bộ khởi tạo được chỉ định cho một điều tra viên thì giá trị khởi tạo có cùng kiểu với biểu thức và hằng số biểu thức sẽ là một tích phân biểu thức hằng số (5.19). - Nếu không có bộ khởi tạo nào được chỉ định cho điều tra viên đầu tiên thì giá trị khởi tạo có kiểu tích phân không xác định. - Nếu không thì kiểu của giá trị khởi tạo giống với kiểu của giá trị khởi tạo của điều tra viên trước trừ khi giá trị gia tăng không thể biểu diễn trong kiểu đó, trong trường hợp đó kiểu này là kiểu tích phân không xác định đủ để chứa giá trị gia tăng. Nếu không có loại nào như vậy tồn tại, chương trình không hợp lệ.


3
Nó không phải là "không xác định" trong C, mà là "không hợp thức" vì một ràng buộc bị vi phạm. Trình biên dịch PHẢI tạo chẩn đoán liên quan đến vi phạm.
Ben Voigt

@BenVoigt Cảm ơn bạn đã dạy tôi về sự khác biệt. Đã sửa nó trong câu trả lời (mà tôi đã thực hiện vì tôi bỏ sót một trích dẫn từ tiêu chuẩn C ++ trong các câu trả lời khác).
PSkocik
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.