在 C 中指定枚举类型的大小
已通读此相关问题,但正在寻找更具体的内容。
- 有没有办法具体告诉编译器您希望枚举的宽度?
- 如果是这样,你会怎么做?我知道如何在 C# 中指定它; C 中也有类似的做法吗?
- 这值得吗?当枚举值传递给函数时,它是否会作为
int
大小的值传递?
Already read through this related question, but was looking for something a little more specific.
- Is there a way to tell your compiler specifically how wide you want your enum to be?
- If so, how do you do it? I know how to specify it in C#; is it similarly done in C?
- Would it even be worth doing? When the enum value is passed to a function, will it be passed as an
int
-sized value regardless?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
现在我无法回答你的前两个问题,因为我正在尝试自己找到一个好方法来做到这一点。如果我找到我喜欢的策略,也许我会编辑这个。但这并不直观。
但我想指出一些到目前为止还没有提到的事情,为此我将回答第三个问题,如下所示:
在编写将从以下语言调用的 C API 时,这是“值得做的”不是 C。任何直接链接到 C 代码的内容都需要正确理解 C 代码 API 中所有结构、参数列表等的内存布局。不幸的是,像 int 这样的 C 类型,或者最糟糕的是枚举,其大小相当难以预测(由编译器、平台等更改),因此了解包含枚举的任何内容的内存布局可能会很危险,除非您有其他编程语言的编译器也是 C 编译器,并且它有一些语言内机制来利用这些知识。当 API 使用可预测大小的 C 类型(例如
uint8_t
、uint16_t
、uint32_t
、uint64_t
、void*
、uintptr_t
等,以及由这些可预测大小的类型组成的结构体/联合体。因此,当枚举大小对程序正确性很重要时,例如当可能出现内存布局和对齐问题时,我会关心枚举大小。但我不会太担心优化,除非你有一些利基情况会放大机会成本(例如:内存受限系统(如小型 MCU)上的大型数组/枚举类型值列表)。
不幸的是,类似我提到的情况并不能通过
-fshort-enums
之类的东西得到帮助,因为此功能是特定于供应商的并且不太可预测(例如另一个系统将具有通过近似 GCC 的-fshort-enums
枚举大小算法来“猜测”枚举大小)。如果有的话,它将允许人们以一种打破其他语言(或未使用相同选项编译的其他 C 代码)中的绑定所做的常见假设的方式编译 C 代码,预期结果是作为参数的内存损坏或者结构成员被写入或读取到内存中的错误位置。Right now I can't answer your first two questions, because I am trying to find a good way to do this myself. Maybe I will edit this if I find a strategy that I like. It isn't intuitive though.
But I want to point something out that hasn't been mentioned so far, and to do so I will answer the third question like so:
It is "worth doing" when writing a C API that will be called from languages that aren't C. Anything that directly links to the C code will need to correctly understand the memory layout of all structs, parameter lists, etc in the C code's API. Unfortunately, C types like
int
, or worst yet, enums, are fairly unpredictably sized (changes by compiler, platform, etc), so knowing the memory layout of anything containing an enum can be dodgy unless your other programming language's compiler is also the C compiler AND it has some in-language mechanism to exploit that knowledge. It is much easier to write problem-free bindings to C libraries when the API uses predictably-sized C types likeuint8_t
,uint16_t
,uint32_t
,uint64_t
,void*
,uintptr_t
, etc, and structs/unions composed of those predictably-sized types.So I would care about enum sizing when it matters for program correctness, such as when memory layout and alignment issues are possible. But I wouldn't worry about it so much for optimization, not unless you have some niche situation that amplifies the opportunity cost (ex: a large array/list of enum-typed values on a memory constrained system like a small MCU).
Unfortunately, situations like what I'm mentioning are not helped by something like
-fshort-enums
, because this feature is vendor-specific and less predictable (e.g. another system would have to "guess" enum size by approximating GCC's algorithm for-fshort-enums
enum sizing). If anything, it would allow people to compile C code in a way that would break common assumptions made by bindings in other languages (or other C code that wasn't compiled with the same option), with the expected result being memory corruption as parameters or struct members get written to, or read from, the wrong locations in memory.这取决于为枚举分配的值。
前任:
如果存储的值大于 2^32-1,则为整个枚举分配的大小将更改为下一个大小。
将 0xFFFFFFFFFFFF 值存储到枚举变量,如果尝试在 32 位环境中编译,则会发出警告(四舍五入警告)
在 64 位编译中,它将成功并且分配的大小将为 8 个字节。
It depends on the values assigned for the enums.
Ex:
If the value greater than 2^32-1 is stored, the size allocated for the overall enum will change to the next size.
Store 0xFFFFFFFFFFFF value to a enum variable, it will give warning if tried to compile in a 32 bit environment (round off warning)
Where as in a 64 bit compilation, it will be successful and the size allocated will be 8 bytes.
我相信如果您使用 GCC,会有一个标志。
I believe there is a flag if you are using GCC.
一般情况下没有。不在标准 C 中。
这取决于上下文。如果您正在谈论将参数传递给函数,那么不,这是不值得做的(见下文)。如果是为了在从枚举类型构建聚合时节省内存,那么这可能是值得做的。但是,在 C 中,您可以简单地在聚合中使用适当大小的整数类型而不是枚举类型。在 C(相对于 C++)中,枚举类型和整数类型几乎总是可以互换的。
如今,许多(大多数)编译器将所有参数作为给定硬件平台的自然字大小值传递。例如,在 64 位平台上,许多编译器会将所有参数作为 64 位值传递,无论其实际大小如何,即使类型
int
在该平台上有 32 位(因此,它在这样的平台上通常不会作为“int-sized”值传递)。因此,尝试优化枚举大小以实现参数传递目的是没有意义的。In general case no. Not in standard C.
It depends on the context. If you are talking about passing parameters to functions, then no, it is not worth doing (see below). If it is about saving memory when building aggregates from enum types, then it might be worth doing. However, in C you can simply use a suitably-sized integer type instead of enum type in aggregates. In C (as opposed to C++) enum types and integer types are almost always interchangeable.
Many (most) compilers these days pass all parameters as values of natural word size for the given hardware platform. For example, on a 64-bit platform many compilers will pass all parameters as 64-bit values, regardless of their actual size, even if type
int
has 32 bits in it on that platform (so, it is not generally passed as "int-sized" value on such a platform). For this reason, it makes no sense to try to optimize enum sizes for parameter passing purposes.从 C23 开始,这在标准 C 中终于成为可能:
您可以在
enum
关键字之后(或者在名称标签之后,如果已命名)放置冒号和整数类型指定枚举的固定底层类型,它设置枚举类型的大小和范围。在 x86_64 上,整数的类型不会影响它是否在寄存器中传递(只要它适合单个寄存器)。然而,堆上数据的大小对于缓存性能非常重要。
As of C23, this is finally possible in standard C:
You can put a colon and an integer type after the
enum
keyword (or after the name tag, if it's named) to specify the enum's fixed underyling type, which sets the size and range of the enum type.On x86_64, the type of an integer does not influence whether it is passed in register or not (as long as it fits in a single register). The size of data on the heap however is very significant for cache performance.
即使您正在编写严格的
C
代码,结果也将取决于编译器。采用此线程中的策略,我得到了一些有趣的结果...enum_size.c
在上面的示例中,我没有意识到
__attribute__((__packed__))
有任何好处直到我开始使用 C++ 编译器。编辑:
@technosaurus 的怀疑是正确的。
通过检查
sizeof(enum PackedFlags)
而不是sizeof(PACKED)
的大小,我看到了我预期的结果...我现在看到了
的预期结果海湾合作委员会:
Even if you are writing strict
C
code, the results are going to be compiler dependent. Employing the strategies from this thread, I got some interesting results...enum_size.c
In my example above, I did not realize any benefit from
__attribute__((__packed__))
modifier until I started using the C++ compiler.EDIT:
@technosaurus's suspicion was correct.
By checking the size of
sizeof(enum PackedFlags)
instead ofsizeof(PACKED)
I see the results I had expected...I now see the expected results from
gcc
:在某些情况下,这可能会有所帮助:
一方面,类型
command_t
的大小为 1(8 位),可用于变量和函数参数类型。另一方面,默认情况下,您可以使用
int
类型的枚举值进行赋值,但当分配给command_t
类型变量时,编译器会立即对它们进行强制转换。此外,如果您执行了一些不安全的操作,例如定义和使用
CMD_16bit = 0xFFFF
,编译器将警告您并显示以下消息:编辑: 如果您正在搜索此主题,也许是时候开始搜索嵌入式中的 C++ 使用情况并将部分代码切换为 C++并使用类枚举。
In some circumstances, this may be helpful:
On one hand type
command_t
has size 1 (8bits) and can be used for variable and function parameter type.On the other hand you can use the enum values for assignation that are of type
int
by default but the compiler will cast them immediately when assigned to acommand_t
type variable.Also, if you do something unsafe like defining and using a
CMD_16bit = 0xFFFF,
the compiler will warn you with following message:Edit: If and you are searching for this topic, maybe it's time to start searching for C++ usage in embedded and switching some of your code to C++ and use class enum.
如果枚举是结构的一部分,还有另一种方法:
:0;如果枚举字段被普通字段包围,则可以省略。如果之前有另一个位字段,则 :0 将强制字节对齐到其后面字段的下一个字节。
There is also another way if the enum is part of a structure:
The :0; can be omitted if the enum field is surrounded by normal fields. If there's another bitfield before, the :0 will force byte alignement to the next byte for the field following it.
您可以通过定义适当的值来强制其至少达到一定的大小。例如,如果您希望枚举的存储大小与
int
相同,即使所有值都适合char
,您也可以执行以下操作:但是请注意,该行为可能取决于实现。
正如您所注意到的,将这样的值传递给函数将导致它扩展为 int 无论如何,但如果您在数组或结构中使用您的类型,那么大小就很重要。如果您真的关心元素大小,那么您应该使用
int8_t
、int32_t
等类型。You can force it to be at least a certain size by defining an appropriate value. For example, if you want your enum to be stored as the same size as an
int
, even though all the values would fit in achar
, you can do something like this:Note, however, that the behavior can be dependent on the implementation.
As you note, passing such a value to a function will cause it to be expanded to an int anyway, but if you are using your type in an array or a struct, then the size will matter. If you really care about element sizes, you should really use types like
int8_t
,int32_t
, etc.正如 @Nyx0uf 所说,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
。关键字:ARM_EXCEPTION_MAKE_ENUM_32_BIT enum 目的为什么要有它? Arm_symbolic_exception_name 结尾处 0xffffffff 枚举条目的用途。
As @Nyx0uf says, GCC has a flag which you can set:
Source: https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html
Additional great reading for general insight: https://www.embedded.fm/blog/2016/6/28/how-big-is-an-enum.
Interesting...notice the line I highlighted in yellow below! Adding an enum entry called
ARM_EXCEPTION_MAKE_ENUM_32_BIT
and with a value equal to0xffffffff
, which is the equivalent ofUINT32_MAX
fromstdint.h
(see here and here), forces this particularArm_symbolic_exception_name
enum to have an integer type ofuint32_t
. That is the sole purpose of thisARM_EXCEPTION_MAKE_ENUM_32_BIT
entry! It works becauseuint32_t
is the smallest integer type which can contain all of the enum values in this enum--namely:0
through8
, inclusive, as well as0xffffffff
, or decimal2^32-1
=4294967295
.Keywords: ARM_EXCEPTION_MAKE_ENUM_32_BIT enum purpose why have it? Arm_symbolic_exception_name purpose of 0xffffffff enum entry at end.