在读一段 C++ 代码,并想要用 Go 重写其逻辑。C++ 的相关代码是这样的:
std::stringstream buffer;
valhalla::baldr::TrafficTileHeader header = {}; // 这是一个 struct
// 此处省略若干字段赋值
header.last_update = last_update_timestamp;
buffer.write(reinterpret_cast<char*>(&header), sizeof(header));
这段代码的作用简单来说是创建一个 header 对象(不熟 C++,不知道这么称呼是否正确?),对其中的字段进行一些赋值后写到一个 stringstream 里。后续会把这个 buffer 写入文件。
我在尝试用 Go 重写这个过程,目的是产生相同格式的文件,让另外一个程序读取。我的关键代码如下:
buf := &bytes.Buffer{}
header := TrafficTileHeader{
// 省略若干字段赋值
LastUpdate: uint64(time.Now().Unix()),
}
binary.Write(buf, binary.BigEndian, header)
然后把 buf 的内容写入一个文件。在文本编辑器中比较 C++ 和 Go 输出的文件,虽然内容是二进制,但字符结构看起来结构差异非常大,目标程序无法识别 Go 输出的文件。
reinterpret_cast<char*>(&header)
等价的操作,从而得到一致的结果?上文 C++ 代码段所在的源文件:
谢谢各位耐心阅读!🙏
感谢各位大佬的指点,思路清楚一些了。
原 C++ 项目里使用了 Memory Map,会沿着这个思路深挖一下。
参考:
1
32uKHwVJ179qCmPj 2022-05-16 17:52:43 +08:00 1
用 protobuf ,因为一旦结构体里面包含指针,即使你通过同一个 c++程序去读取文件也是达不到你的效果的
|
2
32uKHwVJ179qCmPj 2022-05-16 17:55:12 +08:00
看了下代码,看来原作者就是这么干的,对 go 不太熟悉,用 cgo 应该是可以的,主要是要保证结构体在内存中的表现要与 go 一致
|
3
sunny352787 2022-05-16 18:03:54 +08:00 1
C++是内存拷贝,Go 这是按字段序列化,你这怎么可能一致
内存拷贝是要考虑对齐和数据在内存中的排布的,大端还是小端需要跟着 CPU 走 Go 这边 binary 的 write 是给定了大端模式,正常 x86CPU 应该都是小端 话说,这个 C++代码居然直接把内存 dump 出来存成文件好像也确实没考虑用其他类型的 CPU 啊 |
4
dbskcnc 2022-05-16 18:03:55 +08:00 1
reinterpret_cast<char*>只是拿到结构体的内存地址,并没有什么魔法。
你的问题其实是结构体的内存布局不一定相同。内存布局 /对齐是一个比较 hack 的事情,不同的平台,不同的编译器,不同的编译参数都有可能出现不同的结果。 |
5
ysc3839 2022-05-16 18:12:10 +08:00 via Android 1
去找 golang 如何读写 C 结构体
|
6
changnet 2022-05-16 18:14:32 +08:00 1
不懂 go ,但那段 C++的代码,你得保证 header 那个结构体是 POD 数据类型,然后去掉对齐,再写入。那这样写入的就是结构体里的各个字段的值,和手动把各个字段的值取出来再写进文件是一样的。
大小端这个有处理了那就不说了 这样其他程序读出来应该是没问题的,我们的协议有些就是这么发,对方不是 C++ |
7
sunny352787 2022-05-16 18:16:43 +08:00
想了一下,C++自身应该是小端存储,因为默认左移操作等于 x2 ,那你这个问题可以把 Go 代码的大端改成小端试一下,不过即便改成小端模式,也应该会有字节对齐的问题,还是按 5 楼的说法先去搜一下吧
|
8
GuuJiang 2022-05-16 18:42:37 +08:00 via iPhone 1
@sunny352787 左移操作等于 x2 和大小端存储之间没有任何联系
|
9
Buges 2022-05-16 18:57:21 +08:00 via Android 1
你这样要求内存表示完全相同啊,不知道 go 能不能声明 repr(C)的结构体。最稳妥的方式还是序列化。
|
10
lysS 2022-05-17 09:53:46 +08:00 1
go 的结构体就是一段内存,考虑到内存对其,字段中间可能有“空隙”
|
11
sunny352787 2022-05-17 11:26:14 +08:00
@GuuJiang 你确定?再想想呢?
|
12
GuuJiang 2022-05-17 11:41:26 +08:00 via iPhone
@sunny352787 你自己再想想
|
13
sunny352787 2022-05-17 13:25:03 +08:00 1
@GuuJiang 查了一圈,发现了一些东西
对于左移操作等于 x2 这个说法,简单地说,左移操作是将低位转移到高位,也就是说无论高位在前还是高位在后都无所谓,所以确实和大小端没关系,这是我理解错误了 由此我也比较好奇查了一下小端 CPU 和大端 CPU 的一些区别,日常使用的 x86 或者 arm 都可以当做小端处理器,当然 arm 处理器也支持大端,PowerPC 是大端处理器,但这些大端小端的区别也只是体现在寄存器当中,对于整形的左移右移操作来说是没有区别的 不过确实有一个特例情况,就是在处理向量位移操作的时候,PowerPC 的处理是字节序相关的,具体就是 vec_sld 这个指令,https://stackoverflow.com/questions/46341923/is-vec-sld-endian-sensitive/46342218#46342218 在这条回答当中给出了 IBM 的文档 https://www.ibm.com/docs/en/zos/2.2.0?topic=rs-vec-sld-vector-shift-left-double-by-byte 说明这个左移操作是字节序相关的,但这个是处理两个寄存器数据,对于单一寄存器的数据处理还是遵循标准的左移定义 |