Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Применение статических переменных и статическая линковка исполняемых модулей друг в друга

Anna | 24.06.2014 | нет комментариев
Сегодня я хочу рассказать о некоторых хитрых особенностях статических переменных при неправильной линковке исполняемых модулей. Я покажу загвоздку из моей реальной практики, которая может появиться у всякого.
Разжевываю все достаточно подробно, следственно у «бывалых» и красноглазиков может появиться чувство, что я «колупаюсь в песочнице», но это статья не только для них.

Давайте предположим обстановку: есть определенный класс, реализованный в статической библиотеке (lib). Эту библиотеку статически привязывает модуль реализации (dll). Дальше эту dll также статически привязывает исполняемый модуль (exe). Помимо этого Exe-модуль статически линкует статическую библиотеку (lib).
Приблизительно так:image

Скажем, тут есть дальнейшая логика: в lib’е реализован определенный инструмент для чего-либо. В dll’е реализована некоторая функциональность на основе данного инструмента. В exe реализован тест на эту функциональность. Dll сама не экспортирует инструментальный класс (тот, что находится в lib’e), следственно тесту требуется статическая линковка lib’ы.
Пускай инструментальный класс содержит в себе статическую переменную. А в dll есть функция создания данного класса, причем объект возвращается по значению.
Вот дополненная схема:

image
Вот код на С :

  • lib

    ListAndIter.h

    #pragma once
    #include <list>
    
    using namespace std;
    
    class ListAndIter
    {
       private:
          std::list<int>::iterator iter;
          static std::list<int> &getList();
       public:
          void foo();
          ListAndIter();
          ListAndIter(ListAndIter& rhs);
          ~ListAndIter();      
    };
    

    ListAndIter.cpp

    #include "ListAndIter.h"
    
    ListAndIter::ListAndIter()
    {
        getList().push_front(0);
        iter = getList().begin();
    }
    
    ListAndIter::ListAndIter(ListAndIter& rhs)
    {
       this->iter = rhs.iter;
       rhs.iter = getList().end();
    }
    
    std::list<int> & ListAndIter::getList()
    {
       static std::list<int> MyList;
       return MyList;
    }
    
    ListAndIter::~ListAndIter()
    {
       if (iter != getList().end())
           getList().erase(iter);
    }
    
    void ListAndIter::foo()
    {
    
    }
    
  • dll

    GetStaticObj.h

    #pragma once
    
    #include "ListAndIter.h"
    
    #ifdef _DLL_EXPORTS
       #define _DLL_EXP __declspec(dllexport)
    #else
       #define _DLL_EXP __declspec(dllimport)
    #endif
    
    _DLL_EXP ListAndIter GetStaticObj();
    
    

    GetStaticObj.cpp

    #include "GetStaticObj.h"
    
    ListAndIter GetStaticObj()
    {
       ListAndIter obj;
       obj.foo();
       return obj;
    }
    
  • exe

    Main.cpp

    #include "GetStaticObj.h"
    
    int main()
    {
       ListAndIter obj = GetStaticObj();
       obj.foo();
    }
    

Как видно из кода, есть особая функция foo, которая служит для обхода RVO, Дабы вызывался конструктор копирования. Напомню, что и dll-модуль и exe-модуль собираются самостоятельно друг от друга, следственно они обязаны знать о существовании статической переменной в lib’е и следственно создают их у себя.

Объект класса ListAndIter возвращается через конструктор копирования, следственно при приобретении объекта на стороне exe-модуля, все ссылки на статическую переменную станут не валидными. По шагам это выглядит так:

  1. *.exe: Вызов функции GetStaticObj().
  2. Dll.dll: создание временного объекта класса ListAndIter. В список кладется нуль, итератор iter указывает на него. Причем в это время статическая переменная на стороне exe-модуля пустая, соответственно итератор не валидный.
  3. *.exe: Вызывается конструктор копирования для объекта класса ListAndIter. У временного объекта итератор стал не валидным. У нового объекта итератор указывает на список из DLL.dll, правда сам объект создается на стороне exe-модуля.
  4. Dll.dll: Уничтожается непостоянный объект класса ListAndIter. Так как итератор не валидный никаких действий не происходит.
  5. *.exe: Вызывается деструктор для объекта obj. При попытке сопоставления итератора с getList().end() вылезает виндовая оплошность: «Итераторы не совместимы». То есть итератор от «иного списка».

Испробуем поправить такую обстановку, убрав связанность exe-модуля от статической библиотеки. Тогда всю функциональность статической библиотеки необходимо экспортировать через dll (см. код ниже):

image
Метаморфозы в коде:

  • Сотворил новейший заголовочный файл shared.h. В нем описываем макросы экспорта. Поместил файл в lib’е:

    shared.h

    #pragma once
    
    #ifdef _DLL_EXPORTS
       #define _DLL_EXP __declspec(dllexport)
    #else
       #define _DLL_EXP __declspec(dllimport)
    #endif
    
  • В ListAndIter.h добавил директивы экспорта:

    ListAndIter.h

    #pragma once
    #include <list>
    #include "shared.h"
    
    using namespace std;
    
    class ListAndIter
    {
       private:
          std::list<int>::iterator iter;
          _DLL_EXP static std::list<int> &getList();
       public:
          _DLL_EXP void foo();
          _DLL_EXP ListAndIter();
          _DLL_EXP ListAndIter(ListAndIter& rhs);
          _DLL_EXP ~ListAndIter();
    };
    
  • В dll соответственно убрал объявления макросов экспорта:

    GetStaticObj.h

    #pragma once
    
    #include "ListAndIter.h"
    #include "shared.h"
    
    _DLL_EXP ListAndIter GetStaticObj();
    

Сейчас объект будет создаваться и удаляться только на стороне dll. В exe-модуле статической переменной не будет и такой код отработает удачно.

Сейчас давайте представим, что будет, если класс ListAndIter стал шаблонным:

image

Для всякой полной специализации образца и всех объектов таких классов должна быть своя статическая переменная.
Во-первых, мы обязаны реализацию шаблонного класса разместить в заголовочный файл, т.к. образцы раскрываются на этапе компиляции.
Если статическая переменная является членом класса, то Дабы удачно собрать наш план, мы обязаны очевидно проинициализировать эти переменные во всех используемых модулях. В таком случае мы ОчЕВИДНО создаем две статические переменные, что возвращает нас к 1-ому примеру.
Напротив, если статическая переменная не является членом класса, а создается через статический способ, то в этом случае она также создается, но теснее неявно для нас. Оплошность повторяется опять.

Для разрешения такой обстановки нужно создавать промежуточную lib’у, в которой и размещать эту функциональность. То есть взамен dll делать lib. Тогда вновь останется одна статическая переменная.

Итог: При применении статических переменных в статических библиотеках необходимо следить за тем, Дабы исполняемые модули не линковались статически друг в друга.

Изредка задача не решается облегчением зависимостей. Скажем, класс реализован в статической библиотеке и у него есть некоторый статический счетчик экземпляров. Эта статическая библиотека линкуется в две различные dll, таким образом, в них создается два различных счетчика. В данном случае задача решается путем перевоплощения статической библиотеки в динамическую (dll). Соответственно две другие dll прилинковывают новую dll динамически. Тогда статическая переменная будет только в одной dll (в той, к которой реализован класс со счетчиком).

Каждый код дозволено взять с github.

P.S. Много каждого написал, допустимо не идеально… буду рад советам и примечаниям.

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB