2017-02-22 10 views
1

Учитывая базовый класс B, такие как:C++: Нарушение прав доступа при заливке пустот * на указатель базового класса

class B 
    { 
    public: 
     virtual ~B() = default; 
    public: 
     virtual int f() const = 0; 
    }; 

и ряд производных классов Ai: public B (I = 1, .., N), внедрение f(), я получаю void*, который определенно содержит один из полученных классов Ai из внешней программы - для выполнения метода f().

Можно создать точку входа для каждого возможного производного типа, и он будет работать нормально:

// for each derived class Ai 
void executeF(void* aPtr, int* result) 
{ 
    auto aObjPtr = static_cast<Ai*>(aPtr); 
    *result = aObjPtr->f(); 
} 

Однако, это должно быть возможно достигнуть того же результата с только одной функции, такие как:

void executeF(void* aPtr, int* result) 
{ 
    auto bObjPtr = static_cast<B*>(aPtr); // works 
    *result = bObjPtr->f(); // Access violation 
} 

случай удается выше, но исполнение f() терпит неудачу с «нарушение прав доступа» в MSVC 2013.

есть S что-то не так с вышеуказанной функцией? И если это так, есть ли способ решить задачу с помощью одной функции?

Я прочитал некоторые материалы, в которых утверждается, что нужно наложить void* только на тот класс, который он держит (также предлагается в комментарии ниже). Тем не менее, этот код компилирует и выполняет отлично: http://ideone.com/e0Lr6v

Некоторые больше контекста о том, как все называют:

я не могу предоставить весь код здесь, потому что это слишком долго, но в целом .. Функция executeF, конструкторы для объектов Ai и все в библиотеке, которая определяет и работает на объектах A, B предоставляются в качестве экспортируемых функций, которые работают только с типами void*. Просто FYI, эта библиотека скомпилирована и построена с помощью MSVC 2013.

Другая сторона (оболочка для языка R) скомпилирована и построена с помощью g ++ - она ​​динамически загружает указанную библиотеку, экспортирует необходимую функцию и вызывает ее. Единственное, что доступно на этой стороне, это объекты void*, содержащие объекты Ai - он просто отправляет запросы на создание объектов, называет их методы, освобождает их.

Например (схематично), создать объект типа A1:

// "objects" library 
void createA1(void** aObj) 
{ 
    *a1Obj = new A1(); 
} 

// caller library 
auto const createA1func = (int(__CC *)(void**)) GetProcAddress(getDLL(), "CreateA1"); 
void* a1Obj = NULL; 
createAFunc(a1Obj); 
// ... return the a1Obj to the external environemnt to keep it around 

Затем, a1Obj вокруг, сделать какую-то работу с ним:

// caller library 
auto const executeFfunc = (int(__CC *)(void*, int*)) GetProcAddress(getDLL(), "executeF"); 
int res(0); 
executeFfunc(a1Obj, &res); 

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

+1

Вы можете попробовать выполнить кастинг перед вызовом 'executeF', так что' executeF' всегда получает 'B *' вместо 'Ai *' – immibis

+0

Почему это помечено C? – immibis

+0

Внешняя программа @immibis, которая передает 'void *', не знает о типах 'B',' A' и т. Д. –

ответ

2

Когда Ai происходит от B, указатель на Ai части объекта (обычно) не указывает на тот же адрес памяти в качестве указателя на B части одного и того же объекта (особенно, если B имеет поля данных в нем). Доступ к Ai с помощью указателя B* обычно включает в себя исправления указателей, запросы VMT и т. Д., Которые должны учитываться конкретным используемым компилятором. Вот почему вы не можете просто нарисовать указатель Ai* на void* на B* и ожидать, что все будет работать правильно. Указатель B* не является допустимым указателем B*, это фактически указатель Ai*, который был reinterpretted как B*, и это просто не работает на законных основаниях.

Чтобы вещи оставаться выстроены правильно, вы должны либо:

  • отливать Ai* к void*, а затем void* к Ai*. Это то, чего вы пытаетесь избежать.

  • литой Ai* к B* первым, а затем B* к void*, а затем void* к B* (и затем необязательно B* к Ai* через dynamic_cast, если вам нужно получить доступ невиртуальных членов из Ai).

Таким образом, для того, чтобы это работало так, как вы хотели, выполните следующие действия при построении ваших объектов:

void createA1(void** aObj) 
{ 
    *aObj = static_cast<B*>(new A1()); 
} 

void createA2(void** aObj) 
{ 
    *aObj = static_cast<B*>(new A2()); 
} 

И так далее. Это гарантирует, что все указатели, передаваемые executeF() являются соответствующиеB* указатели, и только тогда можно executeF()безопасно типа отбрасывало получил void* указатель на B* и использовать полиморфизм доступа в зависимости от того производный класс это фактически указывает на:

void executeF(void* aPtr, int* result) 
{ 
    B* bObjPtr = static_cast<B*>(aPtr); 
    *result = bObjPtr->f(); // works 
} 

Обновление: в качестве альтернативы, особенно при работе с несколькими производными классами, каждая из которых несколько базовых классов, которые могут или не могут все быть общими, еще один вариант ш olld просто обернуть объекты Ai в struct, у которого есть дополнительное поле, указывающее тип объекта.Тогда ваши create...() функций могут возвращать void* указателей на эту структуру, а не к Ai объектам непосредственно, а execute...() функций могут сначала привести void* к этой структуре, посмотрите на его типе поле, и поверг указатели объектов соответственно:

enum AType 
{ 
    a1, a2 /*, ... */ 
}; 

class B 
{ 
public: 
    virtual ~B() = default; 
    virtual int f() = 0; 
}; 

class Bx 
{ 
public: 
    virtual ~B() = default; 
    virtual int x() = 0; 
}; 

class By 
{ 
public: 
    virtual ~B() = default; 
    virtual int y() = 0; 
}; 

// ... 

class A1 : public B, public Bx 
{ 
public: 
    int f() override { return 1; } 
    int x() override { return 1; } 
}; 

class A2 : public B, public By 
{ 
public: 
    int f() override { return 2; } 
    int y() override { return 2; } 
}; 

// ... 

struct objDesc 
{ 
    AType type; 
    void *obj; 
}; 

void createA1(void** aObj) 
{ 
    objDesc *desc = new objDesc; 
    desc->type = a1; 
    desc->obj = new A1(); 
    *aObj = desc; 
} 

void createA2(void** aObj) 
{ 
    objDesc *desc = new objDesc; 
    desc->type = a2; 
    desc->obj = new A2(); 
    *aObj = desc; 
} 

// ... 

void destroyObj(void* aObj) 
{ 
    objDesc *desc = static_cast<objDesc*>(aObj); 
    switch (desc->type) 
    { 
     case a1: 
      delete static_cast<A1*>(desc->obj); 
      break; 

     case a2: 
      delete static_cast<A2*>(desc->obj); 
      break; 

     //.. 
    } 

    delete desc; 
} 

//... 

void executeF(void* aPtr, int* result) 
{ 
    objDesc *desc = static_cast<objDesc*>(aPtr); 
    B* bObjPtr = nullptr; 

    switch (desc->type) 
    { 
     case a1: 
      bObjPtr = static_cast<A1*>(desc->obj); 
      break; 

     case a2: 
      bObjPtr = static_cast<A2*>(desc->obj); 
      break; 

     // other classes that implement B ... 
    } 

    if (bObjPtr) 
     *result = bObjPtr->f(); 
} 

void executeX(void* aPtr, int* result) 
{ 
    objDesc *desc = static_cast<objDesc*>(aPtr); 
    Bx* bObjPtr = nullptr; 

    switch (desc->type) 
    { 
     case a1: 
      bObjPtr = static_cast<A1*>(desc->obj); 
      break; 

     // other classes that implement Bx ... 
    } 

    if (bObjPtr) 
     *result = bObjPtr->x(); 
} 

void executeY(void* aPtr, int* result) 
{ 
    objDesc *desc = static_cast<objDesc*>(aPtr); 
    By* bObjPtr = nullptr; 

    switch (desc->type) 
    { 
     case a2: 
      bObjPtr = static_cast<A2*>(desc->obj); 
      break; 

     // other classes that implement By ... 
    } 

    if (bObjPtr) 
     *result = bObjPtr->y(); 
} 

// ... 

Это не идеальный или гибкий, но он будет работать в пределах ограничений, которые у вас есть на другой стороне.

В противном случае, вы можете заменить struct с новым базовым классом, который всех других классов MUST проистекает из, то вы можете использовать dynamic_cast по мере необходимости:

class Base 
{ 
public: 
    virtual ~Base() = default; 
}; 

class Bf 
{ 
public: 
    virtual ~Bf() = default; 
    virtual int f() = 0; 
}; 

class Bx 
{ 
public: 
    virtual ~Bx() = default; 
    virtual int x() = 0; 
}; 

class By 
{ 
public: 
    virtual ~By() = default; 
    virtual int y() = 0; 
}; 

class Bz 
{ 
public: 
    virtual ~Bz() = default; 
    virtual int z() = 0; 
}; 

class A1 : public Base, public Bf, public Bx 
{ 
public: 
    int f() override { return 1; } 
    int x() override { return 1; } 
}; 

class A2 : public Base, public Bf, public By 
{ 
public: 
    int f() override { return 2; } 
    int y() override { return 2; } 
}; 

class A3 : public Base, public Bz 
{ 
public: 
    int z() override { return 3; } 
}; 

// ... 

void createA1(void** aObj) 
{ 
    *aObj = static_cast<Base*>(new A1()); 
} 

void createA2(void** aObj) 
{ 
    *aObj = static_cast<Base*>(new A2()); 
} 

void createA3(void** aObj) 
{ 
    *aObj = static_cast<Base*>(new A3()); 
} 

// ... 

void destroyObj(void* aObj) 
{ 
    delete static_cast<Base*>(aObj); 
} 

//... 

void executeF(void* aPtr, int* result) 
{ 
    Base *base = static_cast<Base*>(aPtr); 
    B* bObjPtr = dynamic_cast<B*>(base); 
    if (bObjPtr) 
     *result = bObjPtr->f(); 
} 

void executeX(void* aPtr, int* result) 
{ 
    Base *base = static_cast<Base*>(aPtr); 
    Bx* bObjPtr = dynamic_cast<Bx*>(base); 
    if (bObjPtr) 
     *result = bObjPtr->x(); 
} 

void executeY(void* aPtr, int* result) 
{ 
    Base *base = static_cast<Base*>(aPtr); 
    By* bObjPtr = dynamic_cast<By*>(base); 
    if (bObjPtr) 
     *result = bObjPtr->y(); 
} 

void executeZ(void* aPtr, int* result) 
{ 
    Base *base = static_cast<Base*>(aPtr); 
    By* bObjPtr = dynamic_cast<Bz*>(base); 
    if (bObjPtr) 
     *result = bObjPtr->z(); 
} 

//... 
+0

Возможно, это было бы самое простое исправление, но почему C-стиль отличает? –

+0

Я изменил его на C++. –

+0

Спасибо. Ну, это сработает, да, но это сократит все функциональные возможности Ai. Также есть куча базовых классов, отличных от 'B' ... В любом случае, я понимаю, что мне нужно будет сделать это для каждого типа Ai –

-2

Функция executeF, конструкторы для объектов Ai ...

Это, скорее всего, проблема, вы не должны вызывать виртуальные машины в конструкторе. Он работает для Ai, потому что Ai не вызывает виртуальный метод из таблицы vptr. B однако не имеет такой таблицы, если она строится. См. this other Ответ.

1

Поведения вы наблюдаете просто означает, что преобразование с Ai * в B * не является чисто концептуальным, но на самом деле требует физического изменения значения указателя. В типичных реализациях этот Usally происходит, когда:

  1. Класса B не полиморфный и содержит подобъекты ненулевого размера, в то время как класс Ai является полиморфным. (Не в вашем случае)
  2. Класс Ai имеет несколько оснований и B является лишь одним из них.

Я предполагаю, что вы имеете дело со вторым случаем в своем коде.

В таких случаях может «работать», если вы убедитесь, что база B является очень первая база Ai (но, опять же, это в значительной степени зависит от реализации и, очевидно, ненадежный).

+0

Да, там (возможно) более одного базового класса –

0

Я придумал следующее рабочее решение, которое позволяет избежать наличия функций 2 * N с обеих сторон, где N - число производных классов A. Вместо этого он включает в себя 2 функции по одному с каждой стороны. В библиотеке объектов есть переключатель с N случаями, который отбрасывает void* в соответствующий класс. Обратите внимание, что сторона g ++ должна знать только о enum и до сих пор ничего не знает о типах.

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

http://ideone.com/enNl3f

enum AType 
{ 
    a1 = 1, a2 
}; 

class B 
{ 
public: 
    virtual ~B() = default; 
public: 
    virtual int f() const = 0; 
}; 

class A1: public B 
{ 
    virtual int f() const override 
    { 
     return 1; 
    } 
}; 
class A2: public B 
{ 
    virtual int f() const override 
    { 
     return 2; 
    } 
}; 

void executeF(void* aPtr, AType aType, int* result) 
{ 
    B* bPtr = nullptr; 
    switch(aType) 
    { 
     case a1: 
      bPtr = static_cast<A1*>(aPtr); 
      break; 
     case a2: 
      bPtr = static_cast<A2*>(aPtr); 
      break; 
     default: 
      break; 
    } 

    if(bPtr) 
     *result = bPtr->f(); 
} 
+0

Пока это будет работать, оно не идеально. По крайней мере, я бы избавился от параметра 'aType' на' executeF() 'и обернул объекты' Ai' в 'struct', который имеет поле' AType', чтобы указать тип объекта. Затем ваши функции 'create ...()' возвращают указатели на эту структуру вместо указателей на сами объекты Ai. Это позволит функциям 'execute ...()' смотреть на поле структуры AType' и соответственно выполнять их. Я обновлю свой ответ на примере. –

+0

Я могу видеть, как дополнительная родительская структура может заменить 'enum', но это потребует большой перезаписи существующего кода: 1. наследование каждого класса A из этой новой базовой структуры 2. изменение каждого ctor для изменения созданного Объект Пока с 'enum' существующий код остается нетронутым. Какая польза от этого над 'enum' –

+0

. Я предлагаю не новый базовый тип, из которого классы должны непосредственно вытекать. Это просто оболочка, которая позволяет функциям 'create ...()' передавать этот дополнительный 'enum' в функции' execute ...() 'без изменения существующих классов вообще. См. Обновление моего ответа. –

0

Представьте себе, если есть два типа, Base1 и Base2. Скажем, Base1 содержит только один элемент, целое число. И скажем, содержит только одного члена, поплавок.

Можно было бы ожидать, что Base1* будет указывать на целое число, а Base2* укажет на поплавок.

Теперь рассмотрим:

class Derived : public Base1, public Base2 
{ 
    ... 

Теперь, если мы бросим Derived* к void*, мы можем получить указатель на целое число в Base1 или мы могли бы получить указатель на поплавок в Base2. Но мы не можем получить оба.

Таким образом, ожидание того, что вы можете преобразовать Derived* к void*, а затем бросил его обратно в указатель на базовый класс и получить что-то толковое просит невозможного. Преобразование указателя в базовый класс в указатель на класс, полученный из , должно быть иногда меняет значение этого указателя.

Смежные вопросы