在 C 中指定枚举类型的大小

发布于 2024-10-15 17:58:09 字数 234 浏览 3 评论 0原文

已通读此相关问题,但正在寻找更具体的内容。

  • 有没有办法具体告诉编译器您希望枚举的宽度?
  • 如果是这样,你会怎么做?我知道如何在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

吻风 2024-10-22 17:58:10

现在我无法回答你的前两个问题,因为我正在尝试自己找到一个好方法来做到这一点。如果我找到我喜欢的策略,也许我会编辑这个。但这并不直观。

但我想指出一些到目前为止还没有提到的事情,为此我将回答第三个问题,如下所示:

在编写将从以下语言调用的 C API 时,这是“值得做的”不是 C。任何直接链接到 C 代码的内容都需要正确理解 C 代码 API 中所有结构、参数列表等的内存布局。不幸的是,像 int 这样的 C 类型,或者最糟糕的是枚举,其大小相当难以预测(由编译器、平台等更改),因此了解包含枚举的任何内容的内存布局可能会很危险,除非您有其他编程语言的编译器也是 C 编译器,并且它有一些语言内机制来利用这些知识。当 API 使用可预测大小的 C 类型(例如 uint8_tuint16_tuint32_tuint64_tvoid*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 like uint8_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.

我不会写诗 2024-10-22 17:58:10

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

前任:
如果存储的值大于 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.

谎言月老 2024-10-22 17:58:09

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

-fshort-枚举

I believe there is a flag if you are using GCC.

-fshort-enums

川水往事 2024-10-22 17:58:09

有没有办法告诉你的编译器
具体来说你想要多宽
枚举是?

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

这值得吗?

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

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

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

Is there a way to tell your compiler
specifically how wide you want your
enum to be?

In general case no. Not in standard C.

Would it even be worth doing?

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.

When the enum value is passed to a function, will it be passed as an int-sized value regardless?

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.

始终不够 2024-10-22 17:58:09

从 C23 开始,这在标准 C 中终于成为可能:

您可以在 enum 关键字之后(或者在名称标签之后,如果已命名)放置冒号和整数类型指定枚举的固定底层类型,它设置枚举类型的大小和范围。

这值得做吗?当枚举值传递给函数时,它是否会作为 int 大小的值传递?

在 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.

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?

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.

谎言月老 2024-10-22 17:58:09

即使您正在编写严格的 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__)) 有任何好处直到我开始使用 C++ 编译器。

编辑:

@technosaurus 的怀疑是正确的。

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

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

我现在看到了 的预期结果海湾合作委员会:

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

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

#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

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 of sizeof(PACKED) I see the results I had expected...

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

I now see the expected results from gcc:

$ gcc enum_size.c
$ ./a.out
packed:         1
unpacked:       4
$ gcc enum_size.c -fshort_enums
$ ./a.out
packed:         1
unpacked:       1
乖乖哒 2024-10-22 17:58:09

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

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,编译器将警告您并显示以下消息:

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

编辑: 如果您正在搜索此主题,也许是时候开始搜索嵌入式中的 C++ 使用情况并将部分代码切换为 C++并使用类枚举

In some circumstances, this may be helpful:

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; 

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 a command_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:

warning: large integer implicitly truncated to unsigned type [-Woverflow]

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.

○愚か者の日 2024-10-22 17:58:09

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

enum whatever { a,b,c,d };

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

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

There is also another way if the enum is part of a structure:

enum whatever { a,b,c,d };

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

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.

情愿 2024-10-22 17:58:09

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

typedef enum {
    firstValue = 1,
    secondValue = 2,

    Internal_ForceMyEnumIntSize = MAX_INT
} MyEnum;

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

正如您所注意到的,将这样的值传递给函数将导致它扩展为 int 无论如何,但如果您在数组或结构中使用您的类型,那么大小就很重要。如果您真的关心元素大小,那么您应该使用 int8_tint32_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 a char, you can do something like this:

typedef enum {
    firstValue = 1,
    secondValue = 2,

    Internal_ForceMyEnumIntSize = MAX_INT
} MyEnum;

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.

┼── 2024-10-22 17:58:09

正如 @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 是可以包含此枚举中所有枚举值的最小整数类型,即:08 >,包含在内,以及 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:

-fshort-enums

Allocate to an enum type only as many bytes as it needs for the declared range of possible values. Specifically, the enum type is equivalent to the smallest integer type that has enough room.

Warning: the -fshort-enums switch causes GCC to generate code that is not binary compatible with code generated without that switch. Use it to conform to a non-default application binary interface.

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 to 0xffffffff, which is the equivalent of UINT32_MAX from stdint.h (see here and here), forces this particular Arm_symbolic_exception_name enum to have an integer type of uint32_t. That is the sole purpose of this ARM_EXCEPTION_MAKE_ENUM_32_BIT entry! It works because uint32_t is the smallest integer type which can contain all of the enum values in this enum--namely: 0 through 8, inclusive, as well as 0xffffffff, or decimal 2^32-1 = 4294967295.

enter image description here

Keywords: ARM_EXCEPTION_MAKE_ENUM_32_BIT enum purpose why have it? Arm_symbolic_exception_name purpose of 0xffffffff enum entry at end.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文