为什么 reinterpret_cast 不是 constexpr?
Why is reinterpret_cast not constexpr?
考虑以下片段:
static constexpr uint8_t a = 0;
static constexpr const int8_t *b = reinterpret_cast<const int8_t *>(&a);
使用 error: a reinterpret_cast is not a constant expression
编译失败,因为 the C++ standard forbids 在 constexpr
中使用 reinterpret_cast
。
但是,如果我想将值 b 存储在 PROGMEM
中(对于 AVR 微控制器),编译会成功:
static constexpr uint8_t a = 0;
static const int8_t PROGMEM *const b = reinterpret_cast<const int8_t *>(&a);
在这种情况下,编译器能够证明表达式 reinterpret_cast<const int8_t *>(&a)
是编译时常量,因为它将其结果(指向某个包含零的字节的地址)插入程序 space 在二进制文件中:
_ZL1g:
.zero 1
.section .progmem.data,"a",@progbits
.type _ZL1b, @object
.size _ZL1b, 2
_ZL1b:
.word _ZL1g
此外,我的理解是 reinterpret_cast
是一个编译时指令。那么为什么不能在constexpr
中使用呢?
在运行时,C++ 语言具有未定义行为的概念。在某些(指定的)条件下,程序具有未定义行为,这意味着它可以表现出任何行为:它可以崩溃,它可以永远挂起,它可以打印乱码,它可以工作,或者它可以做任何事情。为什么存在这种情况的简单解释是性能。
在运行时这是一种权衡(如果你愿意的话,这是一种折衷),但在编译时这是不可接受的。如果标准允许在编译时使用 UB,那么不仅在编译程序或无限期编译时发生崩溃是合法的,而且您永远无法确定编译后的可执行文件的有效性。
因此,任何形式的 constexpr
都必须 100% 没有未定义的行为。没有例外。没有余地。
一个臭名昭著的 UB 来源是 reinterpret_cast
。 reinterpret_cast
的有效用法很少,大多数都会导致 UB。此外,几乎不可能检查使用是否有效。所以 reinterpret_cast
在编译期间是不允许的,即在 constexpr.
中是不允许的
So how come it can't be used inside a constexpr?
很简单,因为标准不允许。 constexpr
是自 C++11 以来在不同标准上不断扩展的功能,因此很自然地认为 reinterpret_cast
使用的一个子集可以工作。
问题是允许它实际上是有用的还是有害的。 reinterpret_cast
很少有好的用途,特别是如果您在假设严格的别名规则成立的情况下编程和编译代码:创建破坏它的指针很容易。
另一方面,很明显,对于嵌入式用户和专业用户 compilers/flags/environments,它可能在某种程度上有用。
考虑以下片段:
static constexpr uint8_t a = 0;
static constexpr const int8_t *b = reinterpret_cast<const int8_t *>(&a);
使用 error: a reinterpret_cast is not a constant expression
编译失败,因为 the C++ standard forbids 在 constexpr
中使用 reinterpret_cast
。
但是,如果我想将值 b 存储在 PROGMEM
中(对于 AVR 微控制器),编译会成功:
static constexpr uint8_t a = 0;
static const int8_t PROGMEM *const b = reinterpret_cast<const int8_t *>(&a);
在这种情况下,编译器能够证明表达式 reinterpret_cast<const int8_t *>(&a)
是编译时常量,因为它将其结果(指向某个包含零的字节的地址)插入程序 space 在二进制文件中:
_ZL1g:
.zero 1
.section .progmem.data,"a",@progbits
.type _ZL1b, @object
.size _ZL1b, 2
_ZL1b:
.word _ZL1g
此外,我的理解是 reinterpret_cast
是一个编译时指令。那么为什么不能在constexpr
中使用呢?
在运行时,C++ 语言具有未定义行为的概念。在某些(指定的)条件下,程序具有未定义行为,这意味着它可以表现出任何行为:它可以崩溃,它可以永远挂起,它可以打印乱码,它可以工作,或者它可以做任何事情。为什么存在这种情况的简单解释是性能。
在运行时这是一种权衡(如果你愿意的话,这是一种折衷),但在编译时这是不可接受的。如果标准允许在编译时使用 UB,那么不仅在编译程序或无限期编译时发生崩溃是合法的,而且您永远无法确定编译后的可执行文件的有效性。
因此,任何形式的 constexpr
都必须 100% 没有未定义的行为。没有例外。没有余地。
一个臭名昭著的 UB 来源是 reinterpret_cast
。 reinterpret_cast
的有效用法很少,大多数都会导致 UB。此外,几乎不可能检查使用是否有效。所以 reinterpret_cast
在编译期间是不允许的,即在 constexpr.
So how come it can't be used inside a constexpr?
很简单,因为标准不允许。 constexpr
是自 C++11 以来在不同标准上不断扩展的功能,因此很自然地认为 reinterpret_cast
使用的一个子集可以工作。
问题是允许它实际上是有用的还是有害的。 reinterpret_cast
很少有好的用途,特别是如果您在假设严格的别名规则成立的情况下编程和编译代码:创建破坏它的指针很容易。
另一方面,很明显,对于嵌入式用户和专业用户 compilers/flags/environments,它可能在某种程度上有用。