Почему две переменные перечисления перечисления имеют одно и то же целочисленное значение?
Я знаю, что если бы я определил день недели перечисления следующим образом:
enum weekday {
MON,
TUE,
WED,
THU,
FRI,
};
Затем MON по умолчанию будет 0, а TUE - 1, WED - 2...
Но если я определяю его так:
enum weekday {
MON,
TUE = 0,
WED,
THU,
FRI,
};
Тогда оба MON
и TUE
получат значение 0.
Как система будет дифференцировать MON и TUE внутри? Я имею в виду, если я объявлю что-то вроде этого:
enum weekday today = 0;
Тогда есть сегодня MON
или TUE
? Или, философски говоря, оба?
Ответы
Ответ 1
C перечисления являются "действительно" целыми числами - не только потому, что они реализуются таким образом, а потому, что стандарт определяет типы перечислений, чтобы иметь целочисленные значения. Таким образом, значение today
"действительно" 0. Все, что произошло, это то, что вы создали два разных имени для значения 0.
Я полагаю, что ответ "сегодня MON или TUE" - "да"; -)
Язык не останавливает вас, потому что иногда полезно для перечисления иметь несколько имен для одного и того же значения. Например:
enum compression_method {
COMP_NONE = 0,
COMP_LOW = 1,
COMP_HIGH = 2,
COMP_BEST = 2,
COMP_FASTEST = 0,
};
Ответ 2
Почему две разные константы перечисления имеют одно и то же целочисленное значение?
Поскольку это явно разрешено стандартным проектом N1265 C99 в 6.7.2.2/3 "Спецификаторы перечисления":
Использование счетчиков с =
может приводить к константам перечисления со значениями, которые дублируют другие значения в том же перечислении.
Как система будет дифференцировать MON и TUE внутри?
Я думаю, что это невозможно, потому что они являются константами времени компиляции (6.6/6 "Константные выражения" ). В результате они:
-
не могут быть изменены, чтобы они отличались после компиляции
-
не имеют адреса, чтобы рассказать им обособленно: Место хранения значения перечисления в C
Константы времени компиляции не нуждаются в каком-либо адресе, потому что адреса бесполезны для вещей, которые вы не можете изменить.
GCC просто заменяет использование членов перечисления непосредственными значениями в сборке во время компиляции. Рассмотрим:
#include <stdio.h>
enum E {
E0 = 0x1234,
E1 = 0x1234
};
int i = 0x5678;
int main() {
printf("%d\n", E0);
printf("%d\n", E1);
printf("%d\n", i);
return 0;
}
Скомпилировать и декомпилировать с помощью GCC 4.8 x86_64:
gcc -c -g -O0 -std=c89 main.c
objdump -Sr main.o
Вывод содержит:
printf("%d\n", E0);
4: be 34 12 00 00 mov $0x1234,%esi
...
printf("%d\n", E1);
18: be 34 12 00 00 mov $0x1234,%esi
...
printf("%d\n", i);
2c: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 32 <main+0x32>
2e: R_X86_64_PC32 i-0x4
32: 89 c6 mov %eax,%esi
Итак, мы видим, что:
- члены перечисления используются как непосредственные
$0x1234
, поэтому невозможно узнать, откуда они пришли из
- переменная
i
, однако, поступает из памяти 0x0(%rip)
(для перемещения), поэтому две переменные могут быть дифференцированы по адресу
Ответ 3
Чтобы дополнить другие ответы, я дам вам практический пример того, как использование одного и того же значения для разных перечислений в заданном enum
является весьма полезным:
enum slots_t {
SLOT_FIRST = 0,
SLOT_LEFTARM = SLOT_FIRST,
SLOT_RIGHTARM = 1,
SLOT_TORSO = 2,
SLOT_LEFTLEG = 3,
SLOT_RIGHTLEG = 4,
SLOT_LAST = SLOT_RIGHTLEG
};
Затем вы можете сделать в своем коде:
for (int i = SLOT_FIRST; i <= SLOT_LAST; ++i) { }
Ответ 4
Это как философское (или нет) как
#define ZILCH 0
#define NADA 0
Существует много применений, когда имеет смысл иметь разные имена в том же количестве.
Ответ 5
Имя константы перечисления используется для назначения значения, а не самого фактического значения. Если вы присвоите значение 0 сегодня, выходное значение будет равно 0.
И да, как MON, так и TUE будут иметь значение 0, а оставшиеся будут назначены значение WED = 1 THU = 2 и т.д.