外部变量如何在共享库中工作

How does an extern variable work in a shared library

假设我写了一个像这样的简单动态库:

lib.h

#pragma once

extern int x;
extern int p(void);

lib.c

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

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

a.c

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

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

b.c

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

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

a 和 b 会打印什么? 我能想到一些可能发生的事情:

通常会发生什么?我们应该知道 compilers/platforms 之间是否存在任何不一致?我们可以强制执行一种行为吗(我在想 __declspec(dllexport)、编译器标志等)?

这个问题有几个部分:

a 和 b 会打印什么?我能想到一些可能发生的事情:

Linker error: x declared extern but never defined.

不会打印任何内容,因为 a 和 b 可能尚未内置到可执行文件中。当然,您需要 link lib.so、lib.a 或导入库 lib.lib 以将可执行文件暴露给 x 的 linkable 定义,否则其他任何东西都不起作用(大多数情况下,如果努力的话,它会比这更复杂)。

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

lib 在您的场景中不是一个进程,它是一个共享库。共享库在每个进程 space 中单独加载和 linked,其中某些内容以动态加载程序理解的方式引用它(ld-linux.so,ntdll.dll on windows).每个进程在其地址 space 中观察到已加载库的副本,并且库本身看到相同的副本,因此 运行ning a 应该打印 0,然后永远打印 1。 p() 是 运行 并经过测试,打印 x,x 递减回 0。b 也将打印 0,然后永远打印 1。 p() 是 运行 并经过测试,x 被打印,x 被设置为 0。请注意 p() 打印 x++ 因此增量发生在为 printf 的参数捕获值之后。包含a和b的程序所引用的x变量是特定于a或b的每个运行。这通常在 OS 级别通过将实际可加载库的页面从磁盘映射到内存并设置它们 "copy-on-write" 来完成,其中主机进程尝试更改导致 OS 分配一个新页面并首先复制旧内容。结果是加载库中未修改的部分占用的实际内存较少。

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

Lib 不是一个单独的进程。在 a 中执行 p() 会看到与由 a.

编辑的 link 相同的 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)

一些不支持单独地址 space 的旧 运行 时间系统确实以这种方式工作,特别是 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 限定符。对于其他人来说,这与导出函数没有什么不同。但是,您将收到一个指向目标变量的指针,而不是绑定到 space 占用。

Asterisk, see below?

在 windows 上,部分具有 SHARED 属性,导致加载程序在使用 DLL 的每个进程中分配相同的页面。这不是默认设置,您必须跳转并使用特定于平台的编译指示来完成它。有很多理由不使用它。

大多数时候,当一个 dll 想要在多个进程中加载​​的自身副本之间共享状态时,它会使用主机系统的共享内存 API(通常是 CreateFileMapping 或 mmap)。这允许灵活性(例如,所有 a 进程可以共享一个版本的 x,与具有 x 的另一个副本的所有 b 进程分开)。请注意,使用 SHARED 很容易意味着 运行ning a 可能会使 b 崩溃,并且加载另一个长 运行ning 用户 c 可能会导致 a 或 b 在重新启动之前无法再次启动。