为什么在C++中没有像签名那样的字节顺序修饰符?

Len*_*mel 58 c++ language-features static-typing endianness

(我想这个问题可能适用于许多类型语言,但我选择使用C++作为例子.)

为什么没有办法写:

struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};
Run Code Online (Sandbox Code Playgroud)

指定特定成员,变量和参数的字节顺序?

与签名比较

我知道变量的类型不仅决定了用于存储值的字节数,还决定了在执行计算时如何解释这些字节.

例如,这两个声明每个都分配一个字节,对于两个字节,每个可能的8位序列都是有效值:

signed char s;
unsigned char u;
Run Code Online (Sandbox Code Playgroud)

但是相同的二进制序列可能会有不同的解释,例如11111111,在分配时指的是-1,而指定时指的是s255 u.当有符号和无符号变量涉及相同的计算时,编译器(主要)负责正确的转换.

在我的理解中,字节序只是同一原则的变体:基于关于存储它的存储器的编译时信息对二进制模式的不同解释.

在允许低级编程的类型语言中使用该功能似乎是显而易见的.但是,这不是C,C++或我所知的任何其他语言的一部分,我没有在网上找到任何关于此的讨论.

更新

我会试着总结一下我在询问后的第一个小时内收到的许多评论中的一些内容:

  1. signness是严格二进制的(无论是有符号还是无符号),并且总是与endianness相反,endianness也有两个众所周知的变体(大和小),但也有较少知名的变体,如mixed/middle endian.未来可能会发明新的变种.
  2. 字节顺序访问多字节值时,字节顺序很重要.除了字节序之外还有很多方面会影响多字节结构的内存布局,所以这种访问通常是不受欢迎的.
  3. C++旨在针对抽象机器并最小化有关实现的假设数量.这个抽象机器没有任何字节序.

此外,现在我意识到签名和字节序不是一个完美的类比,因为:

  • endianness仅定义某些事物如何表示为二进制序列,但现在可以表示什么.双方big intlittle int会具有完全相同的数值范围.
  • 有符号性定义比特和实际值如何相互映射,但也影响可以表示的内容,例如-3不能用a表示unsigned char(假设char有8位)130不能用a表示signed char.

因此,改变某些变量的字节顺序永远不会改变程序的行为(除了逐字节访问),而签名通常会改变.

YSC*_*YSC 53

标准说的是什么

[intro.abstract]/1:

本文档中的语义描述定义了参数化的非确定性抽象机器.本文档不要求符合实现的结构.特别是,它们不需要复制或模拟抽象机器的结构.相反,需要符合实现来模拟(仅)抽象机器的可观察行为,如下所述.

C++无法定义字节序限定符,因为它没有字节序的概念.

讨论

关于signness和endianness之间的区别,OP写道

在我的理解中,字节序只是相同原理[(符号)]的变体:基于关于存储它的存储器的编译时信息对二进制模式的不同解释.

我认为签名既有语义也有代表性1.这[intro.abstract]/1意味着C++只关心语义,并且从不解决签名数字应该在内存2中表示的方式.实际上,"符号位"仅在C++规范中出现一次,并引用实现定义的值.
另一方面,字节序仅具有代表性方面:字节序没有任何意义.

随着C++ 20,std::endian出现.它仍然是实现定义的,但让我们测试主机的endian,而不依赖于基于未定义行为的旧技巧.


1)语义方面:有符号整数可以表示零以下的值; 代表性方面:例如,需要保留一点来传达正/负符号.
2)同样,C++从未描述应如何表示浮点数,经常使用IEEE-754,但这是由实现做出的选择,无论如何由标准强制执行:"浮动的值表示点类型是实现定义的".[basic.fundamental]/8


Use*_*ess 37

除了YSC的答案之外,让我们来看看你的示例代码,并考虑它可能实现的目标

struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};
Run Code Online (Sandbox Code Playgroud)

您可能希望这将精确指定与体系结构无关的数据交换(文件,网络,等等)的布局

但这不可行,因为有些事情仍然没有说明:

  • 数据类型的大小:你必须使用little int32_t,big int64_tint16_t分别,如果这就是你想要的
  • 填充和对齐,不能严格控制语言:使用#pragma__attribute__((packed))或其他一些编译器特定的扩展
  • 实际格式(1s或2s补码签名,浮点类型布局,陷阱表示)

或者,你可能只是想反映一些特定硬件的存储方式-但biglittle此不覆盖所有的可能性(仅仅是两种最常见的).

因此,提案是不完整的(它没有区分所有合理的字节排序安排),无效(它没有实现它所规定的),并且还有其他缺点:

  • 性能

    从本机字节顺序更改变量的字节顺序应该禁用算术,比较等(因为硬件无法在此类型上正确执行它们),或者必须静默注入更多代码,创建本机有序的临时工具.

    这里的论点不是手动转换为/从本机字节顺序更快,而是明确地控制它使得更容易最小化不必要的转换次数,并且更容易推断代码的行为方式,而不是转换是隐式的.

  • 复杂

    现在,为整数类型重载或专用的所有内容都需要两倍的版本,以应对传递非本机字节序值的罕见事件.即使这只是一个转发包装器(有几个转换器可以转换为原生顺序),但它仍然有很多代码没有明显的好处.

反对更改语言以支持这一点的最后一个论点是您可以轻松地在代码中执行此操作.更改语言语法是一件大事,并且不会像类型包装器那样提供任何明显的好处:

// store T with reversed byte order
template <typename T>
class Reversed {
    T val_;
    static T reverse(T); // platform-specific implementation
public:
    explicit Reversed(T t) : val_(reverse(t)) {}
    Reversed(Reversed const &other) : val_(other.val_) {}
    // assignment, move, arithmetic, comparison etc. etc.
    operator T () const { return reverse(val_); }
};
Run Code Online (Sandbox Code Playgroud)

  • 我真的没看到,Performance是如何论证的.如果你需要使用其他字节序,那么你出于某种原因这样做.如果没有对它们的语言支持,则需要对其进行编程.表现将是平等的.或者甚至,语言版本会更快,因为编译器可以为字节序转换实现优化代码.关于复杂性:是的,如果它是一个新类型,那么它很复杂.但如果类型相同怎么办?就像const限定符一样.它可以工作,没有严重的复杂性. (3认同)
  • @MartinBonner:但是为什么这个事实会阻止 C++ 使用小/大端限定符?它将帮助人们与这些表示进行交互。这并不意味着我们必须在语言中拥有所有可能的整数表示。如果您想在 PDP/11 上解释 PNG 图片,那么您必须编写一个用于读取大端数字的代码。取而代之的是,您可以只使用“big_endian int x;”,编译器将为您生成代码。 (2认同)