Когда глобальная переменная будет экспортирована в исполняемый файл?

Предположим, у меня есть библиотека (общая или статическая) с двумя файлами: 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. Я могу это понять, включая заголовок, который будет инициализировать trigger, но почему log_console также будет инициализирован? Каков внутренний механизм компилятора/компоновщика?

Возможные относительные ссылки:

  1. Языковая связь

  2. Срок хранения

Но я думаю, что это может быть «ФУНКЦИЯ» какой-то конкретной цепочки инструментов (я использую: gcc 14.1.1 на своем рабочем столе x64 с Manjaro Linux).

@user12002570 user12002570 Кажется, нет. Конструкторы вообще не вызываются, не в том порядке.

HolyBlackCat 18.08.2024 06:45

Зачем вам вообще нужны глобальные переменные? Узнайте о внедрении зависимостей или хотя бы о синглтоне Мейера. Я избегаю глобальных переменных, как чумы, потому что они затрудняют модульное тестирование кода, а во время обслуживания (десятилетий!) Могут появиться зависимости, которые все равно вызовут фиаско статической инициализации.

Pepijn Kramer 18.08.2024 06:57

Компоновщик, вероятно, просто отбрасывает весь lib.cpp, потому что он может определить, что он не используется в программе.

Alan Birtles 18.08.2024 07:08

Я пробовал использовать Window Visual C++ и Linux g++. Я не могу получить копию, просто напечатав «Готово». только. Как ни странно, если я изменю порядок исходных файлов в командной строке: g++ -std=c++17 lib.cpp main.cpp вместо g++ -std=c++17 main.cpp lib.cpp, я получу ошибку сегментации при запуске результирующего ./a.out.... О боже, я использую древнюю версию gcc...

selbie 18.08.2024 07:49

Я не думаю, что компоновщику следует отбрасывать весь lib.cpp, потому что я создаю библиотеку. Как статическая библиотека, она должна экспортировать эти глобальные переменные и позволить исполняемому файлу инициализировать их перед основной.

adversarr 18.08.2024 08:30

динамическое связывание не стандартизировано и фактически отличается в Windows и Linux.

Gene 18.08.2024 09:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
7
89
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

И trigger, и log_console инициализируются тогда и только тогда, когда lib.cpp действительно связан с процессом.

Когда вы встраиваете lib.cpp в общую библиотеку и связываете основной исполняемый файл напрямую с этой общей библиотекой, динамический загрузчик загружает общую библиотеку в ваш процесс и инициализирует ее (до запуска первой инструкции исполняемого файла).

Когда вы встраиваете lib.cpp в архивную библиотеку, то, что происходит, зависит от того, выберет ли статический компоновщик lib.o в ссылку или нет.

Компоновщик выберет lib.o в ссылке только в том случае, если для этого есть какая-то причина. Подробности смотрите в этом посте.

Когда вы #include "lib.h", действительно существует такая ссылка от main.o до lib.o, и компоновщик втягивает lib.o в основной исполняемый файл.

Когда вы закомментируете // #include "lib.h", в main.o нет ничего, что указывало бы компоновщику на необходимость lib.o, поэтому он пропускает этот объект.

Вы можете наблюдать это в действии:

  • с закомментированным #includeg++ main.cpp удастся создать основной двоичный файл
  • с комментарием #include та же команда завершится неудачей с неразрешенным trigger.

Вы также можете проверить nm main.o в обоих случаях и заметить, что с #include есть неразрешенный символ trigger, а с закомментированным - нет.

P.S. Компоновщик может решить, связывать lib.o с двоичным файлом или нет, только если lib.o находится в архивной библиотеке. Если lib.o или lib.cpp указаны непосредственно в командной строке, то у компоновщика нет выбора, и он должен включить lib.o (именно поэтому эксперимент Селби не смог воспроизвести наблюдаемое поведение).

Другие вопросы по теме