c ++ linux двойное уничтожение статической переменной.связующие символы перекрываются - PullRequest
17 голосов
/ 16 июля 2011

Среда: 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 )

Этот пример работает нормально, на Windows, но на Linux - есть проблема.В Windows это работает нормально, решение должно быть похоже на изменение какой-либо опции связывания или чего-то в этом роде, но не на изменение структуры проекта и не на использование статических переменных.

Вывод:

Windows

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

Ответы [ 4 ]

8 голосов
/ 14 августа 2011

ОК, я нашел решение:

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

нм до исправления было:

0000000000200dd4 B _ZN5CTest4testE

после исправления:

0000000000200d7c b _ZN5CTest4testE

Разница заменена глобальным символом "B" на локальный символ "b ".

Вместо добавления атрибута ((visibility (" hidden ")))" к символам, можно использовать опцию компилятора "-fvisibility = hidden".Эта опция заставляет gcc вести себя как Windows env.

5 голосов
/ 13 мая 2017

TL; DR: вы не должны связывать библиотеку один раз как статическую зависимость, а один раз как динамическую зависимость.


Как деструкторы статических переменных выполняются в Itanium ABI (используется clang, gcc, icc ...)?

Стандартная библиотека C ++ предлагает стандартное средство для планирования выполнения функции во время завершения работы программы ( после завершения main) в формате atexit.

Поведение относительно простое, atexit в основном создает стек обратных вызовов и, таким образом, выполняет их в обратном порядке их планирования.

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


Что происходит, когда существует статическая переменная и в статически связанной библиотеке и в динамически связанной библиотеке?

Он пытается существовать дважды.

Каждая библиотека будет иметь:

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

Удивление связано с тем, как в загрузчике работает разрешение символов. По сути, загрузчик создает отображение между символом и местоположением (указателем) в порядке очереди.

Однако секции загрузки / выгрузки являются безымянными, поэтому каждая из них выполняется полностью.

Таким образом:

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

И что?

Решение простое: НЕ связывать как со статической библиотекой A (напрямую), так и с динамической библиотекой B, также связывающейся с A (динамически или статически).

В зависимости от варианта использования вы можете:

  • ссылка статически против B,
  • динамически связывается с A и B.

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

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

Это поведение по умолчанию для Windows, поэтому там требуется атрибут DLLExport и почему, поскольку он был забыт для CTest::test, поведение в Windows отличается.

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

2 голосов
/ 16 июля 2011

Кстати, если определить static var внутри функции stat_useStatic, это будет только один экземпляр этого static 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 отличается еще больше:

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

Asвы можете видеть, Linux создает только один статический var, но Windows создает два экземпляра.

Действительно, похоже, что linux не должен дважды создавать и дважды уничтожать статический var в первом случае, по своей логике, так же, как во второмcase (статическая переменная внутри func).

Использование функции local static var вместо класса static - это просто обходной путь, а не реальное решение.Поскольку источник библиотеки может быть недоступен.

1 голос
/ 16 июля 2011

Трудно сказать, не видя какого-либо кода, но эта территория (динамически загружаемые библиотеки) на самом деле явно не охватывается стандартом, поэтому вполне возможно, что разные реализации будут обрабатывать побочные случаи по-разному.

Не можете ли вы просто избежать этой путаницы, например, используя разные пространства имен для двух экземпляров статической библиотеки (например, создав пространство имен для статического объекта, определенного параметром командной строки)?

...