如何在GCC中指定枚举大小?

我想为枚举指定64位的枚举大小。 这怎么可能通过海湾合作委员会? 代码不需要是“可移植的”,因为我只对将代码用于x86-32和x86-64 Linux的GCC编译感兴趣。 这意味着任何可以提供我想要的function的黑客都可以,只要它适用于那些目标。

鉴于此代码:

#include  #include  enum some_enum { garbage1, garbage2 }; int main(void) { enum some_enum some_val; printf("size: %lu\n", sizeof(some_val)); return EXIT_SUCCESS; } 

这当前打印出4,而我希望能够强制大小为8.尝试在枚举赋值中指定大于4个字节的值会导致警告。 例如,

 enum some_enum { garbage1 = '12345', garbage2 }; 

会产生:

 warning: character constant too long for its type [enabled by default] 

这里对类似问题的回答似乎没有产生任何好结果。 也就是说,由于以下原因产生了相同的警告:

 enum some_enum { garbage1 = 'adfs', garbage2 = 'asdfasdf' }; 

注意:可以通过使用-Wno-multichar进行编译来关闭多字符警告。


合理

由于人们对我为什么这样做感兴趣,我写了一个反汇编引擎。 我将指令的每个部分都作为字符串。 所以我希望枚举看起来像这样:

 enum mnemonic { mov = 'mov', cmp = 'cmp', sysenter = 'sysenter' }; 

然后,我可以使用以下代码轻松存储语义信息:

 enum mnemonic insn; char * example_insn = "mov"; uint64_t buf = 0; strncpy((char *)&buf, example_insn, sizeof(uint64_t)); 

如果buf是一个enum mnemonic那么我们不需要做任何其他事情。 strncpy用于将字符串结尾后的字节填充为空字符。 如果我无法做到这一点,我将不得不这样做:

 if(strcmp(example_insn, "mov") == 0) { insn = mov; } else if(strcmp(example_insn, "cmp") == 0) { insn = cmp; } ... 

由于这个例程将被打击数百万次,这种优化将产生巨大的差异。 我打算对寄存器等操作数做同样的事情。

你错误地解释了警告,它说的是字符文字总是int类型,永远不是longlong long类型。

你可以逃避这样的事情:

 enum foo { garbage1 = (long long)'1' << 32 | (long long)'2' << 24 | (long long)'3' << 16 | (long long)'4' << 8 | (long long)'5', garbage2 }; 

但是如果你想使用strncpy解决方案,你还是要小心使用小端,确保使用正确的移位数。

正如Matteo Italia的回答所说,gcc允许您通过为其中一个成员指定64位值来定义64位枚举类型。 例如:

 enum some_enum { /* ... */ max = 0x7fffffffffffffff }; 

至于你对'mov''cmp'等的使用,像"mov"这样的字符串文字的表示与像'mov'这样的多字符字符常量的表示之间没有必要的关联。

后者是合法的(并由gcc支持),但该值是实现定义的。 标准说类型总是int ,而gcc似乎没有可以覆盖它的扩展名。 因此,如果int是4个字节,那么'sysenter' ,如果它完全被接受,则不一定具有您正在寻找的值。 gcc似乎忽略了这个常量的低位字节。 对于big-endian和little-endian系统,常量的值似乎是一致的 – 这意味着它不会始终匹配类似字符串文字的表示。

例如,这个程序:

 #include  int main(void) { const char *s1 = "abcd"; const char *s2 = "abcdefgh"; printf("'abcd' = 0x%x\n", (unsigned)'abcd'); printf("'abcdefgh' = 0x%x\n", (unsigned)'abcdefgh'); printf("*(unsigned*)s1 = 0x%x\n", *(unsigned*)s1); printf("*(unsigned*)s2 = 0x%x\n", *(unsigned*)s2); return 0; } 

在little-endian系统(x86)上使用gcc编译时产生此输出:

 'abcd' = 0x61626364 'abcdefgh' = 0x65666768 *(unsigned*)s1 = 0x64636261 *(unsigned*)s2 = 0x64636261 

这个输出在big-endian系统(SPARC)上:

 'abcd' = 0x61626364 'abcdefgh' = 0x65666768 *(unsigned*)s1 = 0x61626364 *(unsigned*)s2 = 0x61626364 

所以我担心你想要将像'mov'这样'mov'字符常量与像'mov'这样'mov'字符串匹配是不行的。 (可以想象你可以将字符串表示规范化为big-endian,但我不会自己采用这种方法。)

您尝试解决的问题是将"mov"等字符串快速映射到表示CPU指令的特定整数值。 你是对的, strcmp()调用的长序列效率低下(你实际测量它并发现速度是不可接受的吗?) – 但还有更好的方法。 某种哈希表可能是最好的。 有一些工具可以生成完美的哈希函数,因此对字符串值的相对便宜的计算会给出一个唯一的整数值。

您将无法非常方便地编写枚举值的定义,但是一旦您拥有正确的哈希函数,您就可以编写一个程序来生成枚举类型的C源代码。

这是假设枚举是这里最好的方法; 它可能不是。 如果我这样做,中央数据结构将是结构的集合,其中每个结构包含运算符的字符串名称以及与之关联的任何其他信息。 哈希函数会将像"mov"这样的字符串映射到此集合中的索引。 (我故意模糊地使用什么样的“集合”;使用正确的散列函数,它可能是一个简单的数组。)有了这种解决方案,我认为不需要64位枚举类型。

您可以使用union类型:

 union some { enum { garbage1, garbage2 } a; int64_t dummy; }; 

虽然C99标准规定枚举不能基于int (§6.7.2.2¶2) 1之外的 任何东西 ,但似乎gcc遵循C ++的观点,即如果enum的值大于int ,它可以基于更大的整数类型。 我对此代码没有任何问题,在x64上也没有x86:

 enum myEnum { a=1234567891234567890LL }; int main() { enum myEnum e; printf("%u %u", sizeof(void *), sizeof(e)); return 0; } 

在x86上我得到了

 4 8 

在x64(在我的机器上)我得到

 8 8 

虽然,要求对标准的迂腐尊重,我得到了,如预期的那样:

 matteo@teodeb:~/cpp$ gcc -ansi -pedantic testenum.c testenum.c:5:7: warning: use of C99 long long integer constant testenum.c:5: warning: ISO C restricts enumerator values to range of 'int' 

  1. 实际上,它有点复杂; ¶4指定实现可以自由选择“基本类型”任何“与char兼容,有符号整数类型或无符号整数类型”的特定类型,只要它可以表示enum所有元素。

    另一方面,¶2指定enum每个成员必须可以表示为int ,因此,即使实现可以自由地将你的enum建立在大量位整数的基础上,为它定义的常量也不能是任何可以’用int表示。 因此,这意味着在实践中 ,编译器不会将enum基于大于int任何内容,但如果您的值不需要整个int范围,它可能会基于较小的值。

感谢@ jons34yp指出我最初的错误。

Per Johansson在这里给出了答案。 作为如何使用这种技术的具体例子,我编写了这个程序( insn_enum.c ):

 #include  #include  #include  #include  enum insn { /* * Have the characters backwards because C treats the value as an * integer (of size 64 bits in this case). There is no need for * a null terminator since we are treating the values as an integer, * not a string. */ sysenter = (uint64_t)'r' << 56 | (uint64_t)'e' << 48 | (uint64_t)'t' << 40 | (uint64_t)'n' << 32 | (uint64_t)'e' << 24 | (uint64_t)'s' << 16 | (uint64_t)'y' << 8 | (uint64_t)'s', }; int main(void) { enum insn some_insn = sysenter; char * insn = "sysenter"; uint64_t val = 0; /* * We can optimise this by traversing backwards (little endian) setting * 0 till a NULL char is found, although I will not bother implementing * this till I have done some profiling. */ strncpy((char * )&val, insn, sizeof(uint64_t)); printf("size: %" PRIuPTR"\n", sizeof(enum insn)); if(some_insn == val) { puts("Works"); } else { puts("Doesn't work"); } return EXIT_SUCCESS; } 

这可以使用以下makefile

 all: gcc -std=gnu99 -m32 -Wall insn_enum.c -o insn_enum_32 gcc -std=gnu99 -m64 -Wall insn_enum.c -o insn_enum_64 clean: rm -f insn_enum_32 rm -f insn_enum_64 

使用./insn_enum_32 && ./insn_enum_64运行将打印:

 size: 8 Works size: 8 Works 

应该注意的是,这只表明我们可以在x86-32和x86-64(我打算定位的唯一两个平台)上使用这个技巧。 事实上,由于语言将enum视为整数值,因此保证不会在大端系统上运行此技巧。 此外,我不确定我们是否可以保证编译器必须使用uint64_t作为enum的大小,即使我们按照我们的方式指定它也是如此。 确实用-pedantic编译会发出警告:

 gcc -std=gnu99 -m32 -pedantic -Wall insn_enum.c -o insn_enum_32 insn_enum.c:13:13: warning: ISO C restricts enumerator values to range of 'int' gcc -std=gnu99 -m64 -pedantic -Wall insn_enum.c -o insn_enum_64 insn_enum.c:13:13: warning: ISO C restricts enumerator values to range of 'int' 

只是回答标题中的原始问题 – C ++ 11允许您指定枚举的类型,因此它的大小:

 enum class mynamedenum : long { FOO, BAR } 

Perhas你可以使用定义吗?

 #define GARBAGE1 12345L #define GARBAGE2 67890L 

我不认为你可以使用不同于最佳的枚举的枚举。

也许试试:

 enum { garbage1, garbage2, sentinel = 12345L } 

看到了吗?

您最好的选择可能是使用构建系统自动生成一组定义。 这样,你也可以获得正确的结束。

示例程序gen-instructions可能如下所示

 #include  #include  #include  int main(int argc, char *argv[]) { for(int i = 1; i < argc; ++i) { uint64_t value; strncpy((char *)&value, argv[i], sizeof value); printf("#define %s 0x%.16" PRIX64 "\n", argv[i], value); } return 0; } 

使用相应的makefile规则

 instructions.h : instructions.list gen-instructions ./gen-instructions `cat $<` > $@