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

时间:2016-10-14 17:33:27

标签: c shared-libraries extern

说我写了这样一个简单的动态库:

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;
}

交流转换器

#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打印什么? 我可以想到可能发生的一些事情:

  • 链接器错误:x已声明extern但未定义。
  • 每个进程都拥有自己的x,包括lib。 (b.c始终为0,a.c倒计时,lib计数)
  • 每个进程都有自己的xlib共享。 (a.c和b.c始终为1,lib始终为0)
  • 所有进程共享相同的x,包括lib。 (a.c,b.c和lib返回随机值)
  • 所有进程共享相同的x,包括lib,直到lib以外的人写入,然后该进程获得自己的x版本,lib共享(在某处在线阅读)。 (lib总是递增,b.c总是打印0,a.c倒计时)

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

1 个答案:

答案 0 :(得分:1)

这个问题有几个部分:

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

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()会看到与由。

链接的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再次启动,直到重新启动。