extern变量如何在共享库中工作

yyny

假设我编写了一个简单的动态库,如下所示:

库文件

#pragma once

extern int x;
extern int p(void);

库文件

#include <lib.h>
#include <stdio.h>

x = 0;
int p(void) {
    printf("lib: %d\n", x++);
    return 0;
}

交流电

#include <lib.h>
#include <stdio.h>

int main(void) {
    for (; !p(); x--) printf("a.c: %d\n", x);
    return 0;
}

公元前

#include <lib.h>
#include <stdio.h>

int main(void) {
    for (; !p(); x = 0) printf("b.c: %d\n", x);
    return 0;
}

a和b将打印什么?我可以想到可能发生的几件事:

  • 链接器错误:已x声明extern但从未定义。
  • 每个过程都有自己的x,包括lib(bc始终为0,ac向下计数,lib向上计数)
  • 每个进程都可以x与共享lib(ac和bc始终为1,lib始终为0)
  • 所有进程共享相同的内容x,包括lib(ac,bc和lib返回随机值)
  • 所有进程共享相同的x,包括lib,直到有其他人对其进行lib写操作为止,然后该进程将获得其自己的版本x而不与之共享lib(在某处在线阅读)。(lib总是递增,bc总是打印0,ac递减计数)

通常会发生什么?我们应该知道的编译器/平台之间是否存在任何不一致之处?我们可以强制一种行为(我在思考__declspec(dllexport),编译器标志等)吗?

阿特·耶克斯(Art Yerkes)

这个问题有几个部分:

a和b将打印什么?我可以想到可能发生的几件事:

Linker error: x declared extern but never defined.

由于可能尚未将a和b内置到可执行文件中,因此不会打印任何内容。当然,您需要链接lib.so,lib.a或导入库lib.lib,以将可执行文件公开给x的可链接定义,否则,其他任何方法都不会起作用(大多数情况下,比尝试更复杂)。

Each process gets it's own x, including lib. (b.c is always 0, a.c counts down, lib counts up)

lib不是您的方案中的进程,而是共享库。共享库是在每个进程空间中单独加载和链接的,其中某些文件以动态加载程序(ld-linux.so,Windows上的ntdll.dll)理解的方式对其进行引用。每个进程在其地址空间中观察到加载的库的副本,而库本身也看到相同的副本,因此运行a时应打印0,然后永远打印1。运行p()并进行测试,打印x,将x递减为0。b还将打印0,然后永远打印1。p()被运行和测试,x被打印,x被设置为0。请注意,p()打印x ++,因此增量是在为printf的参数捕获值之后发生的。包含a和b的程序所引用的x变量特定于a或b的每次运行。这通常是通过在操作系统级别上通过将实际可加载库的页面从磁盘映射到内存并将它们设置为“写时复制”来实现的,在这种情况下,主机进程尝试进行更改会导致OS分配新页面并复制旧页面。内容放在首位。结果是,加载的库的未修改部分占用的实际内存更少。

Each process gets it's own x to share with lib. (a.c and b.c are always 1, lib is always 0)

库不是一个单独的过程。在a中执行p()会看到与a链接的x相同的x。

All processes share the same x, including lib. (a.c, b.c and lib return random values)

通常情况并非如此(另请参见下文)。

All processes share the same x, including lib, until someone other than lib writes to it, then that process gets it's own version of x, not shared with lib (Read this online somewhere). (lib always increments, b.c always prints 0, a.c counts down)

一些不支持单独地址空间的旧版运行时系统确实可以这种方式工作,尤其是amigados。您不太可能会碰到一个。

What typically happens? Are there any inconsistencies between compilers/platforms we should know about? Can we force one behaviour (I am thinking __declspec(dllexport), compiler flags, etc.)?

在大多数情况下,每个进程与该进程中加载​​的给定库的一个实例共享外部变量。除非您采取特定措施,否则这是预料之中的。

在评论中,还有其他一些问题:

Can windows dlls (or others) export non-function data.

是的。构建导入库时,请使用.def文件中的DATA限定词。对于其他功能,它与导出功能没有什么不同。但是,您将收到一个指向目标变量的指针,而不是绑定到所占用的空间。

Asterisk, see below?

在Windows上,节具有SHARED属性,该属性使加载程序在使用DLL的每个进程中分配同一页面。它不是默认值,您必须跳过各个步骤并使用特定于平台的编译指示来执行此操作。有很多原因不使用此功能。

大多数情况下,当dll要在许多进程中加载​​的自身副本之间共享状态时,它将使用主机系统的共享内存API(通常是CreateFileMapping或mmap)。这提供了灵活性(例如,所有进程可以共享x的一个版本,与所有b进程与x的另一个副本分开)。请注意,使用SHARED可能很容易意味着运行a可能会使b崩溃,而加载另一个长期运行的用户c可能会阻止a或b重新启动,直到重新启动为止。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章