簡體   English   中英

是枚舉 { a } e = 1; 有效的?

[英]Is enum { a } e = 1; valid?

一個簡單的問題:是enum { a } e = 1; 有效的?

換句話說:分配一個不存在於枚舉常量值集中的值會導致明確定義的行為嗎?

演示:

$ gcc t0.c -std=c11 -pedantic -Wall -Wextra -c
<nothing>

$ clang t0.c -std=c11 -pedantic -Wall -Wextra -c
<nothing>

$ icc t0.c -std=c11 -pedantic -Wall -Wextra -c
t0.c(1): warning #188: enumerated type mixed with another type
# note: the same warning for enum { a } e = 0;

$ cl t0.c /std:c11 /Za /c
<nothing>

根據 6.7.2.2 中的 C18 標准:

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.

所以是enum { a } e = 1; 已驗證。 e是一個“整數”類型,因此它可以取值1 1不作為枚舉值存在的事實沒有問題。 枚舉成員只為一些可能的值提供方便的標識符。

簡而言之

enum { a } e = 1; 基於枚舉類型的定義、兼容類型的轉換規則和兼容性保證,確保特定值1是不同原因的組合。

劇透警報:在 C18 中,它比任何單個段落的引用都要復雜得多(請參閱我在接受的答案下的評論)。

完整解釋

我將逐步引用 C18 標准(准確地說是 N2176)。 亮點來自我。

首先,我們知道enum { a }是一個 integer 類型:

6.2.5. 類型
16:枚舉包含一組命名為 integer 的常量值。 每個不同的枚舉構成不同的枚舉類型
17:char類型,signed和unsigned integer類型,枚舉類型統稱為integer類型

然后,我們可以證明如果enum { a }是一個 integer 類型“足夠大”以容納1 ,賦值將有效(因為沒有明確的相反聲明):

6.2.7 兼容類型和復合類型
1:如果類型相同,則兩種類型具有兼容類型 (...)

6.3. 轉換
2:除非另有明確說明,否則將操作數值轉換為兼容類型不會導致值或表示發生變化

6.7.2.2 枚舉說明符
4:每個枚舉類型都應與char、有符號 integer 類型或無符號 integer 類型兼容。 類型的選擇是實現定義的,但應該能夠表示枚舉的所有成員的值。

但是類型足夠大嗎? 從這里我們推導出a的值為0 由於它也是集合中最大的枚舉常量,我們可以推斷出enum { a } “至少”與char兼容:

6.7.2.2 枚舉說明符
3: (...) 帶有 = 的枚舉器將其枚舉常量定義為常量表達式的值。 如果第一個枚舉器沒有=,則其枚舉常量的值為0。隨后每個沒有=的枚舉器都將其枚舉常量定義為前一個枚舉常量的值加1所得到的常量表達式的值。

總之,賦值是合法的,因為文字1也與char兼容。

當心依賴於實現的假設!

但要小心,這樣的 integer 分配不一定對您選擇的任何 integer 有效:

enum { a } = INT_MAX;  // Implementation dependent, see J.3.9 
enum { a, b=INT_MAX } = 1024;  // ok: b requires int as compatible type  

在第一種情況下,枚舉只保證與char兼容。 但我們不能保證它與int兼容:

J.3。 實現定義的行為
(...) **J.3.9。 結構、聯合、枚舉和位域
(...)

  • integer 類型兼容每個枚舉類型(6.7.2.2)。

在第二種情況下,我們使用 inteer 值的規范來強制與int兼容的類型。

我在C11 標准草案中找不到任何引用(就“約束”而言),它禁止分配給像你這樣的enum類型(並且在這個其他答案中引用的 C18 標准使用基本相同的措辭)。

但是,C11 草案在附件 I – 常見警告中確實提供了這一點:

1 在許多情況下,實施可能會產生警告,但本國際標准均未將這些警告指定為部分。 以下是一些比較常見的情況。

2

— 為枚舉類型的 object 賦予值,而不是通過分配作為該類型成員的枚舉常量,或具有相同類型的枚舉 object,或返回相同類型的 ZC1C425268E68385D1AB507 的值(6.7.2.2)。

但是這個建議的警告同樣適用於enum { a } e = 0;這樣的賦值。 ,其中 RHS 的是該類型的有效枚舉常量,但 RHS 不是精確類型

在 C 中有效,在 C++14/C++17 中實現定義和可能的未指定/未定義行為

雖然根據例如202x 工作草案,這在 C 中有效:

6.7.2.2/4每個枚舉類型應兼容char、有符號 integer 類型或無符號 integer 類型。 類型的選擇是實現定義的,但應該能夠表示枚舉的所有成員的值

由於更強的“兼容類型”要求,有趣的是指出 C++ 並不適用,其中沒有固定基礎類型的“C 風格”無作用域枚舉與 C 有細微差別:這是一個關鍵。

如果我們考慮在 C++ 中語法上有效的類似示例:

// C++ does not allow implicit conversion _from_ an integer type
// _to_ an unscoped enum with no explicit underlying type, so we'll
// need to resort to explicit conversions for a similar example
enum E { a } e = static_cast<E>(2);  // #1

// or ...
enum E { a } e = E(2);

根據[dcl.enum]/7

對於基礎類型固定的枚舉,枚舉的值是基礎類型的值。 否則,枚舉的值是可以由具有最小寬度 M 的假設 integer 類型表示的值,以便可以表示所有枚舉數。 足以容納枚舉類型的所有值的最小位域的寬度是 M。可以定義一個枚舉,其值未由其任何枚舉器定義。 如果 enumerator-list 為空,則枚舉的值就好像該枚舉有一個值為 0 的枚舉數。

[expr.static.cast]/10

整數或枚舉類型的值可以顯式轉換為完整的枚舉類型。 如果枚舉類型具有固定的基礎類型,則值首先通過整數轉換轉換為該類型,如有必要,然后再轉換為枚舉類型。 如果枚舉類型沒有固定的底層類型,則如果原始值在枚舉值([dcl.enum])的范圍內,則值不變,否則行為未定義。 浮點類型的值也可以顯式轉換為枚舉類型。 結果值與將原始值轉換為枚舉的基礎類型([conv.fpint])並隨后轉換為枚舉類型相同。

上面的#2 可能(取決於E的實現定義的底層類型)導致未定義的行為(在 C++17 通過CWG 1766之前一直是未指定的行為),從概念上講可能導致簽名的 integer 溢出(即 UB)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM