С++ linux двойное уничтожение статической переменной. перекрытие совпадающих символов
Среда: linux x64, компилятор gcc 4.x
Проект имеет следующую структуру:
static library "slib"
-- inside this library, there is static object "sobj"
dynamic library "dlib"
-- links statically "slib"
executable "exe":
-- links "slib" statically
-- links "dlib" dynamically
в конце программы, "sobj" разрушается дважды. Такое поведение ожидается, НО оно будет разрушено дважды по одному и тому же адресу памяти, т.е. Тот же самый "this" в деструкторе - в результате возникает проблема двойного уничтожения.
Я думаю, что это связано с перекрытием символа.
Какое решение для этого конфликта? Может быть, какой-то вариант ссылок?
Вот тестовый пример:
main_exe.cpp
#include <cstdlib>
#include "static_lib.h"
#include "dynamic_lib.h"
int main(int argc, char *argv[])
{
stat_useStatic();
din_useStatic();
return EXIT_SUCCESS;
}
static_lib.h
#ifndef STATIC_LIB_H
#define STATIC_LIB_H
#include <cstdio>
void stat_useStatic();
struct CTest
{
CTest(): status(isAlive)
{
printf("CTest() this=%d\n",this);
}
~CTest()
{
printf("~CTest() this=%d, %s\n",this,status==isAlive?"is Alive":"is Dead");
status=isDead;
}
void use()
{
printf("use\n");
}
static const int isAlive=12385423;
static const int isDead=6543421;
int status;
static CTest test;
};
#endif
static_lib.cpp
#include "static_lib.h"
CTest CTest::test;
void stat_useStatic()
{
CTest::test.use();
}
dynamic_lib.h
#ifndef DYNAMIC_LIB_H
#define DYNAMIC_LIB_H
#include "static_lib.h"
#ifdef WIN32
#define DLLExport __declspec(dllexport)
#else
#define DLLExport
#endif
DLLExport void din_useStatic();
#endif
dynamic_lib.cpp
#include "dynamic_lib.h"
DLLExport void din_useStatic()
{
CTest::test.use();
}
CMakeLists.txt
project( StaticProblem )
cmake_minimum_required(VERSION 2.6)
if(WIN32)
else(WIN32)
ADD_DEFINITIONS(-fPIC)
endif(WIN32)
ADD_LIBRARY( static_lib STATIC static_lib.cpp static_lib.h)
ADD_LIBRARY( dynamic_lib SHARED dynamic_lib.cpp dynamic_lib.h)
TARGET_LINK_LIBRARIES( dynamic_lib static_lib )
ADD_EXECUTABLE( main_exe main_exe.cpp )
TARGET_LINK_LIBRARIES( main_exe static_lib dynamic_lib )
Этот пример работает нормально, на окнах, но на linux - есть проблема.
Поскольку это нормально работает в окнах, решение должно быть похоже на изменение какой-либо опции привязки или что-то в этом роде, но не изменение структуры проекта или использование статических vars.
Вывод:
Окна
CTest() this=268472624
CTest() this=4231488
use
use
~CTest() this=4231488, is Alive
~CTest() this=268472624, is Alive
Linux
CTest() this=6296204
CTest() this=6296204
use
use
~CTest() this=6296204, is Alive
~CTest() this=6296204, is Dead
Ответы
Ответ 1
ОК, я нашел решение:
http://gcc.gnu.org/wiki/Visibility
Например, если изменение
static CTest test;
к
__attribute__ ((visibility ("hidden"))) static CTest test;
проблема исчезнет.
Linux:
CTest() this=-1646158468
CTest() this=6296196
use
use
~CTest() this=6296196, is Alive
~CTest() this=-1646158468, is Alive
nm выход до исправления:
0000000000200dd4 B _ZN5CTest4testE
после исправления:
0000000000200d7c b _ZN5CTest4testE
Разница меняется на глобальный символ "B" на локальный символ "b".
Вместо добавления атрибута ((видимость ( "скрытый" ))) "к символам можно использовать параметр компилятора" -fvisibility = hidden ". Этот параметр делает gcc более похожим на Windows env.
Ответ 2
Кстати, если определить static var внутри функции stat_useStatic, это будет только один экземпляр этого статического var в всей программе в Linux (но два экземпляра в Windows) - и это мы используем для обхода этой проблемы.
Вот изменения
void stat_useStatic()
{
static CTest stest;
stest.use();
CTest::test.use();
}
DLLExport void din_useStatic()
{
stat_useStatic();
CTest::test.use();
}
Теперь поведение Linux и Windows отличается еще больше:
Окна
CTest() this=268476728
CTest() this=4235592
CTest() this=4235584
use
use
CTest() this=268476720
use
use
use
~CTest() this=4235584, is Alive
~CTest() this=4235592, is Alive
~CTest() this=268476720, is Alive
~CTest() this=268476728, is Alive
Linux
CTest() this=6296376
CTest() this=6296376
CTest() this=6296392
use
use
use
use
use
~CTest() this=6296392, is Alive
~CTest() this=6296376, is Alive
~CTest() this=6296376, is Dead
Как вы можете видеть, linux создает только один статический var, но окна создают два экземпляра.
В действительности, похоже, что linux не должен удваивать создание и двойное уничтожение статического var в первом случае, по его логике, то же, что и во втором случае (static var внутри func).
Использование функции local static var вместо класса static является обходным, а не реальным решением. Поскольку источник библиотеки может быть недоступен.
Ответ 3
Трудно сказать, не видя никакого кода, но эта территория (динамически загружаемые библиотеки) на самом деле явно не покрыта стандартом, поэтому вполне возможно, что разные реализации будут обрабатывать боковые случаи по-разному.
Нельзя ли просто избежать этой путаницы, например, используя разные пространства имен для двух экземпляров статической библиотеки (например, путем использования пространства имен для статического объекта, определенного параметром командной строки)?
Ответ 4
TL; DR: вам не следует связывать библиотеку один раз как статическую зависимость и один раз в качестве динамической зависимости.
Как деструкторы статических переменных выполняются в Itanium ABI (используется clang, gcc, icc...)?
Стандартная библиотека С++ предлагает стандартное средство для планирования выполнения функции во время выключения программы (после завершения main) в формате atexit
.
Поведение относительно простое, atexit
в основном создает стек обратных вызовов и, таким образом, выполняет их в обратном порядке их планирования.
Всякий раз, когда статическая переменная строится, сразу после ее завершения завершается обратный вызов в стеке atexit
, чтобы уничтожить его во время выключения.
Что происходит, когда статическая переменная существует как в статически связанной библиотеке, так и в динамически связанной библиотеке?
Он пытается существовать дважды.
Каждая библиотека будет иметь:
- область памяти, зарезервированную для переменной, на которую указывает соответствующий символ (искаженное имя для переменной),
- запись в разделе загрузки для создания переменной и расписание ее уничтожения.
Удивление исходит из того, как в загрузчике работает разрешение символа. По сути, загрузчик создает сопоставление между символом и местоположением (указателем), в порядке первого поступления.
Однако разделы загрузки/выгрузки являются безымянными, и поэтому каждый из них выполняется полностью.
Таким образом:
- статическая переменная строится в первый раз,
- статическая переменная конструируется второй раз над первой (которая просочилась),
- статическая переменная разрушается в первый раз,
- статическая переменная разрушается второй раз; который обычно обнаруживает проблему.
Так что?
Решение прост: НЕ связывается как с статической библиотекой A (напрямую), так и с динамической библиотекой B, также связывающей A (динамически или статически).
В зависимости от варианта использования вы можете:
- ссылка статически против B,
- динамически связывается с A и B.
Поскольку он работает нормально в Windows, решение должно быть похоже на изменение какой-либо опции привязки или что-то в этом роде, но не изменение структуры проекта или использование статических vars.
В маловероятном случае, когда вам действительно нужны два независимых экземпляра статической переменной, кроме рефакторинга вашего кода, можно вместо этого скрывать символы в динамической библиотеке.
Это поведение по умолчанию Windows, поэтому здесь требуется атрибут DLLExport
, и почему, поскольку он был забыт для CTest::test
, поведение в Windows отличается.
Помните, однако, что любой будущий сторонник этого проекта громко проклинает вас, если вы выберете это поведение. Никто не ожидает, что статическая переменная будет иметь несколько экземпляров.