【问题标题】:Specifying size of enum type in C在 C 中指定枚举类型的大小
【发布时间】:2011-06-20 05:46:06
【问题描述】:

已阅读this related question,但正在寻找更具体的内容。

  • 有没有办法明确告诉编译器您希望枚举的宽度是多少?
  • 如果是这样,你是怎么做的?我知道如何在 C# 中指定它;是否在 C 中类似地完成?
  • 这值得吗?当枚举值传递给函数时,不管它是否会作为int大小的值传递?

【问题讨论】:

    标签: c optimization enums word-size


    【解决方案1】:

    如果您使用 GCC,我相信会有一个标志。

    -fshort-enums

    【讨论】:

    • +1 酷...这就是我一直在寻找的强制编译器。
    • 或者选择性地enum __attribute__ ((__packed__)) my_enum {...};
    • @Nikolai N Fetissov:也会尝试一下。我知道你可以在struct 上使用__packed__ 属性;不知道你也可以在enum 上使用它。
    • 这里有更多的见解。 stackoverflow.com/a/54527229/4561887
    【解决方案2】:

    有没有办法告诉你的编译器 特别是你想要你的宽度 枚举是?

    一般情况下没有。不在标准 C 中。

    这值得吗?

    这取决于上下文。如果您正在谈论将参数传递给函数,那么不,不值得这样做(见下文)。如果它是关于在从枚举类型构建聚合时节省内存,那么它可能值得做。但是,在 C 中,您可以简单地在聚合中使用适当大小的整数类型而不是枚举类型。在 C(相对于 C++)中,枚举类型和整数类型几乎总是可以互换的。

    当枚举值传递给函数时,不管它是否会作为int大小的值传递?

    如今,许多(大多数)编译器将所有参数作为给定硬件平台的自然字长值传递。例如,在 64 位平台上,许多编译器会将所有参数作为 64 位值传递,而不管它们的实际大小,即使类型 int 在该平台上具有 32 位(因此,它通常不会作为在这样的平台上的“int-sized”值)。因此,尝试优化枚举大小以用于参数传递是没有意义的。

    【讨论】:

    • “如果您正在谈论将参数传递给函数,那么不,不值得这样做(见下文)”并非总是如此。如果您使用 8 位处理器,自然大小为 8 位,但 int 和枚举至少为 16 位(没有 -fshort-enums),因此您至少需要 2 个可以减慢函数调用的寄存器。
    【解决方案3】:

    您可以通过定义一个适当的值来强制它至少达到一定的大小。例如,如果您希望将枚举存储为与int 相同的大小,即使所有值都适合char,您可以执行以下操作:

    typedef enum {
        firstValue = 1,
        secondValue = 2,
    
        Internal_ForceMyEnumIntSize = MAX_INT
    } MyEnum;
    

    但请注意,行为可能取决于实现。

    正如您所注意到的,将这样的值传递给函数会导致它被扩展为 int,但是如果您在数组或结构中使用您的类型,那么大小将很重要。如果你真的很在意元素的大小,你真的应该使用 int8_tint32_t 等类型。

    【讨论】:

    • 我是这么想的。我倾向于定义一些实际上不需要超过 8 或 16 位的枚举,而且我讨厌在不需要时浪费空间。
    • 如果优化器删除了您未使用的Internal_ForceMyEnumIntSize,这可能不起作用
    • @KJohnson 是 Internal_ForceMyEnumIntSize 是 C 关键字吗?
    • @Patrick 不,它只是枚举的另一个成员。它可能被称为thirdValue、lastValue 或maxValue。 MAX_INT 由 C 定义为最大 int 值。
    • @EBlake C99 says 表示该类型“应能够表示枚举中所有成员的值”。它没有说明允许优化器删除成员,所以我看不出这是如何被允许的。我也看不出这样做有什么意义?有什么收获?枚举器只是命名整数常量的替代方法;如果代码中未使用枚举器成员,则它肯定不会出现在二进制文件中,因此无需删除,无需优化。
    【解决方案4】:

    如果枚举是结构的一部分,还有另一种方法:

    struct something {
         :0;
       enum whatever field:CHAR_BIT;
         :0;
    };
    

    :0;如果枚举字段被普通字段包围,则可以省略。如果之前有另一个位域,则 :0 将强制字节对齐到其后域的下一个字节。

    【讨论】:

    • 远程也不行;我在gcc 中找不到的任何标志排列都不会编译它,只给出error: expected specifier-qualifier-list before ‘:’ token。零大小的位域是一个东西,它们必须是未命名的,但它们仍然需要有一个类型。我假设一个零大小/无名的位域意味着“在下一个分配单元的开头开始下一个非零大小的位域”,其中它的类型指定要对齐的单元。我想这会引发为什么它需要一个类型的问题,当以下非零大小的位域有一个时,但你可能需要一台时间机器来询问委员会
    • 这应该被标记为正确的解决方案。所有其他答案都可以优化掉,而使用位域是有保证的。
    • 我不认为这可以用来定义一个可以在结构外使用的枚举,对吗?
    • 没有。仅在 struct 内部,因为它是位集语法的属性。
    【解决方案5】:

    在某些情况下,这可能会有所帮助:

    typedef uint8_t command_t;
    enum command_enum
    {
        CMD_IDENT                = 0x00,     //!< Identify command
        CMD_SCENE_0              = 0x10,     //!< Recall Scene 0 command
        CMD_SCENE_1              = 0x11,     //!< Recall Scene 1 command
        CMD_SCENE_2              = 0x12,     //!< Recall Scene 2 command
    };
    
    /* cmdVariable is of size 8 */
    command_t cmdVariable = CMD_IDENT; 
    

    一方面,command_t 的大小为 8,可用于变量和函数参数类型。 另一方面,默认情况下,您可以使用 int 类型的枚举值进行赋值,但编译器会在分配给 command_t 类型变量时立即将它们强制转换。

    另外,如果你做了一些不安全的事情,比如定义和使用CMD_16bit = 0xFFFF,,编译器会警告你以下消息:

    警告:大整数隐式截断为无符号类型 [-Woverflow]

    【讨论】:

      【解决方案6】:

      即使您正在编写严格的C 代码,结果也将取决于编译器。使用这个线程中的策略,我得到了一些有趣的结果......

      enum_size.c

      #include <stdio.h>
      
      enum __attribute__((__packed__)) PackedFlags {
          PACKED = 0b00000001,
      };
      
      enum UnpackedFlags {
          UNPACKED = 0b00000001,
      };
      
      int main (int argc, char * argv[]) {
        printf("packed:\t\t%lu\n", sizeof(PACKED));
        printf("unpacked:\t%lu\n", sizeof(UNPACKED));
        return 0;
      }
      
      $ gcc enum_size.c
      $ ./a.out
      packed:         4
      unpacked:       4
      
      $ gcc enum_size.c -fshort_enums
      $ ./a.out
      packed:         4
      unpacked:       4
      
      $ g++ enum_size.c
      $ ./a.out
      packed:         1
      unpacked:       4
      
      $ g++ enum_size.c -fshort_enums
      $ ./a.out
      packed:         1
      unpacked:       1
      

      在我上面的例子中,直到我开始使用 C++ 编译器,我才意识到 __attribute__((__packed__)) 修饰符有什么好处。

      编辑:

      @technosaurus 的怀疑是正确的。

      通过检查 sizeof(enum PackedFlags) 而不是 sizeof(PACKED) 的大小,我看到了预期的结果...

        printf("packed:\t\t%lu\n", sizeof(enum PackedFlags));
        printf("unpacked:\t%lu\n", sizeof(enum UnpackedFlags));
      

      我现在看到来自gcc 的预期结果:

      $ gcc enum_size.c
      $ ./a.out
      packed:         1
      unpacked:       4
      
      $ gcc enum_size.c -fshort_enums
      $ ./a.out
      packed:         1
      unpacked:       1
      

      【讨论】:

      • 尝试sizeof (enum PackedFlags),这样枚举就不会被扩展。使用 sizeof( 'a' ) 会有类似的体验
      • @technosaurus 你是对的!为什么 C 编译器而不是 C++ 编译器会发生这种情况。
      【解决方案7】:

      作为@Nyx0uf says,GCC 有一个可以设置的标志:

      -fshort-enums

      只为枚举类型分配声明的可能值范围所需的字节数。具体来说,枚举类型相当于有足够空间的最小整数类型。

      警告:-fshort-enums 开关导致 GCC 生成的代码与没有该开关生成的代码二进制不兼容。使用它来符合非默认应用程序二进制接口。

      来源:https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

      其他精彩阅读,以获得一般见解: https://www.embedded.fm/blog/2016/6/28/how-big-is-an-enum
      有趣...请注意我在下面以黄色突出显示的行!添加一个名为ARM_EXCEPTION_MAKE_ENUM_32_BIT 的枚举条目,其值等于0xffffffff,相当于stdint.h 中的UINT32_MAX(请参阅herehere),强制这个特定的Arm_symbolic_exception_name 枚举具有uint32_t 的整数类型。这就是ARM_EXCEPTION_MAKE_ENUM_32_BIT 条目的唯一目的! 之所以有效,是因为uint32_t 是最小的整数类型,它可以包含此枚举中的所有枚举值——即:08,包括0xffffffff,或十进制@987654341 @ = 4294967295.

      Keywords: ARM_EXCEPTION_MAKE_ENUM_32_BIT enum 目的为什么会有呢? arm_symbolic_exception_name 的目的 0xffffffff 枚举条目在末尾。

      【讨论】:

        【解决方案8】:

        这取决于为枚举分配的值。

        例如: 如果存储了大于 2^32-1 的值,则分配给整个枚举的大小将更改为下一个大小。

        将 0xFFFFFFFFFFFF 值存储到枚举变量中,如果尝试在 32 位环境中编译会给出警告(舍入警告) 而在 64 位编译中,它会成功并且分配的大小将是 8 字节。

        【讨论】:

        • 我在 clang 11 中尝试过,编译器只是将值截断为 32 位。
        【解决方案9】:

        另一种方法是在联合中强制转换枚举,如下所示:

        union char_size {
          char a;
          enum {
            a = 1,
            b = 2,
          } val;
        };
        

        这样做会强制编译器将枚举放入字符中。

        【讨论】:

        • 这不是真的。 char_size 将是较大成员的大小,即枚举。
        • 我偷偷怀疑像这样的代码可能是我所看到的所有 FATAL_SEG_ERR 的原因。嗯嗯嗯…………
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-27
        • 1970-01-01
        • 2023-02-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多