[讨论] 如何将一个结构体的 buffer 数据,拷贝到另一个结构体的实例中

86 天前
 Gorvery

背景

我有一个静态库 A ,提供的头文件中有 public 的结构体和方法接口,以供别人调用

namespace api {

struct MyStruct {
    std::string text;
    int text_length;
};

std::string getStructInfo(const MyStruct &struct);

}

另一个静态库 B ,依赖了静态库 A ,其源码内实例化了一个 MyStruct 结构体对象,并调用了 getStructSize 方法获取了结果

静态库 A 和静态库 B 均以 二进制.a 形式集成到工程 App 中使用

问题

某一天,静态库 A 中的头文件 MyStruct 定义发生变化:

struct MyStruct {
    int text_length;
    float score;
    std::string full_text;
};

我想在不重新构建静态库 B ,保持 MyStruct 命名不变,使用新定义的情况下,通过指针偏移或二进制适配手段,使静态库 B 内的代码逻辑能正常运行,应该如何进行处理?

我的想法是,由于 getStructSize 的参数是引用,所以打算通过上测算出老的结构体定义的大小,然后 memcpy 对应长度的 buffer 到一个和原结构体定义完全一致,但命名空间不一致的的结构体中,随后进行提取:

第一步:定义一个结构体,和原来的 MyStruct 完全一致,但通过命名空间隔离:

namespace old {

struct MyStruct {
    std::string text;
    int text_length;
};

}

第二步:在 getStructInfo 内部,进行转换

int getStructInfo(const MyStruct &struct) {
    size_t old_size = sizeof(old::MyStruct);
    old::MyStruct old_struct;
    memcpy(&old_struct, &struct, old_size);

    printf("old struct text:%s", old_struct.text.c_str());
    ……
}

总觉得这个方法是可以的,但实际操作的过程中就不行,无法获得 text 的值,这是为什么呢?求大神赐教下

补充信息:iOS 系统,arm64 架构

2296 次点击
所在节点    程序员
37 条回复
nagisaushio
86 天前
你成员顺序变了啊
ysc3839
86 天前
memcpy 是原样拷贝其中的值,MyStruct 和 old::MyStruct 的内存结构都不一样,原样拷贝还是不一样的,并不能进行转换,当然是不行的。
这个例子最大的问题还不是转换,而是没办法得知外部传进来的是新的还是旧的。

这种情况的正确做法是,结构体开头用一个字段保存结构体大小,然后当结构体发生改变时,要确保与之前版本的大小都不一样,这样就可以通过大小来区分不同版本了。
```
#include <cstdio>

struct MyStruct_V1 {
size_t size;
int a;
float b;
};

struct MyStruct_V2 {
size_t size;
float b;
int a;
double c;
};

void PrintMyStruct(const void* p) {
auto size = *reinterpret_cast<const size_t*>(p);
if (size == sizeof(MyStruct_V1)) {
auto structV1 = reinterpret_cast<const MyStruct_V1*>(p);
printf("MyStruct_V1: a=%d b=%f\n", structV1->a, structV1->b);
} else if (size == sizeof(MyStruct_V2)) {
auto structV2 = reinterpret_cast<const MyStruct_V2*>(p);
printf("MyStruct_V2: b=%f a=%d c=%f\n", structV2->b, structV2->a, structV2->c);
}
}

int main() {
MyStruct_V1 structV1 = {
sizeof(structV1),
233,
466,
};
PrintMyStruct(reinterpret_cast<void*>(&structV1));
MyStruct_V2 structV2 = {
sizeof(structV2),
233,
466,
699,
};
PrintMyStruct(reinterpret_cast<void*>(&structV2));
}
```
levelworm
86 天前
memcpy 是按照内存来拷贝,他不管你结构是什么样子的。
8620
86 天前
虽然理论上通过偏移能获得更改后的结构体中原来元素的位置,但是一来 B 编译时知晓的结构和内部元素已经发生了一定的改变,二来在 C++进行基本的内存操作,尤其是对一个类的实体进行,本身就是一种比较危险的行为。如果编译 B 真的那么麻烦,A 的更改不如回滚。
binsys
86 天前
上 protobuf 吧,解决你的需求。
CapNemo
86 天前
把 A 中的 score 声明在最后一个就行了
diivL
86 天前
你这是在自己给自己埋坑. 最好重新构建 B, 要么再封装一层 A.
zzzyk
86 天前
两个结构体前面的成员类型保持一致。
yolee599
86 天前
要在结构体后面加,但是也要考虑对齐和 padding
leonshaw
86 天前
这样 memcpy 就算能读,string 不会被析构掉吗?
Skifary
86 天前
搞这么复杂会埋一堆坑,如果 B 无法重新编译,A 和 app 可以,为什么不在 A 里面重新定一个新结构包含原有结构?
StarsunYzL
86 天前
1 、简单点可以学微软 Win32 SDK ,结构第一个成员是结构大小,要求使用结构的人必须初始化这个成员,你的接口内通过这个成员的数值大小来判断结构是新是旧,缺点是新增结构成员只能加在结构最末尾:
```cpp
struct OldMyStruct {
uint32_t struct_size;
int a;
};

struct MyStruct {
uint32_t struct_size;
int a;
int new_a;
};

int getStructInfo(const MyStruct &struct) {
if (struct.struct_size == sizeof(OldMyStruct)) {
struct.a; // 只访问旧结构成员
} else if (struct.struct_size == sizeof(MyStruct)) {
struct.new_a; // 访问新结构成员
} else {
// 错误,未正确初始化结构
}
}

// 使用者
MyStruct my_struct;
my_struct.struct_size = sizeof(my_struct);
getStructInfo(my_struct);
```

2 、该说不说,std::string 这种动态分配内存的结构成员,memcpy 拷贝 MyStruct 结构是不行的
MoYi123
86 天前
要 std::is_trivially_copyable_v 的 struct 才能 memcpy, 更别说你这是 2 个不同的 struct 了.
Gorvery
86 天前
@nagisaushio 成员顺序是变了,但是我定义了一个 old::Struct 和原结构体是一致的了
Gorvery
86 天前
@ysc3839 嗯嗯,我大概理解你说的。
但我的 old::Struct 和原来的 Struct 定义和内存布局是一样的,函数方法传入的类型是引用类型,取地址符后,和你例子中的指针类型不是一样的了吗😂

我现在有办法区分是使用新的结构体传参调用的这个方法,还是老的
Gorvery
86 天前
@8620 因为还有另外一个静态库 C 依赖了新的结构体定义,如果结构体回滚,这个影响更大。静态库 C 也是不可重新构建了
mightybruce
86 天前
成员顺序变了是不可以 copy 的, 内存对齐和指针寻找每个成员的地址都不一样了。
另外用静态库的方式导出,不要使用任何 stl 容器相关的类型,string 这种肯定是不如 char 数组的或 wchar 数组的
Gorvery
86 天前
@diivL 哎,屎山工程,无法重新构建 B 了
Gorvery
86 天前
@leonshaw string 真实值都很短,就两三个字符,理论上编译器优化的时候,用栈上的 3 字节空间就够存了,我理解算值拷贝?
Gorvery
86 天前
@Skifary 因为屎山工程,还有另外一个静态库 C 依赖了新的结构体定义,如果结构体回滚或者再封装一层,这个影响更大。静态库 C 也是不可重新构建了

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

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

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

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

© 2021 V2EX