2

可能的重复:
字节顺序何时成为一个因素?

阅读这个关于字节序的tuto,我落在这个字节序确实很重要的例子上。它是关于写一个用 1 和 0 填充的 char*。然后它可以转换为一个短的,结果取决于字节序,小或大。这是引用的示例。

unsigned char endian[2] = {1, 0}; 短 x;

x = *(short *) endian;

x 的值是多少?让我们看看这段代码在做什么。您正在创建一个包含两个字节的数组,然后将该包含两个字节的数组转换为一个短数组。通过使用数组,您基本上强制了某个字节顺序,您将看到系统如何处理这两个字节。如果这是一个小端系统,则 0 和 1 被向后解释并被视为 0,1。由于高字节为0,无所谓,低字节为1,所以x等于1。反之,如果是big-endian系统,高字节为1,x的值为256.

我想知道:当您使用给定数量的内存字节分配(这里是两个字节)实例化一个数组时,只要数组已分配了数字,如何转换为任何类型(short,int ...)对应于这个字节的字节数?如果没有分配足够的内存来“包含这种类型”,是否仍会读取下一个内存地址?例如,如果我想将 endian 转换为 long,这会被执行,从 endian 的开头读取四个字节,还是会失败?

然后,关于字节序的问题:这是处理器习惯于在内存中写入字节的特性,其中最重要的字节位于最低内存位置(大端)或最高内存位置(小端)。在这种情况下,分配了一个包含两个单字节元素的数组。为什么说 1 是最有意义的字节?

4

4 回答 4

2

不要忘记编译器只会编写汇编代码。如果您忽略编译器的所有警告,您可以检查编译器生成的汇编代码并找出真正发生的情况。

我采用了这个简单的程序:

#include <iostream>

int main()
{
    unsigned endian[2] = { 0, 0 } ;
    long * casted_endian = reinterpret_cast<long*>( endian );
    std::cout << *casted_endian << std::endl;
}

我使用objdump. 让我们破译一下。

 804879c:   55                      push   %ebp
 804879d:   89 e5                   mov    %esp,%ebp
 804879f:   83 e4 f0                and    $0xfffffff0,%esp
 80487a2:   83 ec 20                sub    $0x20,%esp

这些行只是函数的序言,忽略它们。

    unsigned endian[2] = { 0, 0 } ;
 80487a5:   c7 44 24 14 00 00 00    movl   $0x0,0x14(%esp)
 80487ac:   00 
 80487ad:   c7 44 24 18 00 00 00    movl   $0x0,0x18(%esp)
 80487b4:   00 

从这两行中,您可以看到 (0x14)%esp 初始化为 0。因此您知道数组endian在堆栈上,位于寄存器 %ESP(堆栈指针)+ 0x14 中的地址。

    long * casted_endian = reinterpret_cast<long*>( endian );
 80487b5:   8d 44 24 14             lea    0x14(%esp),%eax

LEA 只是一个算术运算。EAX 现在包含 %ESP+0x14,它是堆栈上数组的地址。

 80487b9:   89 44 24 1c             mov    %eax,0x1c(%esp)

在地址 ESP + 0x1c (这是变量的位置casted_endian)我们放置 EAX,因此是字节序的第一个字节的地址。

    std::cout << *casted_endian << std::endl;
 80487bd:   8b 44 24 1c             mov    0x1c(%esp),%eax
 80487c1:   8b 00                   mov    (%eax),%eax
 80487c3:   89 44 24 04             mov    %eax,0x4(%esp)
 80487c7:   c7 04 24 40 a0 04 08    movl   $0x804a040,(%esp)
 80487ce:   e8 1d fe ff ff          call   80485f0 <std::ostream::operator<<(long)@plt>

然后我们使用相关参数准备对 operator << 的调用,而不再进行任何检查。就是这样,程序将不再进行任何检查。变量的类型与机器完全无关。

现在,当读取不在数组中operator<<的部分时,可能会发生两件事。*casted_endian

它的地址要么在当前映射的内存页中,要么不在。在第一种情况下,operator<<将阅读该地址的任何内容而不会抱怨。这可能会在屏幕上写一些奇怪的东西。在第二种情况下,您的操作系统会抱怨程序试图读取他无法读取的内容,并引发中断。这就是著名的分段错误。

于 2012-10-10T18:28:36.820 回答
0

If you try to cast to a size larger than the array, you'll get undefined behavior. It will probably try to read the contents of the memory that comes right after the array, but that result is not guaranteed and need not be consistent either.

于 2012-10-10T18:17:02.650 回答
0

我的天。我在这里要说的是为什么这适用于大多数架构,但我不能说这实际上有多少是标准的。

你在那里做的是将数组endian转换成一个短的。现在,数组基本上是指针,数组的名称实际上保存了第一个元素的地址。唯一真正的区别是数组包含更多有用的元数据,并且数组上的某些操作是不同的(sizeof例如)。然后,您将使用该地址 ( endian) 并从中创建一个short指针。内存地址保持不变,只是您对指向的数据进行了不同的解释。然后,您将取消引用此指针以取回该值,并将其分配给x.

一个快速的旁注。这可能不适用于所有系统。在 C 中,int仅定义为与架构的本机字大小一样宽(x86 上为 4 个字节,x86_64 上为 8 个字节)。short然后仅定义为比 int 短(或等于,如果内存服务正确)。因此,该代码将在 8 位架构上失败。为此,目标数据类型的字节大小必须等于或小于数组的大小。

同样,long只是定义为比 长int,通常在 x86 和 x86_64 上分别为 8 或 16 个字节。在这种情况下,此代码将适用于 x86:

unsigned char endian[8] = {1,2,3,4,5,6,7,8};
long x = *(long*)endian;

无论如何,处理器的字节顺序完全取决于处理器。x86 是小端(并且基本上开始了 LE 设备的约定,IIRC)。SPARC 是大端(直到 9,它可以是两者)。ARM 和 MIPS 也是可配置的,Microblaze 取决于使用的总线(AXI 或 PLB)。无论如何,字节序不仅限于处理器,在与硬件或其他计算机通信时也是一个问题。

对于您的最后一个问题,最重要的字节被称为因为该值表示大于较小字节可以表示的最大值。在 16 位字的情况下,最低有效字节可以表示 0-255,最高有效字节可以表示 256-65535。

在任何情况下,除非您正在进行低级系统编程(我的意思是直接修改内存)或编写通信协议,否则您永远不需要担心字节顺序。

于 2012-10-10T18:24:42.090 回答
0
unsigned char endian[2] = {1, 0};
short x;

x = *(short *) endian;

此代码具有未定义的行为。结果可以x设置为 1、256、4000,或者程序可能会崩溃,或者其他任何事情都可能合法地发生。即使不考虑数组是否足够大以适应它所转换的类型,情况也是如此。

这是对代码的重写,以使其合法并按照作者的意图进行。

unsigned char endian[sizeof(short)] = {1};
short x;
std::memcpy(&x, endian, sizeof(short));

如果您要编写试图int从该数组中取出一个的代码,那么它将访问合法数组边界之外,您将再次遇到未定义的行为;什么事情都可能发生。

在这种情况下,分配了一个包含两个单字节元素的数组。为什么说 1 是最有意义的字节?

(我猜你的意思是问为什么endian[1]据说保存最重要的字节。)

因为在那个例子中,系统是小端的,正如你所说,小端的定义是内存位置中具有最高地址的最高有效字节。endian[1]具有比endian[0]因此endian[1]保存最高有效字节更高的地址。

于 2012-10-10T18:33:11.823 回答