string为接口的注意事项

string为接口的注意事项

问题描述

​ 在一个应用程序中用到了另外一个库的dll,向dll的接口传递std::string参数时报错。由于这方面的问题比较多,所以我进行了深入研究。

前置知识

在vs项目右键 -> 属性 ->C/C++ ->代码生成->运行库,有四个选项,/MD /MDd/MT/MTd

含有D的选项

若设置了这种选项,那么就会和其他设置了含有D的选项的模块共同维护一块堆内存,即使跨模块释放也没关系

含有T的选项

若设置了这种选项,那么该模块就会自己维护一块堆内存,不允许跨模块释放

含有d的选项

若设置了这种选项,那么该模块使用的stl标准库就是debug版本的

不含d的选项

若设置了这种选项,那么该模块使用的stl标准库就是release版本的

std::string的内存结构

Debug版本

struct string{
    std::string::Proxy * _Myproxy
	union Bxty{
		char Buf[0x10];
		char * Ptr;
	};
	Bxty bxty;
#ifdef _WIN64
	unsigned __int64 Mysize;
	unsigned __int64 Myres;
#else
	unsigned int Mysize;
	unsigned int Myres;
#endif
}

Release版本

struct string{
	union Bxty{
		char Buf[0x10];
		char * Ptr;
	};
	Bxty bxty;
#ifdef _WIN64
	unsigned __int64 Mysize;
	unsigned __int64 Myres;
#else
	unsigned int Mysize;
	unsigned int Myres;
#endif
}

可以看到Debug的std::string比Release的std::string就是多了一个指针_Myproxy

大部分stl的类都是这样的

场景模拟

跨模块释放

源代码

dll

lib.h

#include <iostream>
__declspec(dllexport) void test(std::string str);

lib.cpp

#include <lib.h>
void test(std::string str) {
	std::cout << str << std::endl;
}

编译版本为Debug x86, 运行库选项为 /MDd

应用程序

#include <iostream>
#include <lib.h>
int main()
{
	std::string str = "aaaaaaaaaaaaaaaaaaaaaa";
	test(str);
	getchar();
}

编译版本为Debug x86, 运行库选项为 /MTd

字符串的大小要大于0xF,std::string才会创建一块内存去维护它

现象

报错

image

调用堆栈

image

原因分析

为什么会发生跨模块释放呢?

image

看上图的反汇编窗口中红色方框标记的位置,在传递test函数的参数时,首先分配了0x1C个栈内存,这刚好是std::string Debug版本的大小,然后调用了std::string 的复制构造函数,将str的字符串复制到栈参数中,此时会创建一块内存用于存放复制的字符串

下面查看test中的代码

image

发现在打印完了栈参数的变量后,居然直接在函数内部调用了std::string的析构函数,此函数会把自身维护的内存释放掉,这样在主程序中创建内存,在dll中释放内存,发生了跨模块释放。

而为什么不允许跨模块释放呢,因为主程序使用的运行库选项是/MTd,这是个含有T的选项,所以自己会维护一块堆,不允许跨模块释放

内存结构错乱,字符串乱码

源代码

dll

lib.h

#include <iostream>
__declspec(dllexport) void test(std::string str);

lib.cpp

#include <lib.h>
void test(std::string str) {
	std::cout << str << std::endl;
}

编译版本为Release x86, 运行库选项为 /MD

应用程序

#include <iostream>
#include <lib.h>
int main()
{
	std::string str = "aaaaaaaaaaaaaaaaaaaaaa";
	test(str);
	getchar();
}

编译版本为Release x86, 运行库选项为 /MDd

Debug 版本 不能设置运行不含d的选项,因为它依赖了一些 Release 版本的stl没有的函数

现象

现在主程序和dll的运行库都不含T,应该没问题了吧,但是还是发生了错误

image

随后报错

image

原因分析

首先是乱码,由于主程序的运行库选项是/MDd,这个选项是含d,所以在分配栈内存时是使用std::string的Debug版本,char * 指针Ptr是存放在类内存的0x4处,而dll的运行库选项是/MD,这个选项是不含d的,它在使用主程序传过来的std::string时,使用的是Release版本,它认为Ptr存放在0x0中,所以在打印的时候打印错位置了,导致的乱码

而堆损坏同样是这个原因,在test函数打印完字符串,析构std::string的时候,它认为Ptr在0x0处,但实际上Ptr是主程序传递的,Ptr在0x4,0x0处的指针指向的并不是一个堆,所以报了堆已损坏的错误

结论

最好不要使用stl容器当做动态库的接口,如果实在需要的话,得保证运行库保持一致,可以编两份,一份Release,一份Debug,给第三方调试和使用

posted @ 2023-05-05 19:45  乘舟凉  阅读(83)  评论(0编辑  收藏  举报