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

时间:2012-03-01 21:25:20

标签: c gcc enums

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

鉴于此代码:

#include <stdlib.h>
#include <stdio.h>

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]

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

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));

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

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

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

9 个答案:

答案 0 :(得分:8)

正如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 <stdio.h>
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;
}
在小端系统(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"这样的字符串匹配是不行的。 (可以想象你可以将字符串表示规范化为big-endian,但我不会自己采用这种方法。)

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

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

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

答案 1 :(得分:6)

您可以使用union类型:

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

答案 2 :(得分:4)

虽然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;
}

on 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上gazillion位整数,为它定义的常量不能是enum无法表示的任何东西。因此,这意味着在实践中编译器不会将int基于大于int的任何内容,但可能基于它如果您的值不需要{{1}}的全部范围,则更小。

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

答案 3 :(得分:4)

你误解了警告,它说的是字符文字总是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解决方案,你还是要小心使用小端,确保使用正确的移位数。

答案 4 :(得分:1)

Per Johansson用他的回答here击中了头部。作为如何使用这种技术的具体例子,我写了这个程序(insn_enum.c):

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>

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’

答案 5 :(得分:1)

回答标题中的原始问题 - C ++ 11允许您指定枚举的类型,从而指定其大小:

enum class mynamedenum : long {
  FOO,
  BAR
}

答案 6 :(得分:0)

Perhas你可以使用定义吗?

#define GARBAGE1 12345L
#define GARBAGE2 67890L

我认为你不能使用不同于最佳状态的枚举。

也许试试:

enum
{
 garbage1,
 garbage2,
 sentinel = 12345L
}

看看?

答案 7 :(得分:0)

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

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

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

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 $<` > $@

答案 8 :(得分:0)

尚无官方指定枚举大小的方法。也许不是100%适用于您的案例,但是使用此技巧,您可以实现跨平台的目标枚举大小。您应该将关键字__attribute__与参数packed一起使用,以将枚举大小缩小到枚举列表中的最大值。

只需看下面的示例:

#include <stdio.h>

typedef enum  __attribute__((packed))
{
  MY_E_8_BYTES_MAX = 0xFFFFFFFFFFFFFFFF,
} en_8b_t;

typedef enum  __attribute__((packed))
{
  MY_E_4_BYTES_MAX = 0xFFFFFFFF,
} en_4b_t;

typedef enum  __attribute__((packed))
{
  MY_E_2_BYTES_MAX = 0xFFFF,
} en_2b_t;

typedef enum  __attribute__((packed))
{
  MY_E_1_BYTE_MAX = 0xFF,
} en_1b_t;

typedef enum
{
  MY_E_X_BYTES_DEFAULT = 0,
} en_xb_t;

int main(char ** argc, int argv)
{
  printf("Sizeof en_8b_t: %lu\n", sizeof(en_8b_t));
  printf("Sizeof en_4b_t: %lu\n", sizeof(en_4b_t));
  printf("Sizeof en_2b_t: %lu\n", sizeof(en_2b_t));
  printf("Sizeof en_1b_t: %lu\n", sizeof(en_1b_t));
  printf("Default enum size is: %lu\n", sizeof(en_xb_t));

  return 0;
}

输出如下:

Sizeof en_8b_t: 8
Sizeof en_4b_t: 4
Sizeof en_2b_t: 2
Sizeof en_1b_t: 1
Default enum size is: 4

默认枚举大小取决于您的编译器。 有关更多属性,请查看here