已经阅读了这个相关问题,但是想要更具体的解答。
- 有没有一种方法可以明确告诉编译器你希望枚举的宽度是多少?
- 如果有的话,你怎么做呢?我知道在C#中如何指定,那在C语言中是否也是这样做的呢?
- 这样做是否值得?当枚举值传递给一个函数时,它会被作为一个整型值传递吗?
已经阅读了这个相关问题,但是想要更具体的解答。
我相信如果你使用GCC,就会有一个标志。
-fshort-enums
是否有办法告诉编译器要将枚举定义为特定的宽度?
一般情况下不能。标准的C语言不支持。
这么做是否值得呢?
这要看具体情况。如果你是想传递参数给函数,那么不值得这样做(见下文)。如果是想在构建来自枚举类型的聚合时节省内存,那么这样做可能值得考虑。但是,在C语言中,你可以在聚合中使用大小合适的整数类型而不是枚举类型。在C语言中(与C++相反),枚举类型和整数类型几乎总是可以互换使用。
当枚举值被传递给函数时,它是否会作为一个int大小的值传递?
现代的大多数编译器都会将所有参数作为给定硬件平台上自然字长大小的值进行传递。例如,在64位平台上,许多编译器都会将所有参数作为64位值传递,而不管它们的实际大小,即使在该平台上类型 int
的大小为32位(因此,在这种平台上它通常不会作为“int大小”的值传递)。因此,试图优化枚举大小以用于参数传递是没有意义的。
即使你在编写严格的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
__attribute__((__packed__))
修饰符没有产生任何好处。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如果枚举是结构体的一部分,也有另一种方法:
enum whatever { a,b,c,d };
struct something {
char :0;
enum whatever field:CHAR_BIT;
char :0;
};
如果枚举字段周围有普通字段,则可以省略:0;。 如果之前有另一个位域,则:0将强制对其后面的字段进行字节对齐到下一个字节。
gcc
的标识组合可以编译它,只会出现 error: expected specifier-qualifier-list before ‘:’ token
的错误。零大小的位域是真实存在的,而且它们必须是匿名的,但它们仍然需要有一个类型。我猜想,一个零大小/匿名的位域意味着“在下一个分配单元的开始处开始下一个非零大小的位域”,其中它的类型指定对齐到哪个单元。我猜这引发了一个问题,为什么它需要一个类型,当接下来的非零大小的位域已经有一个类型了。但要问委员会这个问题可能需要时间机器。 - underscore_dstruct
内部使用。 - Patrick Schlüter从C23开始,在标准C中终于可以实现以下功能:
您可以在enum
关键字后面(或者在名称标签后面,如果有命名)放置一个冒号和整数类型,以指定枚举的固定底层类型,这将设置枚举类型的大小和范围。
这样做真的值得吗?当将枚举值传递给函数时,它是否会被作为int大小的值传递?
在x86_64上,整数的类型不影响它是否在寄存器中传递(只要它适合单个寄存器)。然而,堆上数据的大小对于缓存性能非常重要。
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
的大小为1(8位),可用于变量和函数参数类型。
另一方面,您可以使用默认为int
类型的枚举值进行赋值,但编译器会在分配给command_t
类型变量时立即将它们转换。CMD_16bit = 0xFFFF
,编译器将通过以下消息警告您:
编辑:如果您正在搜索此主题,也许是时候开始在嵌入式系统中使用C++并将部分代码切换到C++并使用class enum。警告:大整数隐式截断为无符号类型[-Woverflow]
你可以通过定义适当的值来强制枚举至少具有某个大小。例如,如果你希望将枚举存储为与int
相同大小,即使所有值都适合于char
,你可以这样做:
typedef enum {
firstValue = 1,
secondValue = 2,
Internal_ForceMyEnumIntSize = MAX_INT
} MyEnum;
注意,行为可能取决于实现。
正如你所指出的那样,将这样的值传递给函数将导致其被扩展为int类型,但如果你在数组或结构体中使用自己的类型,则大小将很重要。如果你真的关心元素大小,应该使用像int8_t
、int32_t
等类型。
@Nyx0uf 说道,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
(请参见此处和此处),会强制使这个特定的Arm_symbolic_exception_name
枚举具有uint32_t
整数类型。这就是ARM_EXCEPTION_MAKE_ENUM_32_BIT
条目的唯一目的!它之所以有效,是因为uint32_t
是可以包含此枚举中所有枚举值的最小整数类型——即:0
到8
(包括)以及0xffffffff
或十进制2^32-1
=4294967295
。
目前我无法回答你的前两个问题,因为我正在尝试找到一种好的方法来解决这个问题。如果我发现自己喜欢的策略,也许我会编辑这篇文章。但是这并不直观。
但我想指出一些迄今未提到的事情,并回答第三个问题:
当编写将从非C语言调用的C API时,“值得做”。任何直接链接到C代码的内容都需要正确理解C代码API中所有结构体、参数列表等的内存布局。不幸的是,C类型如int
,或更糟糕的是枚举类型,大小相对较不可预测(由编译器、平台等决定),因此,了解包含枚举类型的任何内容的内存布局可能会很棘手,除非您使用的编程语言编译器也是C编译器,并且具有某种语言机制来利用该知识。当API使用可预测大小的C类型,如uint8_t
,uint16_t
,uint32_t
,uint64_t
,void*
,uintptr_t
等以及由这些可预测大小的类型组成的结构/联合时,编写无问题的C库绑定会更容易。
因此,当内存布局和对齐问题可能存在时,我会关心枚举类型的大小。但是,除非您有某些增加机会成本的特殊情况(例如,在像小型MCU这样的内存受限系统上具有大量枚举类型值的数组/列表),否则我不会过于担心优化问题。
不幸的是,像-fshort-enums
这样的情况并没有得到帮助,因为该功能是供应商特定的,且不太可预测(例如,另一个系统必须通过近似GCC算法来“猜测”枚举类型的大小)。如果有任何东西,它将允许人们以一种打破其他语言绑定(或未使用相同选项编译的其他C代码)的常见假设的方式编译C代码,预期结果是参数或结构成员被写入或从内存中错误的位置读取,导致内存损坏。
这取决于为枚举分配的值。
例如: 如果存储大于2^32-1的值,则为整个枚举分配的大小将更改为下一个大小。
将0xFFFFFFFFFFFF值存储到枚举变量中,如果尝试在32位环境中编译,则会发出警告(四舍五入警告)。 而在64位编译中,它将成功,并且分配的大小将为8字节。
enum __attribute__((__packed__)) my_enum {...};
- Nikolai Fetissov__packed__
属性,但不知道在枚举中也可以使用。 - Will-fshort-enums
是危险的,因为它会影响所有枚举类型。最好使用__attribute__((__packed__))
。 - Roland Illig