Динамические загруженные библиотеки и совместно использованные глобальные символы

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

Сначала нам нужна статически связанная библиотека: заголовок test.hpp

#ifndef __BASE_HPP
#define __BASE_HPP

#include <iostream>

class test {
private:
  int value;
public:
  test(int value) : value(value) {
    std::cout << "test::test(int) : value = " << value << std::endl;
  }

  ~test() {
    std::cout << "test::~test() : value = " << value << std::endl;
  }

  int get_value() const { return value; }
  void set_value(int new_value) { value = new_value; }
};

extern test global_test;

#endif // __BASE_HPP

и источник test.cpp

#include "base.hpp"

test global_test = test(1);

Затем я записал динамично загруженную библиотеку: library.cpp

#include "base.hpp"

extern "C" {
  test* get_global_test() { return &global_test; }
}

и клиентская загрузка программы эта библиотека: client.cpp

#include <iostream>
#include <dlfcn.h>
#include "base.hpp"

typedef test* get_global_test_t();

int main() {
  global_test.set_value(2); // global_test from libbase.a
  std::cout << "client:        " << global_test.get_value() << std::endl;

  void* handle = dlopen("./liblibrary.so", RTLD_LAZY);
  if (handle == NULL) {
    std::cout << dlerror() << std::endl;
    return 1;
  }

  get_global_test_t* get_global_test = NULL;
  void* func = dlsym(handle, "get_global_test");
  if (func == NULL) {
    std::cout << dlerror() << std::endl;
    return 1;
  } else get_global_test = reinterpret_cast<get_global_test_t*>(func);

  test* t = get_global_test(); // global_test from liblibrary.so
  std::cout << "liblibrary.so: " << t->get_value() << std::endl;
  std::cout << "client:        " << global_test.get_value() << std::endl;

  dlclose(handle);
  return 0;
}

Теперь я компилирую статически загруженную библиотеку с

g++ -Wall -g -c base.cpp
ar rcs libbase.a base.o

динамично загруженная библиотека

g++ -Wall -g -fPIC -shared library.cpp libbase.a -o liblibrary.so

и клиент

g++ -Wall -g -ldl client.cpp libbase.a -o client 

Теперь я наблюдаю: клиент и динамично загруженная библиотека обладают другой версией переменной global_test. Но в моем проекте я использую cmake. Сценарий сборки похож на это:

CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
PROJECT(globaltest)

ADD_LIBRARY(base STATIC base.cpp)

ADD_LIBRARY(library MODULE library.cpp)
TARGET_LINK_LIBRARIES(library base)

ADD_EXECUTABLE(client client.cpp)
TARGET_LINK_LIBRARIES(client base dl)

анализ созданного makefiles я нашел, что cmake создает клиент с

g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client

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

Я использую cmake неправильным способом? Действительно ли возможно, что клиент и динамично загруженная библиотека используют то же global_test но без этой двойной проблемы разрушения?

9
задан phlipsy 9 June 2010 в 08:53
поделиться

5 ответов

g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client

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

Но... Вы не должны делать ничего подобного. Ваши библиотеки и исполняемый файл не должны не должны совместно использовать символы, если только они действительно не должны быть совместно использованы.

Всегда думайте о динамическом связывании как о статическом связывании.

3
ответ дан 4 December 2019 в 21:48
поделиться

По умолчанию компоновщик не объединяет глобальную переменную ('D') в базовом исполняемом файле с переменной в разделяемой библиотеке. Базовый исполняемый файл особенный. Возможно, существует какой-то непонятный способ сделать это с помощью одного из тех непонятных управляющих файлов, которые читает ld, но я как бы в этом сомневаюсь.

- export-dynamic сделает символы a.out 'D' доступными для разделяемых библиотек.

Однако рассмотрим процесс. Шаг 1: вы создаете DSO из .o с буквой «U» и .a с буквой «D». Итак, компоновщик включает символ в DSO. На шаге 2 вы создаете исполняемый файл с буквой «U» в одном из файлов .o и буквой «D» в файлах .a и DSO. Он попытается разрешить, используя правило письма слева направо.

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

2
ответ дан 4 December 2019 в 21:48
поделиться

Мой первый вопрос - есть ли какая-нибудь конкретная причина, по которой вы и статически, и динамически (через dlopen) связываете один и тот же код?

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

EDIT: учитывая вашу цель, я бы связал вашу программу таким образом:

g++ -Wall -g -ldl client.cpp -llibrary -L. -o client

Возможно, вам придется исправить порядок.

1
ответ дан 4 December 2019 в 21:48
поделиться

При использовании разделяемых библиотек вы должны определить материал, который вы хотите экспортировать, с помощью макроса, например здесь . См. Определение макроса DLL_PUBLIC.

2
ответ дан 4 December 2019 в 21:48
поделиться

Я бы посоветовал использовать dlopen(... RTLD_LAZY|RTLD_GLOBAL); для объединения глобальных таблиц символов.

1
ответ дан 4 December 2019 в 21:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: