为什么C++中没有像符号一样的字节序修饰符?

Why isn't there an endianness modifier in C++ like there is for signedness?

本文关键字:字节 一样 C++ 符号 为什么      更新时间:2023-10-16

(我想这个问题可能适用于许多类型的语言,但我选择以C 为例。)

为什么没有办法写:

struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};

指定特定成员,变量和参数的末端?

与签名的比较

我了解变量的类型不仅确定了使用多少字节来存储一个值,还可以在执行计算时如何解释这些字节。

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

signed char s;
unsigned char u;

,但相同的二进制序列可以不同地解释,例如当分配给s时,11111111表示-1,而分配给u时为255。同一计算中签名和未签名变量时,编译器(主要)负责适当的转换。

在我的理解中,末日只是相同原理的一种变体:基于编译时间信息的二进制模式的不同解释。

以一种允许低级编程的打字语言具有该功能,这似乎很明显。但是,这不是C,C 或我所知道的任何其他语言的一部分,我没有在网上找到任何讨论。

更新

我会尝试总结一些提出的评论中的许多评论:

  1. 签名是严格的二进制(签名或未签名),并且与Endianness相反,Endianness还具有两个众所周知的变体(大和小),但也具有较不知名的变体,例如混合/中端。将来可能会发明新的变体。
  2. 访问多字节值字节时的endianness很重要。除了末日之外,还有许多方面影响了多字节结构的内存布局,因此这种访问大多是不建议的。
  3. C 旨在针对抽象的机器,并最大程度地减少有关实现的假设的数量。这款抽象的机器没有任何 dentianness。

另外,现在我意识到签名和尼迪亚人不是一个完美的类比,因为:

  • endianness仅定义了某物表示为二进制序列,但现在可以代表什么。big intlittle int都具有完全相同的值范围。
  • 签名定义位和实际值彼此映射,但也影响可以代表的内容,例如-3不能用unsigned char表示,并且(假设char具有8位)130不能由signed char表示。

,改变某些变量的终极性永远不会改变程序的行为(字节访问除外),而签名的更改通常会改变。

标准说

[intro.abstract]/1

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

c 无法定义Endianness预选赛,因为它没有底色的概念。

讨论

关于标志性和尼亚尼斯之间的区别,OP写道

在我的理解中,末日只是相同原理的变体((符号)]:基于编译时间信息的二进制模式的不同解释。

我会争辩的符号具有语义和代表性方面 1 [intro.abstract]/1暗示的是,C 仅关心语义,并且切勿解决在内存中应表示签名的数字的方式 2 。实际上," sign bit" 在C 规格中仅出现一次,并参考实现定义的值。
另一方面,Endianness只有一个代表性的方面: endianness毫无意义

使用C 20,出现std::endian。它仍然是实现定义的,但让我们在不依赖于未定义的行为的旧技巧的情况下测试主机的末日。


1)语义方面:符号整数可以表示零以下的值;代表性方面:例如,需要保留一些积极/负符号。
2)在同一静脉中,C 永远不会描述应如何表示浮点数,经常使用IEEE-754,但这是实现的选择,无论如何在标准中强制执行: [basic.fundamental]/8 "浮点类型的值表示是实现定义的"

除了ysc的答案之外,获取您的示例代码,并考虑其目标

的目标
struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};

您可能希望这可以准确地指定独立于架构的数据互换(文件,网络等)

的布局

但这可能无法正常工作,因为几件事仍然未指定:

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

另外,您可能只想反映某些指定硬件的端性 - 但是biglittle在这里不涵盖所有可能性(只是两个最常见的可能性)。

因此,该建议是不完整的(没有区分所有合理的字节顺序安排),无效(它没有实现其设定的内容),并且有其他缺点:

  • 性能

    从本机字节排序更改变量的endianges,应禁用算术,比较等(因为硬件不能正确地在此类型上执行它们),或者必须默默地注入更多代码,创建本身创建的代码 - 订购的临时工。

    此处的参数不是在向本机字节顺序中手动转换的速度 ,而是明确控制它,使其更容易最大程度地减少不必要的转换的数量,并且更易于推理。代码将如何行为,而不是隐含的转换。

  • 复杂性

    现在,整数类型的所有内容都超载或专业化,需要两倍的版本,以应对罕见的事件,即它通过了非本地式价值。即使那只是一个转发包装器(有几个演员阵容可以转换为本机订购),它仍然是很多代码。

反对更改语言以支持此的最终论点是,您可以在代码中轻松地进行。更改语言语法是很重要的,并且与类型包装器这样的东西没有任何明显的好处:

// 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_); }
};

整数(作为数学概念)具有正数和负数的概念。这个符号的抽象概念在硬件中具有许多不同的实现。

endianness不是数学概念。Little-endian是一个硬件实现技巧,可以在具有16或32位寄存器和8位内存总线的微处理器上提高多字节组合整数算术的性能。它的创建需要使用Big-Endian一词来描述在寄存器和内存中具有相同字节订单的所有其他内容。

c抽象机器包括签名和未签名的整数的概念,无需细节 - 而无需二十个填充算术,8位字节或如何将二进制数存储在内存中。

ps:我同意网络或内存/存储中的二进制数据兼容性是PIA。

这是一个很好的问题,我经常有认为这样的事情将很有用。但是,您需要记住,C的目标是平台独立性和endianness仅在将类似结构转换为某些基础内存布局时才很重要。例如,当您将UINT8_T缓冲区施放到INT中时,可能会发生这种转换。虽然Endianness修饰符看起来整洁,但程序员仍然需要考虑其他平台差异,例如INT尺寸和结构对齐和包装。对于防御性编程,您想在内存缓冲区中找到某些变量或结构如何表示谷物时,最好是代码明确的转换功能,然后让编译器优化器为每个受支持的平台生成最有效的代码。

endianness本质上不是数据类型的一部分,而是其存储布局。

因此,它实际上不类似于签名/未签名,而是更像结构中的位字段宽度。与这些相似,它们可用于定义二进制API。

所以你会有

之类的东西
int ip : big 32;

将同时定义存储布局和整数大小,从而将其交给编译器,以便在将字段匹配到其访问权限方面做到最好的工作。对我而言,这并不明显。

简短答案:如果不可能在涉及ints的算术表达式中使用算术表达式中的对象,则这些对象不应是整数类型。而且毫无意义地允许在同一表达中加入和乘法。

更长的答案:

正如某人提到的那样,endianness是特定于处理器的。这确实意味着这是数字用作机器语言中数字时的表示(作为地址和操作数/算术操作结果)。

同样的标牌是"有点"。但不是相同的程度。从语言语义标牌转换为被处理器所接受的标牌是使用数字作为数字的方法。从大型居民转换为小型和反面是使用数字作为数据(通过网络发送或表示元数据关于通过网络发送的数据(例如有效载荷长度)的数据)需要做的事情。

话虽如此,这个决定似乎主要是由用例驱动的。另一方面,有一个很好的务实理由忽略某些用例。实用主义是出于以下事实,即尼斯尼斯转换比大多数算术操作更昂贵。

如果一种语言具有将数字保持在较小的语义的语义,则可以通过在程序中强迫数字少数数字来射击脚,从而使自己的数字发挥作用。如果在一台小型机器上开发,这种对底色的执行将是一个毫无疑问的。但是,当移植到大型机器上时,会有很多意外的放缓。而且,如果所讨论的变量既用于算术和网络数据,则将使代码完全不可存储。

没有这些末日语义或迫使它们明确地是针对编译器的特定迫使开发人员通过将数字思考为"读取"或从网络格式的"读取"数字的心理步骤。这将使在网络和主机字节顺序之间来回转换的代码,在算术操作的中间,笨拙,较少可能是懒惰开发人员的首选写作方式。

并且由于发展是人类的努力,因此不舒服的选择是一件好事(TM)。

edit :这是一个可以做得不好的示例:假设引入了little_endian_int32big_endian_int32类型。那么little_endian_int32(7) % big_endian_int32(5)是一个恒定的表达式。结果是什么?数字是否会隐式转换为本地格式?如果没有,结果的类型是什么?更糟糕的是,结果的值是什么(在这种情况下,每台机器可能应该相同)?

再次,如果将多字节数用作普通数据,则字符阵列也一样好。即使它们是"端口"(实际上是表格或其哈希值的查找值),它们只是字节而不是整数类型(可以在其上进行算术)的序列。

现在,如果将允许的算术操作限制在明显的数字上仅限制为指针类型的操作,那么您可能有更好的可预测性案例。然后,即使myPort被称为大型Endian机器上的little_endian_int16myPort + 5实际上也很有意义。lastPortInRange - firstPortInRange + 1相同。如果算术对指针类型的作用如它所做的那样,那么这将执行您的期望,但是firstPort * 10000是非法的。

那么,当然,您可以参与是否有任何可能的好处来证明功能的膨胀是合理的。

从务实的程序员的角度搜索堆栈溢出,值得注意的是,这个问题的精神可以通过公用事业库来回答。Boost有这样的库:

http://www.boost.org/doc/libs/1_65_1/libs/endian/doc/index.html

库的功能最喜欢讨论中的语言功能是一组算术类型,例如big_int16_t

它符合标准和/或因为编译器实施者从未感到需要。

也许您可以向委员会提出建议。我认为在编译器中实施它并不难:编译器已经提出了不是目标机器基本类型的基本类型。

C 的开发是所有C 编码器的事件。

@schimmel。不要倾听证明现状合理的人!证明这种缺席的所有引用的论点不仅仅是脆弱的。学生逻辑学家可以在不了解计算机科学的情况下找到自己的不一致。只是提出它,只是不在乎病理保守主义者。(建议:提出新类型而不是预选赛,因为unsignedsigned关键字被视为错误)。

endianness是特定于机器特定的编译器,而不是平台独立性的支持机制。标准 - 是一个抽象,不考虑施加使事情"容易"的规则 - 其任务是在编译器之间创建相似性,使程序员可以为其代码创建"平台独立性" - 如果他们选择执行所以。

最初,市场份额平台之间存在很多竞争,而且编译器最常由微处理器制造商编写为专有工具,并支持特定硬件平台上的操作系统。英特尔可能不是很关心支持摩托罗拉微处理器的编译器。

c毕竟是由贝尔实验室发明的,以重写Unix。