Dll堆栈问题(Dll的静态变量与全局变量、vs的MT与MD)
问题引入:
dll有一个导出函数,函数参数是string&,string在函数内部被=赋值。在exe动态加载此dll,调用此导出函数后,会崩溃。
原因:
如果任何STL类的实现中使用了静态变量(我们无从得知但map、string存在此问题),且编译dll时,vc的运行库设置为MT或MTd,会静态链接VC的运行时库,这会导致采用静态链接的方式将导致生成的目标模块拥有独立的堆栈空间。即此静态变量在dll中有独立的一份,在任何加载此dll的exe中,也有独立的一份。
由于"谁申请,谁释放"的原则,如果按照上面的情景,由于string在dll的函数内部被赋值,因此是由dll去申请的内存。但是因为string&传递了出去,在exe中使用,是由exe释放的string。因此会导致崩溃。
解决方法:
方法1.如果任何STL类内部使用了静态变量(无论是直接还是间接使用),那么就不要再写出跨执行单元访问它的代码。
方法2.对于跨模块使用string&,改为使用WCHAR*
方法3.编译dll时,修改vc的运行库为MD或MDd,这样堆栈空间就是共享的了。
/MD 与 /MT、/MTd与/MDd的区别
1./MD 与 /MT 用于Release 版本,前者表示链接时,不链接VC的运行时库(msvcrt.lib),而采用动态库(msvcrtXX.dll,其中XX表示使用的版本);相应地,后者则表示静态链接VC的运行时库,这样的结果是链接生成的的目标模块体积明显比前者要大一些。
2、/MDD与/MTD 用于Debug版本,其它规则同上。
3、除了在是动、静态链接VC运行时库上有区别,另外的区别点在于,采用静态链接的方式将导致生成的目标模块拥有独立的堆栈空间,如果生成的是DLL,那意味着调用该DLL的EXE程序与该DLL有着不同的堆栈空间,如果发生了EXE拿到了在DLL中分配内存创建的对象,在EXE对其进行析构时,就会导致内存非法访问,出现类似于“ windows已在XX.exe中触发一个断点... ...”的错误。所以,尽量不要使用 /MT与/MTD进行静态运行时库链接的方式,即使要使用,也一定要遵循“谁申请,谁释放”的原则。但是该原则在使用类时很难遵循,因为类中可能会有申请内存的动作。
4、采用第1点静态链接时,如果生成的模块拿给别人使用,别人若使用了不同版本的编译器,则会在链接时产生一系列问题,比如经常需要手动忽略 msvcrt.lib这个库。具体会导致的问题此处不做研究。
5.另外,多模块程序的内存空间很值得推敲研究。但Linux下貌似不存在这些问题。
DLL的全局变量与静态变量
微软解释:https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-data
在 DLL 源代码文件中声明为全局的变量被编译器和链接器视为全局变量,如果一个exe加载了此dll,那么这个全局变量对于exe和dll来说是相同的;但加载给定 DLL 的每个进程都会获得该 DLL 全局变量的自己的副本,这个全局变量对于不同进程是不同的。
静态变量的作用域仅限于声明静态变量的块内,静态全局变量(函数)在本编译单元之外不可见。如果某个编译单元exe...(目标文件)包含了dll的这个.h文件,那么他会获得一份此静态全局变量的副本。这两份静态全局变量是不一样的。
因此,默认情况下,每个进程都有自己的 DLL 全局变量和静态变量实例。
当然我们可以使用共享数据段在不同进程间共享dll的全局变量,当然我们必须明确限制同时访问,例如使用命名互斥锁。
动态链接库
#ifdef MYDLL_EXPORTS
define MYDLL_API __declspec(dllexport)
#else
define MYDLL_API __declspec(dllimport)
#endif
MYDLL_API extern int latchCounter;
DLL文件
#include "dll.h"
#pragma data_seg(".shared")
int latchCounter = 0; // 定义在共享数据段的全局变量,可以在不同进程中共享
#pragma data_seg()
// the 'S' here is the key to mark the ".shared" data segment as shared
#pragma comment(linker, "/SECTION:.shared,RWS")
主程序
#include <type_traits>
#include <memory>
#include <Windows.h>
#include "dll.h"
template<auto f>
using fn_constant = std::integral_constant<decltype(f), f>;
using handle_ptr = std::unique_ptr<void, fn_constant<&CloseHandle>>;
int main()
{
// increase latchCounter
{
handle_ptr mx{ CreateMutex(nullptr, false, L"MyLatchCounterMutex") };
WaitForSingleObject(mx.get(), INFINITE);
++latchCounter;
ReleaseMutex(mx);
}
// decrease latchCounter
{
handle_ptr mx{ CreateMutex(nullptr, false, L"MyLatchCounterMutex") };
WaitForSingleObject(mx.get(), INFINITE);
if (--latchCounter == 0)
{
// do something
}
ReleaseMutex(mx);
}
}
测试上面理论的用例:
//dll.h:
#include <tchar.h>
#include <iostream>
#include <vector>
#ifdef MATHLIB_EXPORT
#define MATHLIBAPI __declspec(dllexport)
#else
#define MATHLIBAPI __declspec(dllimport)
#endif
extern MATHLIBAPI int i; // 导出全局变量,在同一个进程中是可见的
//extern static int iStatic; // 提示错误,不能导出,因为静态全局在本编译单元之外不可见。
static int i = 1; // dll中的静态全局变量(函数)在本编译单元之外不可见,
// 如果某个编译单元exe...(目标文件)包含了dll的这个.h文件,那么他会获得一份此静态全局变量的副本。
//dll.cpp:
int i = 1;
extern "C" __declspec(dllexport) void APIENTRY DllTestStatic(std::vector<std::wstring>& vecStr);
extern "C" __declspec(dllexport) void APIENTRY DllTestStatic2();
__declspec(dllexport) void APIENTRY DllTestStatic(std::vector<std::wstring>& vecStr)
{
vecStr.push_back(_T("11111")); // 申请wstring是在dll内部申请的
}
__declspec(dllexport) void APIENTRY DllTestStatic2()
{
wprintf(_T("全局变量 dll_iInt = %d\n"), i);
wprintf(_T("静态全局变量 dll_iStatic = %d\n"), iStatic);
}
EXE的代码:
#include <vector>
#include "../Dll_Static/dllexprot.h" // 实验3、4
//typedef void(*FuncDllTestStatic)(std::vector<std::wstring>& vecStr); // 实验1
//typedef void(*FuncDllTestStatic2)(); // 实验4
/*
// 实验1
// 测试dll动态加载,传递string& - 设置MD运行库的dll不会崩溃,设置MT的会崩溃
// 崩溃原因:
// 采用(MT)静态链接的方式链接msvcrt.lib将导致生成的目标模块dll拥有独立的堆栈空间,和exe不是一个堆栈空间
// 由于stl中很多都具有static变量,这样申请wstring是在dll内部申请的,但是释放却是在exe中。导致崩溃。
std::vector<std::wstring> vecStr;
HMODULE ModuleBase = LoadLibrary(_T("Dll_Static.dll"));
FuncDllTestStatic pDllTestStatic = (FuncDllTestStatic)GetProcAddress(ModuleBase, "DllTestStatic");
pDllTestStatic(vecStr);
*/
//------------------------------------------------------------------------------------------
/*
// 实验2
// 测试dll隐式链接,传递string& - 设置MD运行库的dll不会崩溃,设置MT的会崩溃,原因如上
std::vector<std::wstring> vecStr;
DllTestStatic(vecStr);
*/
//------------------------------------------------------------------------------------------
/*
// 实验3
// 测试dll隐式链接 - 静态全局变量是不可见的(不管MD还是MT)
DllTestStatic2(); // 打印1
iStatic++; // exe中的static变量变为了2
DllTestStatic2(); // 打印1
*/
//------------------------------------------------------------------------------------------
/*
// 实验4
// 测试动态链接dll - 静态全局变量是不可见的(不管MD还是MT)
HMODULE ModuleBase = LoadLibrary(_T("Dll_Static.dll"));
FuncDllTestStatic2 pDllTestStatic2 = (FuncDllTestStatic2)GetProcAddress(ModuleBase, "DllTestStatic2");
pDllTestStatic2(); // 打印1
iStatic++; // exe中的static变量变为了2
pDllTestStatic2(); // 打印1
*/
//------------------------------------------------------------------------------------------
// 实验5
// 测试dll导出的全局变量,在同一个进程中是可见的
HMODULE ModuleBase = LoadLibrary(_T("Dll_Static.dll"));
FuncDllTestStatic2 pDllTestStatic2 = (FuncDllTestStatic2)GetProcAddress(ModuleBase, "DllTestStatic2");
pDllTestStatic2(); // 打印1
i++; // exe中的全局变量变为了2
pDllTestStatic2(); // 打印2
Dll堆栈问题(Dll的静态变量与全局变量、vs的MT与MD)的更多相关文章
- matlab中的静态变量与全局变量
matlab中的静态变量和全局变量 1.静态变量 在matlab中,和其他语言一样,函数中的变量一把都是局部变量,也就是说,在函数调用完毕后,变量就会被释放.但是有些时候回希望上次改变的变量在下一次调 ...
- linux进程的堆栈空间_代码段(指令,只读)、数据段(静态变量,全局变量)、堆栈段(局部变量)、栈【转】
转自:http://blog.csdn.net/gongweijiao/article/details/8207333 原文参见:http://blog.163.com/xychenbaihu@yea ...
- C++ 局部静态变量,全局变量,全局静态变量,局部变量的区别和联系
C++变量根据定义位置的不同,具有不同的作用域,作用域可分为6种:全局作用域,局部作用域,语句作用域,类作用域,命名作用域和文件作用域. 从作用域看: 全局变量具有全局作用域.全局变量只需在一个源文件 ...
- 说几个python与c区别的地方以及静态变量,全局变量的区别
一: python代码: a = 2 def b(): print a a = 4 print a b() 在b函数中,有a=4这样的代码,说明a是函数b内部的局部变量,而不是外部的那个值为2的全局变 ...
- C语言那年踩过的坑--局部变量,静态变量,全局变量在内存中存放的位置
先看几个概念: 1.bss是英文block started by symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0.bss段属于静态内存分配.它的初始 ...
- 内存四个领域,变量声明和定义,注册,c内联汇编,auto,堆,不变,静态变量
1.内存四大区域 2.在程序中,变量的声明能够有多份,定义仅仅能有一份 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdG90b3R1enVvcXVh ...
- c++ 类的静态变量
类的静态变量作为类的一部分,但不由类的创建产生,类的销毁而消失.静态变量和全局变量一样,会在main函数结束后销毁. 类可以对静态变量的值进行改变 #pragma once class ctest { ...
- php中静态变量和静态方法
1,静态变量:所有对象共享的变量成为静态变量.静态变量类似于全局变量,不过全局变量破坏对象的封装性,因此其对应于面向过程:静态变量对应于面向对象. 2,全局变量,全局变量的使用实例如下,声明全局变量时 ...
- block 解析 - 静态变量
静态变量 上一篇 我们了解了block全局变量的使用,静态变量和全局变量一样,可以直接在block内部使用,也可以在block内部修改 引用官方文档: Global variables are acc ...
- Java基础笔记(七)—— 成员变量、静态变量、局部变量
public class Test { int c; //成员变量(实例变量) static int s1; //静态变量(类变量)(全局变量) public static void main(Str ...
随机推荐
- CF1834
A 给出一个由 \(1,-1\) 组成的序列.一次操作可以让一个数变相反. 要多少次操作,才能让整个序列和非负且积等于 \(1\). 大 氵题. B 定义两个数 \(A,B\) 有一个价值:每一位上的 ...
- JS Leetcode 213. 打家劫舍 II 题解分析,在动态规划基础上感受分治算法的魅力
壹 ❀ 引 本题来自LeetCode 213. 打家劫舍 II,难度中等,属于前面我们做过的198. 打家劫舍的升级版,难度同样为中等,题目描述如下: 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内 ...
- Python枚举类型enum
为什么需要枚举 枚举(Enum)是一种数据类型,也是一种特别的类,是绑定到唯一值的符号表示,可以使用它来创建用于变量和属性的常量集枚举类可以看成是一个下拉菜单,给出特定的选项且这些选项不可修改,更贴近 ...
- 对称加密算法汇总:AES DES 3DES SM4 java 实现入门
密码的世界 如果你是黑帮老大,平时和手下沟通,如何保证自己的信息安全呢? 在神探夏洛克的第一季中,就讲述了一个如何侦破黑帮的加密交流的故事. 这种密码利用的是密码字典. 密码本身可以是一本书,比如常见 ...
- HTTP协议发展历程
HTTP协议发展历程 HTTP超文本传输协议是一个用于传输超文本文档的应用层协议,它是为Web浏览器与Web服务器之间的通信而设计的,HTTP协议到目前为止全部的版本可以分为HTTP 0.9.HTTP ...
- 虚拟机ubuntu配置静态IP
手头搭建了几天虚拟机ubuntu用来做微服务环境的搭建,目前使用的是DHCP分配的网络,每次启动各台服务器的ip都是随机的 管理起来有点乱,所以就要把他们配置成静态的ip.具体操作步骤如下: 我直接用 ...
- mysql-5.7.16-winx64配置文件
# For advice on how to change settings please see # http://dev.mysql.com/doc/refman/5.7/en/server-co ...
- java处理json类型数据--阿里巴巴fastjson api常用方法实战
fastjson介绍 最近工作上经常需要解析json类型数据以及java对象到json类型的互转,特地研究了下阿里巴巴的fastjson,这个是国内用的 比较多的json转换api,还有其他的入jac ...
- [攻防世界][江苏工匠杯]file_include
打开靶机url,上来就是代码审计 <?php highlight_file(__FILE__); include("./check.php"); if(isset($_GET ...
- springboot自动配置原理以及spring.factories文件的作用详解
一.springboot 自动配置原理 先说说我们自己的应用程序中Bean加入容器的办法: package com.ynunicom.dc.dingdingcontractapp; import co ...