整数中的字节顺序控制

硬件层面的各种信息系统在内存中表示数字时,使用不同的字节顺序。因此,当将 MQL 程序与“外部世界”整合时,尤其是当实现网络通信协议或读写常见格式的文件时,可能需要更改字节顺序。

Windows 计算机应用小端模式(从最低有效字节开始),即在为变量分配的内存单元中,首先从最低字节开始,然后是具有更高位的字节,以此类推。互联网上则广泛使用另一种大端模式(从最高位,即最有效字节开始)。在这种情况下,内存单元中的第一个字节是高位字节,而最后一个字节是低位字节。这种顺序类似于我们在日常生活中“从左到右”写数字的方式。例如,值 1234 从 1 开始,代表千位,后面是 2 代表百位,3 代表十位,最后的 4 就是个位四(低序)。

我们看看 MQL5 中的默认字节序。为此,我们将使用 MathSwap.mq5 脚本。

该脚本描述了允许你将整数转换为字节数组的连接模式:

template<typename T>
union ByteOverlay
{
   T value;
   uchar bytes[sizeof(T)];
   ByteOverlay(const T v) : value(v) { }
   void operator=(const T v) { value = v; }
};

该代码允许你以可视化的方式将数字分为字节,并使用数组中的索引枚举这些字节。

OnStart 中,我们以值 0x12345678 描述 uint 变量(注意,数字为十六进制;在该记数法中,它们与字节边界完全对应:每 2 个数位是一个单独的字节)。我们将数字转换为数组,并将其输出到日志。

void OnStart()
{
   const uint ui = 0x12345678;
   ByteOverlay<uintbo(ui);
   ArrayPrint(bo.bytes); // 120  86  52  18 <==> 0x78 0x56 0x34 0x12
   ...

ArrayPrint 函数不能打印十六进制的数字,因此我们看到的是它们的十进制表示,但可轻松将它们转换为十六进制,以确保它们匹配原始字节。直观来看,它们处于逆反顺序:即,在数组中的 0 号索引是 0x78,然后是 0x56、0x34 和 0x12。很明显,这一顺序是从最低有效字节开始(实际上,我们处于 Windows 环境中)。

现在让我们来熟悉函数 MathSwap,MQL5 提供该函数用于更改字节顺序。

integer MathSwap(integer value)

该函数返回一个整数,其中传入参数的字节顺序被反转。该函数使用 ushort/uint/ulong 类型的参数(即大小为 2、4、8 字节)。

我们以实操的方式来测试该函数:

   const uint ui = 0x12345678;
   PrintFormat("%I32X -> %I32X"uiMathSwap(ui));
   const ulong ul = 0x0123456789ABCDEF;
   PrintFormat("%I64X -> %I64X"ulMathSwap(ul));

结果如下:

   12345678 -> 78563412
   123456789ABCDEF -> EFCDAB8967452301

我们尝试在使用 MathSwap 转换值 0x12345678 之后,将字节数组记录到日志:

   bo = MathSwap(ui);    // put the result of MathSwap into ByteOverlay
   ArrayPrint(bo.bytes); //  18  52  86 120 <==> 0x12 0x34 0x56 0x78

字节中索引为 0 的位置,之前是 0x78,现在是 0x12,在包含其它数字的元素中,值也已经交换。