将指向类型化/大小的枚举的指针转换为指向基础类型的指针是否安全?

Is it safe to convert a pointer to typed/sized enum to a pointer to the underlying type?

本文关键字:指针 是否 安全 类型 枚举 类型化 转换      更新时间:2023-10-16

以下代码:

void f(const uint8_t* a) {}  // <- this is an external library function
enum E : uint8_t { X, Y, Z };
int main(void) {
E e = X;
f(&e);  // <- error here
}

生成以下错误:

/tmp/c.cc:10:3: error: no matching function for call to 'f'
f(&e);
^
/tmp/c.cc:5:6: note: candidate function not viable: no known conversion from 'E *' to 'const uint8_t *' (aka 'const unsigned char *') for 1st argument
void f(const uint8_t* e) { }

这让我感到惊讶,因为我认为枚举定义中的: uint8_t意味着它们必须用该底层类型表示。我可以通过演员表轻松解决这个问题:

f((uint8_t*)&e);

我不太介意,但鉴于省略它是一个错误,这是否总是安全的,还是: uint8_t没有提供我认为的保证?

它确实是安全的(虽然我不是语言律师):存储在内存中的是一个uint8_t,这就是你要指向的。但是,如果f()要获取指向非常量uint8_t的指针,那么它可能会将值更改为未明确定义为E枚举值之一的值。(编辑:)虽然这显然是C++标准允许的,但对许多人来说还是令人惊讶的(请参阅下面评论中关于这一点的讨论),我鼓励您确保它不会发生。

。但正如其他人所建议的那样,您获得错误并不是因为您的安全概念,而是因为隐式转换不是在指向类型之间执行的。您可以将E传递给采用uint8_t的函数,但不能将E *传递给采用uint8_t *的函数;根据语言委员会的说法,在我看来,这将是对指针类型的过于漫不经心的态度。

Afaik 这只是偶然合法的:

您正在做的是执行reinterpret_cast,我假设f正在内部取消引用该指针。这仅在非常有限的情况下是合法的,虽然不是规范性的,但 cppreference.com 很好地概述了这些情况:

当对动态类型为 DynamicType 的对象的指针或引用reinterpret_cast(或 C 样式强制转换)到对不同类型的 AliasedType 对象的指针或引用时,强制转换始终成功,但生成的指针或引用只能在满足以下条件之一时用于访问对象:

  • AliasedType 是(可能符合 cv 标准的)DynamicType

  • AliasedType 和 DynamicType 都是指向同一类型 T 的指针(可能是多级的,每个级别可能符合 cv 条件)(自 C++11 起)

  • AliasedType 是 DynamicType 的(可能符合 cv 标准的)有符号或无符号变体

  • AliasedType 是一种聚合类型或联合类型,它将上述类型之一保存为元素或非静态成员(包括递归的子聚合元素和所包含联合的非静态数据成员):这使得在给定指向其非静态成员或元素的指针的情况下,可以安全地获取指向结构或联合的可用指针。

  • AliasedType 是 DynamicType
  • 的基类(可能符合 cv 条件),DynamicType 是一个没有非静态数据成员的标准布局类,AliasedType 是它的第一个基类。

  • AliasedType 是 char、unsigned char 或 std::byte:这允许将任何对象的对象表示形式检查为字节数组。

如果 AliasedType 不满足这些要求,则通过新指针或引用访问对象将调用未定义的行为。这称为严格别名规则,适用于 C++ 和 C 编程语言。

这些情况都不包括强制转换为指向枚举基础类型的指针!

但是:
强制转换为指向unsigned char*的指针并取消引用它始终是合法的,在大多数平台上,uint8_t只是一个类型定义。所以在这种情况下没关系,但如果底层类型(例如)是uint16_t,那就不行了。

话虽如此,听到大多数编译器允许这种使用,即使标准不允许,我也不会感到惊讶。