从基础到中级:联合(二)·综合运用
「字节序翻转与数据块打包的实现细节」
这段逻辑在做一件事:把 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 把这段代码补进完整脚本验证即可。外汇与贵金属杠杆高,这类底层序列化只影响通信正确性,不预示任何行情方向。
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 位;外汇与贵金属市场杠杆高、滑点随机,这类底层字节处理只解决数据正确性,不预示任何价格方向。
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。
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 一次确认偏移没漂。
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 返回值的变动。练过这一遍,下次写指标缓冲区会少绕很多弯。