Почему мой новый оператор не называется - PullRequest
8 голосов
/ 28 июня 2009

Я хотел видеть, что динамически загружаемая библиотека (загруженная с помощью dlopen и т. Д.) Действительно использует свои новые операторы удаления, а не те, которые определены в вызывающей программе. Поэтому я написал следующую библиотеку .cpp

#include <exception>
#include <new>
#include <cstdlib>
#include <cstdio>
#include "base.hpp"
void* operator new(size_t size) {
    std::printf("New of library called\n");
    void *p=std::malloc(size); 
    if (p == 0) // did malloc succeed?
        throw std::bad_alloc(); // ANSI/ISO compliant behavior
    return p;
}
void operator delete(void* p) {
    std::printf("Delete of library called\n");
    std::free(p);
}
class Derived : public Base {
public:
    Derived() : Base(10) { }
};
extern "C" {
    Base* create() {
        return new Derived;
    }
    void destroy(Base* p) {
        delete p;
    }
}

и скомпилировал его с

g++ -g -Wall -fPIC -shared library.cpp -o library.so

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

g++ -g -Wall -fPIC -shared -Wl,-Bsymbolic library.cpp -o library.so

Класс Base содержит только значение типа int и функцию get_value () для получения этого значения. После этого я написал client.cpp вот так

#include <exception>
#include <new>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <dlfcn.h>
#include "base.hpp"
void* operator new(size_t size) {
    std::printf("New of client called\n");
    void *p=std::malloc(size); 
    if (p == 0) // did malloc succeed?
        throw std::bad_alloc(); // ANSI/ISO compliant behavior
    return p;
}
void operator delete(void* p) {
    std::printf("Delete of client called\n");
    std::free(p);
}
typedef Base* create_module_t();
typedef void destroy_module_t(Base *);

int main() {
    void* handle = dlopen("./library.so", 
        RTLD_LAZY);
    if (handle == NULL) {
        std::cout << dlerror() << std::endl;
        return 1;
    }
    create_module_t* create_module = NULL;
    void* func = dlsym(handle, "create");
    if (func == NULL) {
        std::cout << dlerror() << std::endl;
        return 1;
    } else create_module = (create_module_t *)func;
    destroy_module_t* destroy_module = NULL;
    func = dlsym(handle, "destroy");
    if (func == NULL) {
        std::cout << dlerror() << std::endl;
        return 1;
    } else destroy_module = (destroy_module_t *)func;
    Base* a = create_module();
    std::cout << "Value: " << a->get_value() << std::endl;
    destroy_module(a);
    return 0;
}

и скомпилировал его с

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

Выполнение клиента Я получаю только «Новый клиент называется» и «Удалить клиент называется». Даже если я использую переключатель компилятора -Bsymbolic для библиотеки, как предложил Employed Russian.

Теперь: что пошло не так? Я думал, что разделяемые библиотеки используют свои собственные новые / удаления, и поэтому вы должны предоставить рядом с фабрикой создать деструктор уничтожить в коде библиотеки.

Дополнительный вопрос: зачем мне функция destroy (Base * p)? Если эта функция вызывает только оператор удаления клиента, я мог бы сделать это сам, то есть «удалить a» вместо destroy_module (a) в следующей за последней строкой.

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

Дополнение: я имею в виду только платформу Linux.

Редактировать: Важные части находятся в комментариях к Ответу занятого русского. Итак, я даю основную подсказку в двух словах: если кто-то так называет gcc

g++ -Wall -g -fPIC -shared library.cpp -o library.so -Wl,-Bsymbolic

библиотека будет использовать собственные операторы new / delete. В противном случае результаты

g++ -Wall -g -fPIC -shared library.cpp -o library.so

в библиотеке, использующей операторы new / delete вызывающей программы. Спасибо занятым русским!

Ответы [ 4 ]

11 голосов
/ 29 июня 2009

Проблема заключается в том, что на большинстве UNIX платформ (в отличие от Win32 и AIX) все ссылки на символы по умолчанию привязываются к определению first символа, видимого для загрузчика времени выполнения.

Если вы определите 'operator new' в основном a.out, все будет привязано к этому определению (как показывает пример Нила Баттеруорта), потому что a.out - самый первый поиск загрузчика во время выполнения изображения.

Если вы определите его в библиотеке, которая загружается после libC.so (или libstdc++.so в случае, если вы используете GCC), то ваше определение никогда не будет использовано. Поскольку вы dlopen() загружаете свою библиотеку после запуска программы, к этому моменту libC уже загружен, и ваша библиотека является самой последней, которую будет искать загрузчик времени выполнения; так что ты проиграл.

На ELF платформах вы можете изменить поведение по умолчанию, используя -Bsymbolic. С man ld в Linux:

 -Bsymbolic
   When creating a shared library, bind references to global symbols
   to the definition within the shared library, if any. Normally, it
   is possible for a program linked against a shared library to override
   the  definition within the shared library. This option is only meaningful
   on ELF platforms which support shared libraries.

Обратите внимание, что -Bsymbolic - это флаг компоновщика, а не флаг компилятора. При использовании g++ вы должны передать флаг компоновщику следующим образом:

  g++ -fPIC -shared library.cpp -o library.so -Wl,-Bsymbolic
1 голос
/ 07 октября 2009

Загляните в RTLD_DEEPBIND

1 голос
/ 28 июня 2009

Следующий код работает как положено. Ожидаете ли вы, что код вашей динамической библиотеки будет использовать предоставленную вами новую / удаленную версию? Я думаю, вы будете разочарованы.

#include <memory>
#include <cstdio>
#include <cstdlib>
using namespace std;;

void* operator new(size_t size) {
        std::printf("New...\n");
        void *p=std::malloc(size); 
        if (p == 0) // did malloc succeed?
                throw std::bad_alloc(); // ANSI/ISO compliant behavior
        return p;
}

void operator delete(void* p) {
        std::printf("Delete...\n");
        std::free(p);
}

int main() {
    int * p = new int(42);
    delete p;
}
0 голосов
/ 28 июня 2009

Мне кажется, проблема в том, что перегрузка операторов в C ++ - это функция времени компиляции, а не ссылки. DLL была скомпилирована без знания перегруженной функции new (), поэтому она не будет работать правильно.

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

...