С++ 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 отличается.

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