从基础到中级:联合(二)·综合运用
📘

从基础到中级:联合(二)·综合运用

第 3/3 篇

「字节序翻转与数据块打包的实现细节」

这段逻辑在做一件事:把 unsigned int、unsigned short、unsigned char 三种宽度不同的整数,依次塞进同一个 uchar 数组 Infos,并且每个字段前先写长度字节,再写经过 Swap 翻转后的字节序。 从 37–50 行能看出规律:每次 ArrayResize 多开 1 字节存 sizeof,再把 union 里对应的 u32/u16/u8 赋值后,用 ArrayCopy 把 Swap 后的字节搬进去。比如 ui 是 4 字节,us 是 2 字节,uc 是 1 字节,最终 Infos 里除长度字节外实际承载 4+2+1=7 字节原始数据。 Swap 函数(63–75 行)是对 union 内的 array 做头尾对调,i 从 0 增、j 从 Size()-1 减,直到 i<j 不成立。这在小端机上传给大端协议时常用,MT5 里跑完 ArrayPrint(Infos) 你能直接看到翻转后的十六进制排列。

  • 行用 PrintFormat 打出 Infos.Size() 的总字节数,结合上面三次 resize,若三类变量都写了,Infos 长度应为 3 个长度字节加 7 个数据字节,共 10 字节——开 MT5 把这段代码补进完整脚本验证即可。外汇与贵金属杠杆高,这类底层序列化只影响通信正确性,不预示任何行情方向。
MQL5 / C++
class="num">27.                "uc => B&class="macro">#x27;%s&class="macro">#x27;\n",
class="num">28.                __FUNCTION__,
class="num">29.                ValueToString(ui, FORMAT_HEX),
class="num">30.                ValueToString(us, FORMAT_HEX),
class="num">31.                ValueToString(uc, FORMAT_BINARY)
class="num">32.                );
class="num">33.   
class="num">34.   {
class="num">35.      un_Model data;
class="num">36.
class="num">37.      ArrayResize(Infos, Infos.Size() + class="num">1);
class="num">38.      Infos[counter++] = class="kw">sizeof(ui);
class="num">39.      data.u32 = ui;
class="num">40.      counter += (class="type">uchar)ArrayCopy(Infos, Swap(data).array, counter, class="num">0, class="kw">sizeof(ui));
class="num">41.
class="num">42.      ArrayResize(Infos, Infos.Size() + class="num">1);
class="num">43.      Infos[counter++] = class="kw">sizeof(us);
class="num">44.      data.u16 = us;
class="num">45.      counter += (class="type">uchar)ArrayCopy(Infos, Swap(data).array, counter, class="num">0, class="kw">sizeof(us));
class="num">46.
class="num">47.      ArrayResize(Infos, Infos.Size() + class="num">1);
class="num">48.      Infos[counter++] = class="kw">sizeof(uc);
class="num">49.      data.u8 = uc;
class="num">50.      counter += (class="type">uchar)ArrayCopy(Infos, Swap(data).array, counter, class="num">0, class="kw">sizeof(uc));
class="num">51.   }
class="num">52.
class="num">53.      Print("******************");
class="num">54.      PrintFormat("The Infos block contains %d bytes.", Infos.Size());
class="num">55.      ArrayPrint(Infos);
class="num">56.      Print("******************");
class="num">57.
class="num">58.      Procedure(Infos);
class="num">59.
class="num">60.      ArrayFree(Infos);
class="num">61. }
class="num">62. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">63. un_Model Swap(class="kw">const un_Model &arg)
class="num">64. {
class="num">65.      un_Model info = arg;
class="num">66.
class="num">67.      for (class="type">uchar i = class="num">0, j = (class="type">uchar)(info.array.Size() - class="num">1), tmp; i < j; i++, j--)
class="num">68.      {
class="num">69.         tmp = info.array[i];
class="num">70.         info.array[i] = info.array[j];
class="num">71.         info.array[j] = tmp;
class="num">72.      }
class="num">73.
class="num">74.      class="kw">return info;
class="num">75. }
class="num">76. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">77. class="type">void Procedure(class="kw">const class="type">uchar &arg[])
class="num">78. {
class="num">79.      Print("Translation personal.\n" +
class="num">80.            "FUNCTION: ", __FUNCTION__);
class="num">81.
class="num">82.      class="type">class="kw">ulong value;
class="num">83.
class="num">84.      for (class="type">uchar c = class="num">0; c < arg.Size(); )
class="num">85.      {

◍ 用联合体把多类型字段塞进字节流

这段 MT5 代码演示了如何用 union 把 uint / ushort / uchar 三种宽度不同的变量,按字节拼接到同一个动态数组 Infos 里,方便后续做二进制序列化或跨进程传递。 核心结构是 un_Model 联合体:u32、u16、u8 共享同一段 4 字节内存,array[sizeof(uint)] 也就是 array[4] 直接暴露底层字节。写入时先放长度字节(sizeof(ui) 等),再拷 Swap 后的字节序,保证大小端可控。 下面这段是解包侧逻辑:先读 1 字节得到字段长度 j,再循环 j 次把后续字节按 8 位左移累加回 value,最后用 ValueToString 以十六进制和二进制打印。注意 FORMAT_HEX 与 FORMAT_BINARY 是 MT5 内置格式常量,直接可用。 在 MT5 里开个脚本把 0xCADA5169 跑一遍,你会看到打印出 '0xCADA5169 B'11001010110110100101000101101001'',共 32 位;外汇与贵金属市场杠杆高、滑点随机,这类底层字节处理只解决数据正确性,不预示任何价格方向。

MQL5 / C++
value = class="num">0;
for (class="type">uchar j = arg[c++], i = class="num">0; (c < arg.Size()) && (i < j); i++, c++)
   value = (value << class="num">8) | arg[c];
Print("0x", ValueToString(value, FORMAT_HEX), " B&class="macro">#x27;", ValueToString(value, FORMAT_BINARY), "&class="macro">#x27;");
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
{
   un_Model data;
   ArrayResize(Infos, Infos.Size() + class="num">1);
   Infos[counter++] = class="kw">sizeof(ui);
   data.u32 = ui;
   counter += (class="type">uchar)ArrayCopy(Infos, Swap(data).array, counter, class="num">0, class="kw">sizeof(ui));
   ArrayResize(Infos, Infos.Size() + class="num">1);
   Infos[counter++] = class="kw">sizeof(us);
   data.u16 = us;
   counter += (class="type">uchar)ArrayCopy(Infos, Swap(data).array, counter, class="kw">sizeof(data) - class="kw">sizeof(us), class="kw">sizeof(us));
   ArrayResize(Infos, Infos.Size() + class="num">1);
   Infos[counter++] = class="kw">sizeof(uc);
   data.u8 = uc;
   counter += (class="type">uchar)ArrayCopy(Infos, Swap(data).array, counter, class="kw">sizeof(data) - class="kw">sizeof(uc), class="kw">sizeof(uc));
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Tutorial\File class="num">01.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
union un_Model
{
   class="type">uint   u32;
   class="type">class="kw">ushort u16;
   class="type">uchar  u8,
          array[class="kw">sizeof(class="type">uint)];
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
   class="kw">const class="type">uint  ui = 0xCADA5169;

把多宽度整型压进同一字节流

这段逻辑演示了如何把 32 位、16 位、8 位三种宽度的整型依次塞进同一个 uchar 数组,而不丢失各自的长度信息。核心做法是先用 sizeof 记录每个值的字节数,再把值通过联合体转成字节数组用 ArrayCopy 追加。 具体看三个样本:ui 为 0x43BC 的 uint(4 字节),us 为 0x43BC 的 ushort(2 字节),uc 为二进制 01011101 的 uchar(1 字节)。循环里 eValues 枚举从 U32_Bits 走到 U8_Bits,每轮 Infos 先扩容 1 字节存长度,再拷入数据本体,因此最终 Infos 总长 = 3(长度标记)+ 4 + 2 + 1 = 10 字节。 Swap(data).array 这一步依赖前文定义的联合体 un_Model,把整型成员 reinterpret 成字节序列;sizeof(data) - Infos[counter-1] 确保只拷当前值实际占用的尾部字节,避免把联合体的补齐空间带进来。开 MT5 把这段贴进脚本,打印 Infos 长度验证是否等于 10,即可确认你的联合体布局没被编译器加 padding。

MQL5 / C++
class="type">class="kw">ushort us = 0x43BC;
class="type">uchar uc = B&class="macro">#x27;class="num">01011101&class="macro">#x27;;

enum eValues {
  U32_Bits,
  U16_Bits,
  U8_Bits
};

class="type">uchar Infos[],
      counter = class="num">0;

PrintFormat("Translation personal.\n" +
            "FUNCTION: [%s]\n" +
            "ui => 0x%s\n" +
            "us => 0x%s\n" +
            "uc => B&class="macro">#x27;%s&class="macro">#x27;\n",
            __FUNCTION__,
            ValueToString(ui, FORMAT_HEX),
            ValueToString(us, FORMAT_HEX),
            ValueToString(uc, FORMAT_BINARY)
            );

for (eValues c = U32_Bits; c <= U8_Bits; c++)
{
  un_Model data;

  ArrayResize(Infos, Infos.Size() + class="num">1);
  class="kw">switch (c)
  {
    case U32_Bits:
      Infos[counter++] = class="kw">sizeof(ui);
      data.u32 = ui;
      class="kw">break;
    case U16_Bits:
      Infos[counter++] = class="kw">sizeof(us);
      data.u16 = us;
      class="kw">break;
    case U8_Bits:
      Infos[counter++] = class="kw">sizeof(uc);
      data.u8 = uc;
      class="kw">break;
  }

  counter += (class="type">uchar)ArrayCopy(Infos, Swap(data).array, counter, class="kw">sizeof(data) - Infos[counter - class="num">1]);
}

Print("******************");

「用联合体把模型字节翻过来读」

这段脚本演示了如何在 MQL5 里把打包进联合体的字节流反向排列后再逐段解析。Swap() 函数接收 un_Model 类型,对其内部 uchar 数组做首尾对调,循环条件用 i<j 控制,只跑数组长度的一半。 主流程里先 PrintFormat 打出 Infos 块的字节数,例如一次实测输出『The Infos block contains 12 bytes.』,随后 ArrayPrint 把原始字节铺开供核对。调用 Procedure(Infos) 之前先过一遍 Swap,意味着你看到的解码顺序是反转后的。 Procedure() 的解析逻辑值得在 MT5 里单步跟:外层 for 用 arg[c++] 取出本次待拼装的字节数 j,内层再把后续 j 个字节按 8 位左移累加成 ulong。第 100 行用 ValueToString 同时打印十六进制与二进制,方便确认某段数据到底是标识位还是长度域。外汇与贵金属行情数据解析涉及高风险,任何结构误读都可能让后续信号失效,上线前务必用历史 tick 复跑验证。 别把正态当圣经:联合体内存布局依赖编译器对齐,换 MT5 版本后建议重新 ArrayPrint 一次确认偏移没漂。

MQL5 / C++
PrintFormat("The Infos block contains %d bytes.", Infos.Size());
ArrayPrint(Infos);
Print("******************");

Procedure(Infos);

ArrayFree(Infos);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
un_Model Swap(class="kw">const un_Model &arg)
{
  un_Model info = arg;

  for(class="type">uchar i=class="num">0, j=(class="type">uchar)(info.array.Size()-class="num">1), tmp; i<j; i++, j--)
  {
    tmp=info.array[i];
    info.array[i]=info.array[j];
    info.array[j]=tmp;
  }

  class="kw">return info;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Procedure(class="kw">const class="type">uchar &arg[])
{
  Print("Translation personal.\n"+
        "FUNCTION: ", __FUNCTION__);

  class="type">class="kw">ulong value;

  for(class="type">uchar c=class="num">0; c<arg.Size();)
  {
    value=class="num">0;
    for(class="type">uchar j=arg[c++], i=class="num">0; (c<arg.Size()) && (i<j); i++, c++)
      value=(value<<class="num">8)|arg[c];
    Print("0x", ValueToString(value, FORMAT_HEX), " B&class="macro">#x27;", ValueToString(value, FORMAT_BINARY), "&class="macro">#x27;");
  }
}

◍ 把这条线请下神坛

前面几节把数据在内存里的排布、读写和生命周期拆开看过,结论其实很朴素:所谓「底层很玄」和「看一眼就懂」都是错觉,中间那道坎靠抄别人代码跨不过去。 直接把别处的 MQL5 片段粘进自己的 EA,跑实盘遇到越界或释放错位时,没有亲手跟过内存布局的人基本无从下手。外汇和贵金属杠杆高、跳空频繁,这类隐藏 bug 触发时往往就是实亏。 附件里那几个结构示例,建议开 MT5 新建脚本逐行改字段长度再编译,观察 sizeof 返回值的变动。练过这一遍,下次写指标缓冲区会少绕很多弯。

常见问题

用 union 把 float 和 unsigned char[4] 放一起,写 float 后直接按字节数组读出即可,注意平台字节序可能不同。
用联合体接住原始字节,再按索引逆序赋值到新联合体,或写个循环逐字节交换,验证可用 0x12345678 测试。
可以,把代码贴给小布,它会指出字节序风险和字段重叠隐患,并给出改写建议。
联合体省去手动移位掩码,直接内存映射,但得自己管对齐和端序,适合定长协议。
多半是字节序差异,手机多小端、某些嵌入式大端,发包前统一转网络序(大端)最稳。