Что на самом деле происходит, когда две общие библиотеки определяют один и тот же символ? - PullRequest
1 голос
/ 16 марта 2019

Недавно я столкнулся с проблемой сбоя, когда соединил две общие библиотеки (обе были созданы мной).В конце концов я обнаружил, что это произошло из-за одного исходного файла , дублированного между двумя файлами.В этом исходном файле был определен глобальный std :: vector (фактически статический член класса), и в итоге он был освобожден дважды - по одному для каждой библиотеки.

Затем я написал некоторый тестовый кодчтобы проверить мои мысли.Здесь в заголовке я объявляю класс и глобальный объект этого класса:

#ifndef SHARED_HEADER_H_
#define SHARED_HEADER_H_

#include <iostream>

struct Data {
  Data(void) {std::cout << "Constructor" << std::endl;}
  ~Data(void) {std::cout << "Destructor" << std::endl;}
  int FuncDefinedByLib(void) const;
};

extern const Data data;

#endif

Функция FuncDefinedByLib оставлена ​​неопределенной.Затем я создал две библиотеки libA и libB, обе содержат этот заголовок.libA выглядит следующим образом

const Data data;

int Data::FuncDefinedByLib(void) const {return 1;}

void PrintA(void) {
  std::cout << "LibB:" << &data << " "
    << (void*)&Data::FuncDefinedByLib <<  " "
    << data.FuncDefinedByLib() << std::endl;
}

Он определяет глобальный объект data, функцию FuncDefinedByLib и функцию PrintA, которая печатает адрес объекта data, адресFuncDefinedByLib, и возвращаемое значение FuncDefinedByLib.

libB практически совпадает с libA, за исключением того, что имя PrintA изменяется на PrintB и FuncDefinedByLib возвращает 2 вместо 1.

Затем я создаю программу, которая связывает обе библиотеки и вызывает PrintA и PrintB.Прежде чем столкнуться с проблемой сбоя, я думал, что обе библиотеки создадут свои собственные версии class Data.Однако фактический вывод

Constructor
Constructor
LibB:0x7efceaac0079 0x7efcea8bed60 1
LibB:0x7efceaac0079 0x7efcea8bed60 1
Destructor
Destructor

Указывает, что обе библиотеки используют только одну версию class Data и только одну версию const Data data, даже если класс и объект определены по-разному, то есть от libA (Я так понимаю, потому что libA связан первым).И двойное уничтожение ясно объясняет мою проблему с аварией.

Итак, вот мои вопросы

  1. Как это происходит?Я понимаю, что основной код, ссылающийся на две библиотеки, может ссылаться только на первый символ, который он видит.Но разделяемая библиотека должна была быть внутренне связана при ее создании (или нет? У меня действительно нет особых знаний о разделяемой библиотеке), как они могут знать, что в других библиотеках есть класс-близнец, и ссылаться на него, когда послебыл создан самостоятельно?

  2. Я знаю, что дублирующий код между общими библиотеками - это вообще плохая практика.Но есть ли условие, что при этом дублирование между библиотеками безопасно?Или есть систематический способ сделать мой код дублирующимся без риска?Или это никогда не безопасно и всегда должно быть строго запрещено?Я не хочу всегда разбивать другую разделяемую библиотеку, просто чтобы поделиться крошечным фрагментом кода.

  3. Такое поведение выглядит волшебным.Кто-нибудь использует это поведение для совершения каких-то хороших волшебных вещей?

1 Ответ

1 голос
/ 16 марта 2019

Часть 1: О компоновщике

Это известная проблема как в C, так и в C ++, и она является результатом текущей модели компиляции. Полное объяснение , как это происходит, выходит за рамки этого ответа, однако этот доклад Мэтта Годболта дает подробное объяснение процесса для начинающих. Смотрите также эту статью на компоновщике .

В 2020 году выйдет новая версия C ++, в которой будет представлена ​​новая модель компиляции (называемая модулями), которая позволяет избежать подобных проблем. Вы сможете импортировать и экспортировать вещи из модуля, подобно тому, как пакеты работают в Java.

Часть 2. Решение вашей проблемы

Есть несколько разных решений.

Волшебное решение 1: одна уникальная глобальная переменная

Это довольно гладко. Если вы поместите глобальную переменную внутри функции как статическую переменную, она будет всегда создаваться только один раз, и это обеспечивается стандартом (даже в многопоточной среде).

#ifndef SHARED_HEADER_H_
#define SHARED_HEADER_H_

#include <iostream>

struct Data {
  Data(void) {std::cout << "Constructor" << std::endl;}
  ~Data(void) {std::cout << "Destructor" << std::endl;}
  int FuncDefinedByLib(void) const;
};
Data& getMyDataExactlyOnce() {
    // The compiler will ensure
    // that data only gets constructed once
    static Data data;
    // Because data is static, it's fine to return a reference to it
    return data; 
}

// Here, the global variable is a reference
extern const Data& data = getMyDataExactlyOnce();

#endif

Волшебное решение 2. Несколько различных глобальных переменных, по одной на единицу перевода

Если вы пометите глобальную переменную как встроенную в C ++ 17, то каждый модуль перевода, который включает в себя заголовок, получает свою собственную копию в своем собственном месте в памяти. См .: https://en.cppreference.com/w/cpp/language/inline

#ifndef SHARED_HEADER_H_
#define SHARED_HEADER_H_

#include <iostream>

struct Data {
  Data(void) {std::cout << "Constructor" << std::endl;}
  ~Data(void) {std::cout << "Destructor" << std::endl;}
  int FuncDefinedByLib(void) const;
};
// Everyone gets their own copy of data
inline extern const Data data;

#endif

Часть 3: Можем ли мы использовать это для создания Темной Магии?

Вид. Если вы действительно хотите использовать Dark Magic с глобальными переменными, C ++ 14 вводит templated глобальные переменные:

template<class Key, class Value>
std::unordered_map<Key, Value> myGlobalMap; 

void foo() {
    myGlobalMap<int, int>[10] = 20;
    myGlobalMap<std::string, std::string>["Hello"] = "World"; 
}

Делай из того, что хочешь. У меня не было большого применения для шаблонных глобальных переменных, хотя я представляю, что если бы вы делали что-то вроде подсчета количества вызовов функции или числа созданий типа, было бы полезно сделать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...