47

一位和我一起工作的实习生向我展示了他参加的关于字节顺序问题的计算机科学考试。有一个问题显示了一个 ASCII 字符串“My-Pizza”,学生必须展示该字符串如何在小端计算机的内存中表示。当然,这听起来像是一个技巧问题,因为 ASCII 字符串不受字节序问题的影响。

但令人震惊的是,实习生声称他的教授坚持认为该字符串将表示为:

P-yM azzi

我知道这不可能。在任何机器上都无法像这样表示 ASCII 字符串。但显然,教授在坚持这一点。所以,我写了一个小 C 程序并告诉实习生把它交给他的教授。

#include <string.h>
#include <stdio.h>

int main()
{
    const char* s = "My-Pizza";
    size_t length = strlen(s);
    for (const char* it = s; it < s + length; ++it) {
        printf("%p : %c\n", it, *it);
    }
}

这清楚地表明该字符串在内存中存储为“My-Pizza”。一天后,实习生回复我,告诉我教授现在声称 C 正在自动转换地址以以正确的顺序显示字符串。

我告诉他他的教授疯了,这显然是错误的。但只是为了检查我自己的理智,我决定将其发布在 stackoverflow 上,以便让其他人确认我在说什么。

所以,我问:谁在这里?

4

12 回答 12

30

毫无疑问,你是对的。

ANSI C 标准 6.1.4 指定字符串文字通过“连接”文字中的字符存储在内存中。

ANSI 标准 6.3.6 还规定了加法对指针值的影响:

当具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始数组元素的下标之差等于积分表达式。

如果归因于这个人的想法是正确的,那么当整数用作数组索引时,编译器也必须使用整数数学。许多其他的谬误也会产生,这些谬误留给想象。

人们可能会感到困惑,因为(与字符串初始值设定项不同),多字节字符常量(例如“ABCD”)字节序存储。

一个人可能对此感到困惑的原因有很多。正如其他人在这里所建议的那样,他可能误读了他在调试器窗口中看到的内容,其中内容已被字节交换以提高 int 值的可读性。

于 2009-10-14T18:25:30.950 回答
16

教授一头雾水。为了看到像“P-yM azzi”这样的东西,您需要使用一些内存检查工具,以“4 字节整数”模式显示内存,同时为您提供高阶每个整数的“字符解释”字节到低位字节模式。

当然,这与字符串本身无关。并且说字符串本身在 little-endian 机器上以这种方式表示完全是胡说八道。

于 2009-10-14T18:45:53.140 回答
16

字节序定义了多字节值中的字节顺序。字符串是单字节值的数组。因此,每个值(字符串中的字符)在 little-endian 和 big-endian 架构上都是相同的,并且 endianness 不会影响结构中值的顺序。

于 2016-05-05T19:06:51.757 回答
10

如果我们谈论的是一个每个字符使用 8 位的系统,那么教授就错了。

我经常使用实际使用 16 位字符的嵌入式系统,每个单词都是 little-endian。在这样的系统上,字符串“My-Pizza”确实会存储为“yMP-ziaz”。

但只要它是一个每字符 8 位的系统,该字符串将始终存储为“My-Pizza”,而与高级架构的字节序无关。

于 2009-10-14T18:23:31.330 回答
10

你可以很容易地证明编译器没有做这样的“魔法”转换,通过在一个不知道它被传递一个字符串的函数中进行打印:

int foo(const void *mem, int n)
{
    const char *cptr, *end;
    for (cptr = mem, end = cptr + n; cptr < end; cptr++)
        printf("%p : %c\n", cptr, *cptr);
}

int main()
{
    const char* s = "My-Pizza";

    foo(s, strlen(s));
    foo(s + 1, strlen(s) - 1);
}

或者,您甚至可以编译为汇编,gcc -S并最终确定没有魔法。

于 2009-10-14T20:40:06.803 回答
2

但令人震惊的是,实习生声称他的教授坚持认为该字符串将表示为:

P-yM阿齐

它将被表示为,表示为什么?向用户表示为 32 位整数转储?或在计算机内存中表示/布局为 P-yM azzi?

如果教授说“My-Pizza”将在计算机的内存中表示/布局为“P-yM azzi”,因为计算机是小端架构,请有人教教授如何使用调试器! 我认为这就是教授所有困惑的根源,我有一种暗示,教授不是编码员(不是我看不起教授),我认为他没有办法用代码证明他的意思了解了字节序。

也许教授大约一周前才学会了字节序的东西,然后他只是错误地使用了调试器,很快就对他对计算机的新独特见解感到高兴,然后立即向他的学生宣讲。

如果教授说机器的字节序与 ascii 字符串在内存中的表示方式有关,他需要清理他的行为,有人应该纠正他。

如果教授给出了一个例子,而不是根据机器的字节序如何在机器中表示/布局整数,他的学生可以理解他所教授的内容。

于 2009-10-15T07:35:15.890 回答
1

我假设教授试图通过类比来说明 endian/NUXI 问题,但是当您将其应用于实际字符串时,您是对的。不要因为他试图教给学生一个观点以及如何以某种方式思考问题这一事实而脱轨。

于 2009-10-14T18:27:30.080 回答
1

您可能感兴趣,可以在大端机器上模拟小端架构,反之亦然。编译器必须发出代码,当它取消引用它们时,它会自动与指针的最低有效位char*混淆:在 32 位机器上,您将映射 00 <-> 11 和 01 <-> 10。

因此,如果您0x01020304在大端机器上写入数字,并使用此地址转换读回该数字的“第一个”字节,那么您将获得最低有效字节,0x04. 即使硬件是大端的,C 实现也是小端的。

对于短时间访问,您需要一个类似的技巧。未对齐访问(如果支持)可能不引用相邻字节。您也不能对大于一个字的类型使用本机存储,因为它们在一次读回一个字节时会出现字交换。

然而,显然,小端机器并非一直都这样做,这是一项非常专业的要求,它会阻止您使用本机 ABI。听起来好像教授认为实际数字是“事实上的”大端,并且对小端架构的真正含义和/或它的内存是如何表示的深感困惑。

确实,字符串P-yM azzi在 32 位文件机器上“表示为”,但只有当“表示”是指“按地址递增的顺序读取表示的单词,但打印每个单词 big-endian 的字节”时。正如其他人所说,这是一些调试器内存视图可能会做的事情,因此它确实是内存内容表示。但是,如果您要表示单个字节,那么更常见的是按地址递增的顺序列出它们,无论单词存储的是 be 还是 le,而不是将每个单词表示为多字符文字。当然没有指针摆弄,如果教授选择的表示让他认为有一些,那么它误导了他。

于 2009-10-14T21:15:13.417 回答
0

另外,(而且我很久没有玩这个了,所以我可能错了)他可能在想 pascol,其中字符串表示为“打包数组”,IIRC 是打包成 4 字节整数的字符?

于 2009-10-14T21:52:37.877 回答
0

很难读懂教授的想法,当然编译器除了在 BE 和 LE 系统上将字节存储到相邻的递增地址之外什么也没做,但是以字大小的数字显示内存正常的,无论字大小是多少,我们把一千写成一千。不是 000,1。

$ cat > /tmp/pizza
My-Pizza^D
$ od -X /tmp/pizza
0000000 502d794d 617a7a69
0000010
$ 

为了记录,y == 79,M == 4d。

于 2009-10-15T00:48:39.337 回答
0

AFAIK,仅当您想将大值分解为小值时,字节序才有意义。因此,我认为 C 风格的字符串不会受到影响。因为它们毕竟只是字符数组。当您只读取一个字节时,从左侧或右侧读取它有什么关系?

于 2009-10-15T05:36:18.010 回答
0

我遇到了这个,觉得有必要把它弄清楚。这里似乎没有人解决bytes 和words 的概念或如何解决它们。一个字节是 8 位。一个是字节的集合。

如果计算机是:

  • 字节可寻址
  • 带有 4 字节(32 位)字
  • 字对齐
  • 内存被“物理”查看(不转储和字节交换)

那么确实,教授是正确的。他没有表明这一点证明他并不完全知道他在说什么,但他确实理解了基本概念。

字中的字节顺序:(a)Big Endian,(b)Little Endian

字中的字节顺序:(a)Big Endian,(b)Little Endian

Words 中的字符和整数数据:(a) Big Endian,(b) Little Endian

Words 中的字符和整数数据:(a) Big Endian,(b) Little Endian

参考

于 2013-01-28T20:37:10.290 回答