我有两个控制台应用程序A1.exe A2.exe和一个DLL。两者都在调试模式下运行,optmization关闭。 有一个全局const char *变量,我从这个dll导出并导入回A1和A2:
//dll.h
extern "C" {DLLEXPORT extern const char* str;}
//dll.cpp
const char *str = "qwerty123";
我期待" qwerty123"要在DLL的只读部分创建,我希望Windows的内存管理器将实际内存与此字符串映射到A1.exe的某个虚拟内存地址和A2.exe的不同虚拟地址,并且不创建数据的真实副本。我希望这也会发生在该dll的所有函数定义中。
我同时运行两个应用程序,它们都打印从DLL导入的正确字符串。但是我想要一些证据,所以我粗暴地使用Cheat Engine附加到A1.exe进程并将该只读字符串更改为某个不同的值。结果是A1.exe打印新值,A2.exe仍然打印旧值。怎么解释这个? 1.我认为它是只读存储器,它将被共享以节省实际内存,那么为什么值只为一个应用程序改变了? 2.如何证明具有程序代码(导出函数)的部分不会在两个进程中重复?
答案 0 :(得分:2)
“只读内存”的概念'在很大程度上不适用于这种讨论。是的,操作系统有一些方法可以使某些内存区域对程序显示为只读,但是a)这个内存与任何其他内存完全一样,并且b)你的字符串文字没有存储到任何这样的内存中反正。
当您将字符串声明为const
时,您只是阻止自己从C ++程序中更改它,并且您可能还启用了一些编译器优化,但是因为您在排除故障时已正确关闭它们,在这次讨论中不发挥任何作用。为了证明我在说什么,你可以抛弃那个char*
指针的常量,然后你就可以很好地修改它指向的字符串。
DLL的每个实例都有自己的数据段。 DLL实例不共享其数据。因此,当然,当您修改项目时,您只是在DLL的一个实例的数据段中修改它,而另一个DLL实例的数据段保持不变。
要共享数据,您需要使用VirtualAllocEx()
和WriteProcessMemory()
,或者更好的CreateFileMapping()
和MapViewOfFile[Ex]()
,或其他一些机制。
答案 1 :(得分:2)
您在这里考虑的优化称为写入时复制。在Visual C ++的情况下,DLL中定义的所有全局变量最初都加载到具有PAGE_WRITECOPY
属性的共享内存页中。如果某个进程写入这样的位置,它将接收具有PAGE_READWRITE
属性的自己的页面并进一步使用它。 Visual C ++似乎在const和非const全局变量之间没有区别,因为该属性是编译器功能。例如,它可以被抛弃,并从操作系统的角度处理它将是一个头痛和安全漏洞。
尝试在网上搜索PAGE_WRITECOPY
以了解详情。
答案 2 :(得分:0)
对于连接DLL的进程,DLL的“只读全局变量”的值可能不同。因此,如果DLL为多个进程共享“只读全局变量”的单个物理内存,则是不安全的。谨慎但安全的解决方案是为多个进程创建“只读全局变量”的不同物理内存实例。
例如,在dll.cpp中,
int foo(){return 1;}
typedef int (*C_pFunc)();
const C_pFunc pf=foo;
显然,A.exe和B.exe都加载了dll,DLL中的函数foo()可能在A.exe和B.exe中具有不同的逻辑地址。因此,全局const变量pf在A.exe和B.exe中可能具有不同的初始化值。
这是COM dll的典型现象,当COM dll附加到进程时,在此进程中应使用函数逻辑地址初始化许多vtables作为全局const变量。