10

我们都是可移植 C/C++ 程序的粉丝。

我们知道sizeof(char)orsizeof(unsigned char)总是1“字节”。但是那个1“字节”并不意味着一个 8 位的字节。它仅表示“机器字节”,其中的位数可能因机器而异。看到这个问题


假设您将 ASCII 字母“A”写入文件foo.txt。如今,在任何具有 8 位机器字节的普通机器上,这些位都会被写出:

01000001

但是,如果您要在具有 9 位机器字节的机器上运行相同的代码,我想这些位会被写出:

001000001

更重要的是,后一种机器可以将这 9 位写为一个机器字节:

100000000

但是如果我们要在以前的机器上读取这些数据,我们将无法正确地完成它,因为没有足够的空间。不知何故,我们必须首先读取一个机器字节(8 位),然后以某种方式将最后的 1 位转换为 8 位(一个机器字节)。


程序员如何正确调和这些东西?

我问的原因是我有一个写入和读取文件的程序,我想确保它在 5、10、50 年后不会中断。

4

9 回答 9

7

程序员如何正确调和这些东西?

无所作为。您提出了文件系统问题。

想象一下可怕的一天,当第一台 9 位机器启动时,准备重新编译您的代码并处理A您去年写入文件的 ASCII 字母。

为了确保 C/C++ 编译器可以合理地存在于这台机器上,这台新计算机的操作系统遵循 C 和 C++ 假定的相同标准,其中文件的大小以字节为单位。

...您的 8 位源代码已经存在小问题。每个源文件的大小只有大约九分之一的机会甚至可以存在于该系统上。

或者可能不是。就像我经常遇到的情况一样,Johannes Schaub-litb已经先发制人地引用了有关 C++ 源代码有效格式的标准

如有必要,物理源文件字符以实现定义的方式映射到基本源字符集(为行尾指示符引入换行符)。三元字符序列(2.3)被相应的单字符内部表示代替。任何不在基本源字符集 (2.2) 中的源文件字符都将替换为指定该字符的通用字符名。(实现可以使用任何内部编码,只要处理源文件中遇到的实际扩展字符,以及源文件中表示为通用字符名称的相同扩展字符(即使用 \uXXXX 表示法)等价的。)

“以实现定义的方式。” 这是个好消息...只要有某种方法可以将您的源代码转换为可以在这台机器上表示的任何 1:1 格式,您就可以编译它并运行您的程序。

所以这就是你真正的问题所在。如果这台计算机的创建者好心提供了一个实用程序来对 8 位 ASCII 文件进行位扩展,以便它们实际上可以存储在这台新机器上,那么A您很久以前编写的 ASCII 字母已经没有问题了。如果没有这样的实用程序,那么您的程序已经需要维护,并且您无法采取任何措施来阻止它。

编辑:较短的答案(解决已被删除的评论)

问题询问如何处理特定的9 位计算机...

  • 使用没有向后兼容的 8 位指令的硬件
  • 使用不使用“8 位文件”的操作系统。
  • 使用 C/C++ 编译器打破了 C/C++ 程序过去编写文本文件的方式。

Damian Conway经常重复引用 C++ 与 C 的比较:

“C++ 试图防范墨菲,而不是马基雅维利。”

他描述的是其他软件工程师,而不是硬件工程师,但意图仍然是合理的,因为推理是一样的。

C 和 C++ 都以某种方式标准化,要求您假设其他工程师想要玩得好。您的马基雅维利式计算机不会对您的程序构成威胁,因为它完全是对 C/C++ 的威胁。

回到你的问题:

程序员如何正确调和这些东西?

你真的有两个选择。

  • 接受您描述的计算机不适合 C/C++ 世界
  • 接受 C/C++ 不适合可能在您描述的计算机上运行的程序
于 2013-01-20T18:49:44.207 回答
3

唯一可以确定的方法是将数据存储在文本文件中,将数字存储为数字字符串,而不是一些位。使用 UTF-8 和 base 10 的 XML 在可移植性和可读性方面应该是相当不错的总体选择,因为它定义明确。如果您想偏执,请保持 XML 足够简单,以便在紧要关头可以使用简单的自定义解析器轻松解析,以防您的假设计算机无法使用真正的 XML 解析器。

当解析数字时,它比适合您的数字数据类型的要大,那么,这是您需要在上下文中认为合适的情况下处理的错误情况。或者使用“big int”库,它可以处理任意大的数字(当然,与“本机”数字数据类型相比,性能下降了一个数量级)。

如果您需要存储位字段,则存储位字段,即位数,然后以任何格式存储位值。

如果您有特定的数字范围,则存储该范围,以便您可以明确检查它们是否适合可用的数字数据类型。

字节是非常基本的数据单元,因此您无法真正在具有不同位数的存储之间传输二进制数据,您必须进行转换,并且要转换您需要知道数据的格式,否则您根本无法转换多字节值正确。

添加实际答案:

  • 在您的 C 代码中,不要处理字节缓冲区,除非在隔离函数中,然后您将根据 CPU 架构进行适当修改。例如,.JPEG 处理函数将采用以未指定方式包装图像数据的结构,或用于从中读取图像的文件名,但绝不会采用原始char*​​到字节缓冲区。
  • 将字符串包装在不采用编码的容器中(大概它将在 8 位字节机器上使用 UTF-8 或 UTF-16,在 9 位字节机器上可能当前使用非标准UTF-9 或 UTF-18等) .
  • 将从外部源(网络、磁盘文件等)读取的所有内容包装到返回本机数据的函数中。
  • 创建不会发生整数溢出的代码,并且不要依赖任何算法中的溢出行为。
  • ~0使用(而不是0xFFFFFFFF或其他东西)定义全为位掩码
  • 对于大多数不需要整数的数字存储,首选 IEEE 浮点数,因为它们独立于 CPU 架构。
  • 不要将持久性数据存储在可能需要转换的二进制文件中。而是使用 UTF-8 中的 XML(可以在不破坏任何内容的情况下转换为 UTF-X,用于本机处理),并将数字作为文本存储在 XML 中。
  • 与不同的字节顺序相同,除了更多,唯一可以确定的方法是将您的程序移植到具有不同位数的实际机器上,并运行全面测试。如果这真的很重要,那么您可能必须首先实现这样的虚拟机,并为它移植 C 编译器和所需的库,如果您找不到其他库的话。即使是仔细(=昂贵)的代码审查也只会让你走上正轨。
于 2013-01-18T16:58:39.207 回答
2

如果您打算为 Quantum Computers 编写程序(我们将在不久的将来购买),那么请开始学习 Quantum Physics 并参加有关编程的课程。

除非您计划在不久的将来使用布尔计算机逻辑,否则..我的问题是您将如何确保今天可用的文件系统明天将不一样?或者用 8 位二进制存储的文件如何在明天的文件系统中保持可移植性?

如果你想让你的程序代代相传,我的建议是创建你自己的计算机,使用你自己的文件系统和你自己的操作系统,并根据明天的需求改变接口。

我的问题是,我几年前编写的计算机系统(摩托罗拉 68000)对于普通大众来说已经不存在了,而且该程序严重依赖机器的字节顺序和汇编语言。不再便携了:-(

于 2013-01-18T12:38:28.007 回答
2

如果您正在谈论写入和读取二进制数据,请不要打扰。今天没有可移植性保证,除了你从程序中写入的数据可以被用相同编译器编译的相同程序读取(包括命令行设置)。如果您正在谈论编写和阅读文本数据,请不要担心。有用。

于 2013-01-18T14:05:27.910 回答
2

第一:可移植性最初的实际目标是减少工作量;因此,如果要实现相同的最终结果,可移植性比不可移植性需要更多的努力,那么在这种情况下编写可移植代码不再是有利的。不要仅仅出于原则而将“可移植性”作为目标。在您的情况下,带有关于磁盘格式的详细记录说明的非便携式版本是一种更有效的面向未来的方法。尝试编写以某种方式迎合任何可能的通用底层存储格式的代码可能会使您的代码几乎难以理解,或者维护它会因此而失宠(如果没有人愿意,无需担心未来的验证)无论如何都要在 20 年后使用它)。

第二:我认为您不必担心这一点,因为在 9 位机器(或类似机器)上运行 8 位程序的唯一现实解决方案是通过虚拟机。

在不久的或遥远的将来,任何使用 9 位以上机器的人都极有可能能够启动传统的 x86/arm 虚拟机并以这种方式运行您的程序。从现在开始的 25 到 50 年硬件应该不会有任何问题,为了执行单个程序而运行整个虚拟机;并且该程序可能仍会比现在在当前的本机 8 位硬件上更快地加载、执行和关闭。 (事实上​​,今天的一些云服务已经倾向于启动整个虚拟机来服务单个任务)

我强烈怀疑这是任何 8 位程序在 9 位/其他位机器上运行的唯一方法,因为其他答案中提到了简单加载和解析8 位源代码所固有的基本挑战或8 位二进制可执行文件。

它可能不像“高效”,但它会起作用。当然,这也假设 VM 将具有某种机制,通过该机制可以将 8 位文本文件从虚拟磁盘导入和导出到主机磁盘。

但是,正如您所看到的,这是一个远远超出您的源代码的巨大问题。最重要的是,最有可能的是,在新硬件上更新/修改甚至从头开始重新实现您的程序会更便宜、更容易,而不是费心试图解决这些晦涩难懂的可移植性问题——正面。考虑到它的行为几乎肯定需要比仅仅转换磁盘格式更多的努力。

于 2013-01-21T00:48:15.780 回答
1

8 位字节将一直保留到时间结束,所以不要担心。会有新的类型,但这种基本类型永远不会改变。

于 2013-01-18T12:29:33.787 回答
1

有点晚了,但我无法抗拒这一点。预测未来是艰难的。预测计算机的未来对你的代码来说可能比过早的优化更危险。

简短答案
虽然我以 9 位系统如何处理 8 位字节的可移植性来结束这篇文章,但这种经历也让我相信 9 位字节系统将永远不会在通用计算机中再次出现。

我的预期是,未来的可移植性问题将是硬件具有至少 16 位或 32 位访问权限,使得 CHAR_BIT 至少为 16。此处的仔细设计可能有助于处理任何意外的 9 位字节。

/的问题。读者:有没有人知道今天生产中使用 9 位字节或补码算法的通用 CPU?我可以看到嵌入式控制器可能存在的位置,但仅此而已。

长答案
早在 1990 年代,计算机和 Unicode 的全球化让我期望 UTF-16 或更大,以推动每字符位数的扩展:C 中的 CHAR_BIT。但由于遗留的寿命比一切都长,我也期望 8 位字节至少只要计算机使用二进制文件,它就仍然是一个行业标准。

BYTE_BIT:每字节位数(流行,但不是我所知道的标准)
BYTE_CHAR:每字符字节数

C 标准没有解决消耗多个字节的字符。它允许它,但没有解决它。

3.6 字节: ( C11 标准 ISO/IEC 9899:201x最终草案)
数据存储的可寻址单元,大到足以容纳执行环境的基本字符集的任何成员。

注 1:可以唯一地表示对象的每个单独字节的地址。

注 2:一个字节由一个连续的比特序列组成,其数量由实现定义。最低有效位称为低位;最高有效位称为高位。

在 C 标准定义如何处理大于 1 的 BYTE_CHAR 值之前,我不是在谈论“宽字符”,这是可移植代码必须解决的主要因素,而不是更大的字节。CHAR_BIT 为 16 或 32 的现有环境是要研究的。ARM 处理器就是一个例子。我看到了两种读取外部字节流的基本模式,开发人员需要从中选择:

  • 解压:将一个 BYTE_BIT 字符转换为本地字符。当心符号扩展。
  • 打包:将 BYTE_CHAR 字节读入本地字符。

便携式程序可能需要一个 API 层来解决字节问题。为了即时创造和想法,我保留将来攻击的权利:

  #define BYTE_BIT 8 // 每字节位数
  #define BYTE_CHAR (CHAR_BIT/BYTE_BIT) //bytes-per-char

  size_t byread(void *ptr,
                size_t size, // BYTE_BIT 字节数
                int packing, // 每个字符要读取的字节数
                                 //(符号扩展为负数)
                文件*流);

  size_t bywrite(void *ptr,
                size_t 大小,
                内包装,
                文件*流);
  • size要传输的 BYTE_BIT 字节数。
  • packing每个char字符传输的字节数。虽然通常为 1 或 BYTE_CHAR,但它可以指示外部系统的 BYTE_CHAR,它可以小于或大于当前系统。
  • 永远不要忘记字节顺序冲突。

避免 9 位系统:
我之前为 9 位环境编写程序的经验让我相信我们不会再看到这种情况,除非你碰巧需要一个程序在某个地方的真正旧的遗留系统上运行。可能在32/64 位系统上的9 位 VM中。自 2000 年以来,我有时会快速搜索但没有看到对旧 9 位系统当前后代的引用。

在我看来,任何非常出乎意料的未来通用 9 位计算机都可能具有 8 位模式或 8 位 VM(@jstine)来运行程序。唯一的例外是专门构建的嵌入式处理器,通用代码无论如何都不太可能在其上运行。

在过去,一台 9 位机器是 PDP/15。十年与这种野兽的克隆搏斗让我从没想过会再次出现 9 位系统。我对为什么遵循的首选:

  • 额外的数据位来自于抢夺核心内存中的奇偶校验位。旧的 8 位内核带有一个隐藏的奇偶校验位。每个制造商都这样做了。一旦内核变得足够可靠,一些系统设计人员会迅速将现有的奇偶校验转换为数据位,以便在非 MMU 机器较弱的时候获得更多的数字能力和内存地址。现在的内存技术没有这么奇偶校验位,机器没那么弱,64位内存这么大。所有这些都应该使设计更改的成本效益低于当时的更改。
  • 在 8 位和 9 位架构之间传输数据,包括现成的本地 I/O 设备,而不仅仅是其他系统,一直是一个痛苦的过程。同一系统上的不同控制器使用了不兼容的技术:
    1. 使用 18 位字的低 16 位。
    2. 使用 9 位字节的低 8 位,其中额外的高位可能设置为从奇偶校验敏感设备读取的字节的奇偶校验。
    3. 将三个 8 位字节的低 6 位组合成 18 位二进制字。
    一些控制器允许在运行时选择 18 位和 16 位数据传输。您的程序会发现什么样的未来硬件和支持系统调用,只是无法提前预测。
  • 连接到 8 位互联网本身就足以扼杀任何人的 9 位梦想。当时他们侥幸逃脱,因为当时机器之间的互联程度较低。
  • 在字节寻址存储中拥有 2 位的偶数倍数以外的东西会带来各种麻烦。示例:如果您想要一个 8 位字节中包含数千位的数组,您可以unsigned char bits[1024] = { 0 }; bits[n>>3] |= 1 << (n&7);. 要完全打包 9 位,您必须进行实际除法,这会带来可怕的性能损失。这也适用于每字字节数。
  • 任何没有在 9 位字节硬件上实际测试过的代码都可能在第一次实际冒险进入意想不到的 9 位字节领域时失败,除非代码非常简单,以至于将来为 9 位重构它只是一个小问题. 之前的 byread()/bywrite() 在这里可能会有所帮助,但它可能需要额外的 CHAR_BIT 模式设置来设置传输模式,返回当前控制器如何排列请求的字节。

为了完整起见,任何想要为教育体验担心 9 位字节的人可能还需要担心自己的补码系统会回来;其他似乎已经死的当之无愧的死亡(两个零:+0和-0,是持续噩梦的根源……相信我)。那时 9 位系统似乎经常与补码运算配对。

于 2013-01-21T09:17:49.357 回答
1

我认为未来计算机中非 8 位字节的可能性很低。它需要重写这么多,但收益却很少。但如果发生...

通过在本机数据类型中进行所有计算并仅重写输入,您将为自己省去很多麻烦。我正在描绘类似的东西:

template<int OUTPUTBITS, typename CALLABLE>
class converter {
  converter(int inputbits, CALLABLE datasource);
  smallestTypeWithAtLeast<OUTPUTBITS> get();
};

请注意,这可以在将来存在这样的机器时编写,因此您现在无需执行任何操作。或者,如果您真的很偏执,请确保在 OUTPUTBUTS==inputbits 时只调用数据源。

于 2013-01-23T18:46:47.253 回答
-1

在编程语言中,一个字节总是 8 位。因此,如果字节表示在某些机器上具有 9 位,无论出于何种原因,都由 C 编译器来协调它。只要您使用 char 编写文本,例如,如果您将“A”写入/读取到文件,您将只写入/读取文件的 8 位。所以,你不应该有任何问题。

于 2013-01-24T04:12:13.860 回答