我有一个静态库 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 架构
![]() | 1 nagisaushio 2024-08-20 04:39:55 +08:00 via Android 你成员顺序变了啊 |
![]() | 2 ysc3839 2024-08-20 05:01:17 +08:00 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)); } ``` |
![]() | 3 levelworm 2024-08-20 07:13:59 +08:00 via Android memcpy 是按照内存来拷贝,他不管你结构是什么样子的。 |
![]() | 4 8620 2024-08-20 07:53:07 +08:00 via Android 虽然理论上通过偏移能获得更改后的结构体中原来元素的位置,但是一来 B 编译时知晓的结构和内部元素已经发生了一定的改变,二来在 C++进行基本的内存操作,尤其是对一个类的实体进行,本身就是一种比较危险的行为。如果编译 B 真的那么麻烦,A 的更改不如回滚。 |
5 binsys 2024-08-20 07:59:28 +08:00 上 protobuf 吧,解决你的需求。 |
![]() | 6 CapNemo 2024-08-20 08:32:28 +08:00 把 A 中的 score 声明在最后一个就行了 |
7 diivL 2024-08-20 08:57:04 +08:00 你这是在自己给自己埋坑. 最好重新构建 B, 要么再封装一层 A. |
![]() | 8 zzzyk 2024-08-20 09:07:03 +08:00 两个结构体前面的成员类型保持一致。 |
![]() | 9 yolee599 2024-08-20 09:14:00 +08:00 via Android 要在结构体后面加,但是也要考虑对齐和 padding |
10 leonshaw 2024-08-20 09:25:39 +08:00 via Android 这样 memcpy 就算能读,string 不会被析构掉吗? |
![]() | 11 Skifary 2024-08-20 09:36:33 +08:00 搞这么复杂会埋一堆坑,如果 B 无法重新编译,A 和 app 可以,为什么不在 A 里面重新定一个新结构包含原有结构? |
12 StarsunYzL 2024-08-20 09:40:23 +08:00 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 结构是不行的 |
![]() | 13 MoYi123 2024-08-20 10:26:47 +08:00 要 std::is_trivially_copyable_v 的 struct 才能 memcpy, 更别说你这是 2 个不同的 struct 了. |
![]() | 14 Gorvery OP @nagisaushio 成员顺序是变了,但是我定义了一个 old::Struct 和原结构体是一致的了 |
![]() | 15 Gorvery OP @ysc3839 嗯嗯,我大概理解你说的。 但我的 old::Struct 和原来的 Struct 定义和内存布局是一样的,函数方法传入的类型是引用类型,取地址符后,和你例子中的指针类型不是一样的了吗 我现在有办法区分是使用新的结构体传参调用的这个方法,还是老的 |
![]() | 16 Gorvery OP @8620 因为还有另外一个静态库 C 依赖了新的结构体定义,如果结构体回滚,这个影响更大。静态库 C 也是不可重新构建了 |
![]() | 17 mightybruce 2024-08-20 10:36:37 +08:00 成员顺序变了是不可以 copy 的, 内存对齐和指针寻找每个成员的地址都不一样了。 另外用静态库的方式导出,不要使用任何 stl 容器相关的类型,string 这种肯定是不如 char 数组的或 wchar 数组的 |
![]() | 19 Gorvery OP @leonshaw string 真实值都很短,就两三个字符,理论上编译器优化的时候,用栈上的 3 字节空间就够存了,我理解算值拷贝? |
![]() | 20 Gorvery OP @Skifary 因为屎山工程,还有另外一个静态库 C 依赖了新的结构体定义,如果结构体回滚或者再封装一层,这个影响更大。静态库 C 也是不可重新构建了 |
21 thevita 2024-08-20 10:47:21 +08:00 怎么会 A/B 的共同依赖有变更了, A/B 中只变更其中一个,这不是给自己找不痛快么 不要纠结于 什么 新 Struct / 老 Struct , 就一个问题, A/B 的依赖关系怎么处理 1) 保持依赖关系, 那就更新 B ,(这也交给你的构建系统去更新啊,又不耽误你自己) 2) 拆掉依赖关系, 通过一些其他约定/API , 拆掉 A/B 间的依赖关系 如果是那种 这个 .a 没源码有不可控的情况,这种模块也应该再包一层把它隔离掉 |
![]() | 22 Gorvery OP @StarsunYzL 如果不拷贝,只做指针偏移应该怎么处理,我只要能读到里面的一两个成员属性的值就可以了 |
24 greycell 2024-08-20 10:49:02 +08:00 以为这样做省事,其实是浪费时间 |
![]() | 25 Gorvery OP @mightybruce 假如我可以区分是新老定义调用的这个方法,不考虑 copy 的情况可以通过指针偏移手段,读到使用老定义调用函数时,传入结构体中的部分属性么 |
![]() | 26 Gorvery OP @thevita 还有另外一个静态库 C 依赖了新的结构体定义,如果结构体回滚或者再封装一层,这个影响更大。静态库 C 也是不可重新构建了 |
![]() | 27 Skifary 2024-08-20 11:13:06 +08:00 @Gorvery 可以通过指针偏移的方式做到,但是这种方式要严格保证结构体成员顺序。 int getStructInfo(const MyStruct& s) { auto sizeOfInt = sizeof(int); auto sizeOfFloat = sizeof(float); char* pointer = (char*)&s; old::MyStruct old; old.text_length = *((int*)(pointer)); old.text = *((std::string*)(pointer + sizeOfInt + sizeOfFloat)); return 0; } |
28 jones2000 2024-08-20 11:25:03 +08:00 ![]() 结构体里面定义好,数据版本号, 不同的版本号,拷贝不同的大小。 |
29 leonshaw 2024-08-20 11:26:16 +08:00 直接 reinterpret_cast 成老 struct 的指针 |
30 txhwind 2024-08-20 14:07:20 +08:00 ![]() 工厂方法虚基类应该是 best practice 了,不过不知道屎山代码好不好改 |
![]() | 31 Gorvery OP @Skifary 不知道为啥我这边试了不好使,看汇编和逆向后的代码感觉没问题,也通过 offsetof 函数动态计算偏移了,还是取不到数据。 |
![]() | 34 Skifary 2024-08-20 15:33:20 +08:00 @Gorvery 试过 msvc 那段函数是可以用的,这里还有一个 online 的版本用的 g++ https://wandbox.org/permlink/9VbIO1IZ4gV9Vzkb |
35 sampeng 2024-08-20 15:36:45 +08:00 这种东西重构静态库 B 是最优解。并且暴露出的不是 struct ,而是接口的方法是最优解。。。把所有不对的地方都重构一遍。 从工程的角度来说,个人建议。。别这么干拷贝内存的事。不然就是下一个接手的人跳脚骂人。因为这就是副作用,也就是在偷偷摸摸的干了一些外部不知道的事。 |
36 daimen 2024-08-20 16:52:09 +08:00 变量申明顺序一致,对齐规则一致,编译环境一致,应该是可以的 |
37 shapper 2024-08-20 17:48:11 +08:00 B 要重新编译,B 中 A 的结构和符号还是之前没变化的原型,现在新 A 虽然变化了。假设 B 的结构跟着变为新 A 的,B 里原来的位置肯定 corrupt ,后面一连串的 corrup 。要想更新快改动态链接 |