阅读关于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是最有意义的字节?
答案 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]
将保留最重要的字节。