当endianess确实重要时 - 投射操作

时间:2012-10-10 18:08:58

标签: c++ memory types byte endianness

  

可能重复:
  When does Endianness become a factor?

阅读关于endianess的这个tuto,我将看到这个有关endianess重要的例子。它是关于写一个填充了1和0的char *,然后它可以转换为short,结果取决于endianess,很少或很大。这是引用的例子。

  

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

x = *(short *) endian;
     

x的价值是多少?我们来看看这段代码在做什么。   您正在创建一个包含两个字节的数组,然后转换该数组   两个字节成一个短。通过使用数组,你基本上被迫   某个字节顺序,您将看到系统如何处理   那两个字节。如果这是一个小端系统,0和1是   向后解释,看起来好像是0,1。由于高字节是   0,无关紧要,低字节为1,所以x等于1   另一方面,如果它是一个大端系统,高字节是1和   x的值是256。

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

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

4 个答案:

答案 0 :(得分: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,所以是endian的第一个字节的地址。

    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>

然后我们准备对运营商的呼叫&lt;&lt;与相关的论点没有任何更多的检查。就这样,程序将不再进行任何检查。变量的类型与机器完全无关。

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

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

答案 1 :(得分:0)

如果您尝试强制转换为大于数组的大小,则会出现未定义的行为。它可能会尝试读取阵列后面的内存内容,但结果不能得到保证,也不需要保持一致。

答案 2 :(得分: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。

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

答案 3 :(得分: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]被认为是最重要的字节。)

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