有办法用 Go 输出与 C++ 的 reinterpret_cast<char*> 一致的结果吗?

2022-05-16 17:45:48 +08:00
 ck65

问题描述

在读一段 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 输出的文件。


疑问

  1. 用 Go 实现的思路是否合理?
  2. Go 是否有和 reinterpret_cast<char*>(&header) 等价的操作,从而得到一致的结果?

参考

上文 C++ 代码段所在的源文件:

https://github.com/alinmindroc/valhalla_traffic_poc/blob/main/valhalla_code_overwrites/src/mjolnir/valhalla_traffic_demo_utils.cc#L110


谢谢各位耐心阅读!🙏

910 次点击
所在节点    问与答
13 条回复
7v9TEc53
2022-05-16 17:52:43 +08:00
用 protobuf ,因为一旦结构体里面包含指针,即使你通过同一个 c++程序去读取文件也是达不到你的效果的
7v9TEc53
2022-05-16 17:55:12 +08:00
看了下代码,看来原作者就是这么干的,对 go 不太熟悉,用 cgo 应该是可以的,主要是要保证结构体在内存中的表现要与 go 一致
sunny352787
2022-05-16 18:03:54 +08:00
C++是内存拷贝,Go 这是按字段序列化,你这怎么可能一致

内存拷贝是要考虑对齐和数据在内存中的排布的,大端还是小端需要跟着 CPU 走
Go 这边 binary 的 write 是给定了大端模式,正常 x86CPU 应该都是小端

话说,这个 C++代码居然直接把内存 dump 出来存成文件好像也确实没考虑用其他类型的 CPU 啊
dbskcnc
2022-05-16 18:03:55 +08:00
reinterpret_cast<char*>只是拿到结构体的内存地址,并没有什么魔法。

你的问题其实是结构体的内存布局不一定相同。内存布局 /对齐是一个比较 hack 的事情,不同的平台,不同的编译器,不同的编译参数都有可能出现不同的结果。
ysc3839
2022-05-16 18:12:10 +08:00
去找 golang 如何读写 C 结构体
changnet
2022-05-16 18:14:32 +08:00
不懂 go ,但那段 C++的代码,你得保证 header 那个结构体是 POD 数据类型,然后去掉对齐,再写入。那这样写入的就是结构体里的各个字段的值,和手动把各个字段的值取出来再写进文件是一样的。

大小端这个有处理了那就不说了

这样其他程序读出来应该是没问题的,我们的协议有些就是这么发,对方不是 C++
sunny352787
2022-05-16 18:16:43 +08:00
想了一下,C++自身应该是小端存储,因为默认左移操作等于 x2 ,那你这个问题可以把 Go 代码的大端改成小端试一下,不过即便改成小端模式,也应该会有字节对齐的问题,还是按 5 楼的说法先去搜一下吧
GuuJiang
2022-05-16 18:42:37 +08:00
@sunny352787 左移操作等于 x2 和大小端存储之间没有任何联系
Buges
2022-05-16 18:57:21 +08:00
你这样要求内存表示完全相同啊,不知道 go 能不能声明 repr(C)的结构体。最稳妥的方式还是序列化。
lysS
2022-05-17 09:53:46 +08:00
go 的结构体就是一段内存,考虑到内存对其,字段中间可能有“空隙”
sunny352787
2022-05-17 11:26:14 +08:00
@GuuJiang 你确定?再想想呢?
GuuJiang
2022-05-17 11:41:26 +08:00
@sunny352787 你自己再想想
sunny352787
2022-05-17 13:25:03 +08:00
@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 说明这个左移操作是字节序相关的,但这个是处理两个寄存器数据,对于单一寄存器的数据处理还是遵循标准的左移定义

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/853238

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX