constexpr 和字节序
Posted
技术标签:
【中文标题】constexpr 和字节序【英文标题】:constexpr and endianness 【发布时间】:2010-12-07 17:09:02 【问题描述】:在 C++ 编程世界中不时出现的一个常见问题是编译时确定字节顺序。通常这是通过几乎不可移植的#ifdefs 来完成的。但是 C++11 constexpr
关键字以及模板特化是否为我们提供了更好的解决方案?
在 C++11 中执行以下操作是否合法:
constexpr bool little_endian()
const static unsigned num = 0xAABBCCDD;
return reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD;
然后为两种字节序类型专门化一个模板:
template <bool LittleEndian>
struct Foo
// .... specialization for little endian
;
template <>
struct Foo<false>
// .... specialization for big endian
;
然后做:
Foo<little_endian()>::do_something();
【问题讨论】:
【参考方案1】:新答案 (C++20)
c++20 引入了新的标准库头文件<bit>
。
除此之外,它还为check the endianness 提供了一种干净、可移植的方式。
由于我的旧方法依赖于一些有问题的技术,我建议任何使用它的人切换到标准库提供的检查。
这是一个适配器,它允许使用检查字节顺序的新方法,而无需更新依赖于我的旧类接口的代码:
#include <bit>
class Endian
public:
Endian() = delete;
static constexpr bool little = std::endian::native == std::endian::little;
static constexpr bool big = std::endian::native == std::endian::big;
static constexpr bool middle = !little && !big;
;
旧答案
我可以这样写:
#include <cstdint>
class Endian
private:
static constexpr uint32_t uint32_ = 0x01020304;
static constexpr uint8_t magic_ = (const uint8_t&)uint32_;
public:
static constexpr bool little = magic_ == 0x04;
static constexpr bool middle = magic_ == 0x02;
static constexpr bool big = magic_ == 0x01;
static_assert(little || middle || big, "Cannot determine endianness!");
private:
Endian() = delete;
;
我已经用 g++ 对其进行了测试,它编译时没有警告。它在 x64 上给出了正确的结果。 如果您有任何大端或中端处理器,请在评论中确认这对您有用。
【讨论】:
什么是const uint8_t &
@Nick 是对常量 8 位无符号整数的引用。
我明白,但这样的演员阵容有什么好处?为什么不只是没有 const 和 ref 的 uint8_t?
我很惊讶这是允许的。看起来标准中的某种遗漏。它不应该被允许,因为它排除了一大类实现。
这不起作用。对const uint8_t&
的强制转换通过将uint32_
的值截断为8 位来创建一个临时值。这将始终声称每个目标都是小端的。 (尽管这些天在实践中可能已经足够准确了!)【参考方案2】:
假设N2116 是被合并的措辞,那么您的示例格式错误(请注意,C++ 中没有“合法/非法”的概念)。 [decl.constexpr]/3 的拟议文本说
它的函数体应该是一个复合语句的形式
return expression;
其中表达式是潜在的常量表达式(5.19);
您的函数违反了要求,因为它还声明了一个局部变量。
编辑:可以通过将 num 移出函数来克服此限制。那么,该函数仍然不是格式正确的,因为表达式需要是一个潜在的常量表达式,它被定义为
一个表达式是一个潜在的常量表达式,如果它是一个常量 替换所有出现的函数参数时的表达式 通过适当类型的任意常量表达式。
IOW, reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD
必须是一个常量表达式。但是,它不是:&num
将是地址常量表达式 (5.19/4)。但是,常量表达式不允许访问此类指针的值:
下标运算符 [] 和类成员 access 。和 在创建 地址常量表达式,但不能使用这些运算符访问对象的值。
编辑:以上文字来自C++98。显然,C++0x 对常量表达式的允许程度更高。该表达式涉及数组引用的左值到右值转换,除非
它适用于有效整数类型的左值,该左值指 到已初始化的非易失性 const 变量或静态数据成员 用常量表达式
我不清楚 (&num)[0]
是“指”一个 const 变量,还是只有文字 num
“指”这样一个变量。如果(&num)[0]
引用该变量,则不清楚reinterpret_cast<const unsigned char*> (&num)[0]
是否仍然“引用”num
。
【讨论】:
我觉得这里不适用。静态变量本身就是常量。 N2116 4.1 中的措辞规定函数体必须只有一个语句(即返回语句)。请注意,从我对文本的快速浏览来看,如果 num 是全局定义的,我看不到任何禁止上述代码的内容。 最后引用的段落似乎不是最新的 c++0x 草案 (n2960) 的一部分。草案说&num
是一个常量表达式,如果num
不是线程或自动存储持续时间的变量或数据成员(阅读:如果num
是没有“thread_local”说明符的本地静态或命名空间范围变量, 那么&num
是一个常量表达式)。然而reinterpret_cast
使它成为一个非常量表达式,因为它构成了指针类型到文字类型的转换(注意指针类型本身就是文字类型)。
不,这没有问题。可以肯定的是,这是不允许的。措辞很清楚。
而指针类型是标量类型。 :) 顺便说一句,我认为您也对上述(&num)[0]
感到困惑:在代码中,他从不这样做(&num)[0]
。他正在做(reinterpret_cast<...>(&num))[0]
。所以你要先考虑reinterpret_cast,然后是result_of_reinterpret_cast[0]
。你的最后一段表明你把它的装订弄错了,这让读者很困惑。【参考方案3】:
无法在编译时使用constexpr
确定字节顺序(在 C++20 之前)。 reinterpret_cast
被 [expr.const]p2 明确禁止,正如 iain 建议从工会的非活跃成员那里读取数据一样。也禁止转换为不同的引用类型,因为这种转换被解释为reinterpret_cast
。
更新:
这现在可以在 C++20 中实现。一种方式(live):
#include <bit>
template<std::integral T>
constexpr bool is_little_endian()
for (unsigned bit = 0; bit != sizeof(T) * CHAR_BIT; ++bit)
unsigned char data[sizeof(T)] = ;
// In little-endian, bit i of the raw bytes ...
data[bit / CHAR_BIT] = 1 << (bit % CHAR_BIT);
// ... corresponds to bit i of the value.
if (std::bit_cast<T>(data) != T(1) << bit)
return false;
return true;
static_assert(is_little_endian<int>());
(请注意,C++20 保证二进制补码整数——具有未指定的位顺序——因此我们只需要检查数据的每一位是否映射到整数中的预期位置。)
但如果你有 C++20 标准库,你也可以问它:
#include <type_traits>
constexpr bool is_little_endian = std::endian::native == std::endian::little;
【讨论】:
++ .. 看了this question之后才想到这个。【参考方案4】:在即将到来的 C++20 中有std::endian
。
#include <type_traits>
constexpr bool little_endian() noexcept
return std::endian::native == std::endian::little;
【讨论】:
我不敢相信他们这么称呼它而不是endianness
或 byte_order
等。【参考方案5】:
这是一个非常有趣的问题。
我不是语言律师,但您也许可以将 reinterpret_cast 替换为联合。
const union
int int_value;
char char_value[4];
Endian = 0xAABBCCDD ;
constexpr bool little_endian()
return Endian[0] == 0xDD;
【讨论】:
在联合中放置一个值然后通过另一个成员访问联合是无效的。 @GMan:格式正确,但会调用未定义的行为。 “有效”不是 C++ 标准中定义的属性。 @Martin:标准的哪 § 中说它调用了未定义的行为? char 左值当然可以给 int 对象起别名(部分)。此外,据我所知,所有可能的位模式都表示有效的 char 和 unsigned char 值。这让我相信这只是调用实现定义的行为,而不是 UB。 @sellibitze:使用char*
为指针起别名可以,但不能通过联合。
@Martinv.Löwis clang 给出了一个错误,并指出在常量表达式中根本不允许读取联合中的非活动成员。通常它是未定义的行为,但看起来它在常量表达式中格式不正确。【参考方案6】:
我的第一篇文章。只是想分享一些我正在使用的代码。
//Some handy defines magic, thanks overflow
#define IS_LITTLE_ENDIAN ('ABCD'==0x41424344UL) //41 42 43 44 = 'ABCD' hex ASCII code
#define IS_BIG_ENDIAN ('ABCD'==0x44434241UL) //44 43 42 41 = 'DCBA' hex ASCII code
#define IS_UNKNOWN_ENDIAN (IS_LITTLE_ENDIAN == IS_BIG_ENDIAN)
//Next in code...
struct Quad
union
#if IS_LITTLE_ENDIAN
struct std::uint8_t b0, b1, b2, b3; ;
#elif IS_BIG_ENDIAN
struct std::uint8_t b3, b2, b1, b0; ;
#elif IS_UNKNOWN_ENDIAN
#error "Endianness not implemented!"
#endif
std::uint32_t dword;
;
;
constexpr 版本:
namespace Endian
namespace Impl //Private
//41 42 43 44 = 'ABCD' hex ASCII code
static constexpr std::uint32_t LITTLE_ 0x41424344u ;
//44 43 42 41 = 'DCBA' hex ASCII code
static constexpr std::uint32_t BIG_ 0x44434241u ;
//Converts chars to uint32 on current platform
static constexpr std::uint32_t NATIVE_ 'ABCD' ;
//Public
enum class Type : size_t UNKNOWN, LITTLE, BIG ;
//Compare
static constexpr bool IS_LITTLE = Impl::NATIVE_ == Impl::LITTLE_;
static constexpr bool IS_BIG = Impl::NATIVE_ == Impl::BIG_;
static constexpr bool IS_UNKNOWN = IS_LITTLE == IS_BIG;
//Endian type on current platform
static constexpr Type NATIVE_TYPE = IS_LITTLE ? Type::LITTLE : IS_BIG ? Type::BIG : Type::UNKNOWN;
//Uncomment for test.
//static_assert(!IS_LITTLE, "This platform has little endian.");
//static_assert(!IS_BIG_ENDIAN, "This platform has big endian.");
//static_assert(!IS_UNKNOWN, "Error: Unsupported endian!");
【讨论】:
@A Andrew F. 如果您添加一些代码描述会很棒 AFAIK,整数多字符常量的值是实现定义的。至少不是UB。【参考方案7】:这看起来像是作弊,但您始终可以包含 endian.h... BYTE_ORDER == BIG_ENDIAN 是一个有效的 constexpr...
【讨论】:
并非所有系统都有 endian.h,MacOS 和 BSD endian.h 也会发出大量警告。【参考方案8】:如果您的目标是确保编译器在编译时将 little_endian()
优化为常量 true 或 false,而其任何内容不会在可执行文件中结束或在运行时执行,并且仅从“正确”你的两个 Foo
模板之一,我担心你会失望。
我也不是语言律师,但在我看来,constexpr
就像 inline
或 register
:一个提醒编译器编写者存在潜在优化的关键字。然后由编译器编写者决定是否利用它。语言规范通常要求行为,而不是优化。
另外,您是否真的在各种 C++0x 投诉编译器上尝试过这个,看看会发生什么?我猜他们中的大多数人会在你的双模板上窒息,因为如果用false
调用,他们将无法确定使用哪一个。
【讨论】:
不太一样。 'constexpr' 函数的结果通常可用于需要常量表达式的地方,例如。一个数组边界。虽然我相信在函数模板的情况下还有一些余地。以上是关于constexpr 和字节序的主要内容,如果未能解决你的问题,请参考以下文章