将指向 typed/sized 枚举的指针转换为指向基础类型的指针是否安全?
Is it safe to convert a pointer to typed/sized enum to a pointer to the underlying type?
以下代码:
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
枚举值之一的值。 (Edit:) 虽然这显然是 C++ 标准允许的,但令许多人感到惊讶(请参阅下面关于这一点的评论中的讨论),我鼓励您确保它确实如此不会发生。
...但正如其他人所建议的那样,由于您的安全概念,您并没有收到错误,而是因为在指向的类型之间不执行隐式转换。您可以将 E
传递给采用 uint8_t
的函数,但不能将 E *
传递给采用 uint8_t *
的函数;那将是——根据语言委员会和我的看法——对指针类型的一种过于傲慢的态度。
Afaik 这只是偶然合法的:
您正在执行的是 reinterpret_cast
,我假设 f
正在内部取消引用该指针。这仅在非常有限的情况下是合法的,虽然不是规范的,cppreference.com 很好地概述了这些情况:
When a pointer or reference to object whose dynamic type is DynamicType is reinterpret_cast (or C-style cast) to a pointer or reference to object of a different type AliasedType, the cast always succeeds, but the resulting pointer or reference may only be used to access the object if one of the following is true:
AliasedType is (possibly cv-qualified) DynamicType
AliasedType and DynamicType are both (possibly multi-level, possibly cv-qualified at each level) pointers to the same type T (since C++11)
AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType
AliasedType is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to obtain a usable pointer to a struct or union given a pointer to its non-static member or element.
AliasedType is a (possibly cv-qualified) base class of DynamicType and DynamicType is a standard-layout class that has has no non-static data members, and AliasedType is its first base class.
AliasedType is char, unsigned char, or std::byte: this permits examination of the object representation of any object as an array of bytes.
If AliasedType does not satisfy these requirements, accessing the object through the new pointer or reference invokes undefined behavior. This is known as the strict aliasing rule and applies to both C++ and C programming languages.
None 这些情况包括强制转换为指向枚举的基础类型的指针!
然而:
转换为指向 unsigned char*
的指针并取消引用它始终是合法的,并且在大多数平台上 uint8_t
只是一个类型定义。所以在这种情况下没问题,但如果基础类型例如将是 uint16_t
.
话虽这么说,听到大多数编译器都允许这种用法,即使标准不允许,我也不会感到惊讶。
以下代码:
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
枚举值之一的值。 (Edit:) 虽然这显然是 C++ 标准允许的,但令许多人感到惊讶(请参阅下面关于这一点的评论中的讨论),我鼓励您确保它确实如此不会发生。
...但正如其他人所建议的那样,由于您的安全概念,您并没有收到错误,而是因为在指向的类型之间不执行隐式转换。您可以将 E
传递给采用 uint8_t
的函数,但不能将 E *
传递给采用 uint8_t *
的函数;那将是——根据语言委员会和我的看法——对指针类型的一种过于傲慢的态度。
Afaik 这只是偶然合法的:
您正在执行的是 reinterpret_cast
,我假设 f
正在内部取消引用该指针。这仅在非常有限的情况下是合法的,虽然不是规范的,cppreference.com 很好地概述了这些情况:
When a pointer or reference to object whose dynamic type is DynamicType is reinterpret_cast (or C-style cast) to a pointer or reference to object of a different type AliasedType, the cast always succeeds, but the resulting pointer or reference may only be used to access the object if one of the following is true:
AliasedType is (possibly cv-qualified) DynamicType
AliasedType and DynamicType are both (possibly multi-level, possibly cv-qualified at each level) pointers to the same type T (since C++11)
AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType
AliasedType is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to obtain a usable pointer to a struct or union given a pointer to its non-static member or element.
AliasedType is a (possibly cv-qualified) base class of DynamicType and DynamicType is a standard-layout class that has has no non-static data members, and AliasedType is its first base class.
AliasedType is char, unsigned char, or std::byte: this permits examination of the object representation of any object as an array of bytes.
If AliasedType does not satisfy these requirements, accessing the object through the new pointer or reference invokes undefined behavior. This is known as the strict aliasing rule and applies to both C++ and C programming languages.
None 这些情况包括强制转换为指向枚举的基础类型的指针!
然而:
转换为指向 unsigned char*
的指针并取消引用它始终是合法的,并且在大多数平台上 uint8_t
只是一个类型定义。所以在这种情况下没问题,但如果基础类型例如将是 uint16_t
.
话虽这么说,听到大多数编译器都允许这种用法,即使标准不允许,我也不会感到惊讶。