首页 > 其他分享 >string为接口的注意事项

string为接口的注意事项

时间:2023-05-05 19:56:29浏览次数:39  
标签:std 选项 运行库 string 接口 内存 str 注意事项

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

现象

现在主程序和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,给第三方调试和使用

标签:std,选项,运行库,string,接口,内存,str,注意事项
From: https://www.cnblogs.com/czlnb/p/17375212.html

相关文章

  • collection接口
    Collection接口和常用方法collection接口包含:list和set两个接口而list里有Vector类,ArrayList类和LinkedList类set里有:Hashset类和Treeset类collection接口实现类的特点collection类实现子类可以存放多个元素,每个元素可以是Object有些Collection的实现类,可以存放重复的元素......
  • MASA MinimalAPI源码解析:为什么我们只写了一个app.MapGet,却生成了三个接口
    源码解析:为什么我们只写了一个app.MapGet,却生成了三个接口1.ServiceBase1.AutoMapRoute源码如下:AutoMapRoute自动创建map路由,MinimalAPI会根据service中的方法,创建对应的api接口。比如上文的一个方法:publicasyncTask<WeatherForecast[]>PostWeather(){re......
  • api接口怎么用?
    ​ API接口是一种应用程序编程接口,它允许不同的软件应用程序之间进行通信和交互。通过使用API接口,开发人员可以轻松地将自己的应用程序集成到其他应用程序中,从而实现更丰富的功能和更好的用户体验。 API接口的使用方法一般包括以下几个步骤:首先,开发人员需要了解API接口的文档......
  • 1688|Taobao|JD京东api接口获取商品详情C++演示案例
    ​ 商品详情页的作用:介绍产品信息、给出购买理由、提升信任感、提出售后保障。1、介绍产品信息:产品信息表做得越完整,越能让用户更细致了解产品,也减少了售前客服咨询的工作量。2、给出购买理由:在用户初步了解了产品信息后,商家就需要展示商品优势,给出核心卖点。接下来小编会展......
  • 1 字符串 、2 指针 、3 结构体 、4 方法、 5 接口
    目录1字符串2指针3结构体4方法5接口1字符串packagemain//字符串funcmain(){ //1定义字符串 //vars="中alqz" //2字符串可以按下标取值,不能改 //s[0]=98 //fmt.Println(s[0])//取字节,是个数字 //fmt.Println(s[3]) //fmt.Printf("%T\n",s[3])/......
  • 1 接口 、2 并发与协程 、3 信道,缓冲信道 、4 mutex 、5 异常处理
    目录1接口2并发与协程3信道,缓冲信道4mutex5异常处理1接口#1实现多个接口#2接口嵌套#3接口零值packagemainimport"fmt"//接口//1实现多个接口具体类型可以赋值给多种接口类型//typeAnimalinterface{// run()//}////typeDoginterface{......
  • nginx 发布网站通过域名访问,后端接口反向代理
    1、申请域名证书,申请好之后,下载Nginx域名部署包(包里一般会含义*.key和*.pem文件)2、把 *.key和*.pem文件放置到 Nginx程序 conf文件夹下,与nginx.conf文件路径保存一致3、修改Nginx的配置文件 server{    listen   443ssl;    #你的域......
  • String常用方法
    返回类型方法名作用StringtoUpperCase()将一个字符串全部转换成大写StringtoLowerCase()将一个字符串全部转换成小写Stringreplace(String,String)将某个内容全部替换成......
  • SpringBoot中策略模式+工厂模式业务实例(接口传参-枚举类查询策略映射关系-执行不同策
    场景设计模式-策略模式在Java中的使用示例:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127622238上面讲了策略模式在Java中的使用示例。下面看一个在SpringBoot中的实际使用示例。业务场景:有多个煤矿,信号灯有多个厂家/规则,每个煤矿对应一种信号灯。需要编......
  • chatgpt接口开发笔记2生成图片接口
    chatgpt接口开发笔记2生成图片接口chatgpt的生成图片接口,可以根据用户的描述来生成满足用户意愿的图片1、了解接口参数接口地址:POSThttps://api.openai.com/v1/images/generations下面是接口文档描述内容curlhttps://api.openai.com/v1/images/generations\-H"Co......