全局变量什么时候会导出到可执行文件?

问题描述 投票:0回答:1

假设我有一个包含两个文件的库(共享或静态):

lib.h
lib.cpp

lib.h

#pragma once

struct Trigger {
  struct TriggerImpl{
    TriggerImpl();
  };
  inline static TriggerImpl trigger;
};

lib.cpp

#include "lib.h"
#include <iostream>

struct LogConsole {
  LogConsole() { std::cout << "LogConsole constructor" << std::endl; };
};

LogConsole log_console;

Trigger::TriggerImpl::TriggerImpl() {
  std::cout << "Trigger constructor" << std::endl;
}

然后我将库链接到可执行文件。

main.cpp

#include <iostream>
// #include "lib.h" ///< uncomment this line and you will get different result!!!

int main() {
  std::cout << "Done." << std::endl;
  return 0;
}

使用静态链接(例如 CMake

target_link_library(... STATIC ...)
),您将得到:

Done.

通过动态链接(例如 CMake

target_link_library(... SHARED ...)
),您将得到:

Trigger constructor
LogConsole constructor
Done.

但是,如果取消注释

#include
行并使用静态链接,您将得到:

Trigger constructor
LogConsole constructor
Done.

我的问题是:

  1. 为什么动态链接和静态链接会(或不会)调用全局变量的构造函数?

  2. 我可以理解,包含header会初始化

    trigger
    ,但是为什么
    log_console
    也会被初始化呢?编译器/链接器的内部机制是什么?

可能的相关链接是:

  1. 语言联动

  2. 存储时间

但我认为这可能是某些特定工具链的“功能”(我正在使用的是:

gcc 14.1.1
在我的带有 Manjaro Linux 的 x64 桌面上)。

c++ gcc dll constructor linker
1个回答
0
投票

当且仅当

 
trigger
 实际上链接到流程中时,
log_consolelib.cpp
 都会被初始化。

当您将

lib.cpp

 构建到共享库中,并将主可执行文件直接与该共享库链接时,动态加载器会将共享库加载到您的进程中并对其进行初始化(在可执行文件的第一条指令运行之前)。

当您将

lib.cpp

 构建到存档库中时,会发生什么取决于静态链接器是否选择 
lib.o
 到链接中。

链接器只会选择

lib.o

 进入链接 
if 有某种原因这样做。请参阅这篇文章了解完整详细信息。

当您

#include "lib.h"

 时,确实存在从 
main.o
lib.o
 的引用,并且链接器将 
lib.o
 拉入主可执行文件中。

当您注释掉

// #include "lib.h"

 时,
main.o 中没有 nothing
,这表明链接器需要 
lib.o
,因此它会跳过该对象。

您可以观察实际情况:

  • #inlude
     注释掉,
    g++ main.cpp
     将成功生成主二进制文件
  • 如果添加了
  • #include
     注释,相同的命令将会失败并出现未解决的 
    trigger
您还可以检查这两种情况的

nm main.o

 并观察到 
#include
 存在未解析的 
trigger
 符号,而注释掉后则没有。

附注仅当

lib.o

 位于存档库中时,链接器才会决定是否将 
lib.o
 链接到二进制文件中。如果 
lib.o
lib.cpp
 直接在命令行上列出,则链接器没有选择(这就是为什么 selbie 的实验未能重现观察到的行为)。

© www.soinside.com 2019 - 2024. All rights reserved.