Войти
ПрограммированиеФорумОбщее

Callback какие "баги" могут полезть?

Страницы: 1 2 Следующая »
#0
12:37, 3 фев 2014

Есть пара классов.

class IObject
{ 
public :
           IObject ( void ) {};
virtual ~IObject ( void ) {};
};

class TestCall :
    public IObject
    
{

private :
    int     m_i;
    float   m_f;

public :

             TestCall ( void ) { m_i = 999; m_f = 1.7777f; }
    virtual ~TestCall ( void ) {}

public :

    int __stdcall test_call_method ( int i, float f )
    {
        this->m_i = i;
        this->m_f = f;

        return m_i;
    }

};

///
int main ( int, char *[] )
{
    TestCall          test_call;
    IObject    *test_call_obj = &test_call;
    void             *v_func        = NULL;

    union
    {
        int ( __stdcall TestCall::*cFunc ) ( int, float );
        void *voidFunc;
    }; cFunc = ( &TestCall::test_call_method );
    v_func = voidFunc;


    for ( int i = 0; i < 10000; i++ )
    {
        union 
        { 
            int ( __stdcall IObject::*Method )( int, float );  /// Смущает это преобразование. Всегда ли это будет работать?
            void  *void_func; 
        }; void_func = v_func;
        int ret = ( ( test_call_obj )->* ( Method ) ) ( i, 7.999f );
        std::cout << " call method \"test_call_method\" ret = "  << ret << std::endl;
    }

    return 0;
}

Смущает это преобразование  int ( CALLSPEC Core::IObject::*Method )( int, float ); . Всегда ли это будет работать?

#1
13:38, 3 фев 2014

Yotce
> Смущает это преобразование int ( CALLSPEC Core::IObject::*Method )( int, float ); . Всегда ли это будет работать?
По закону это вообще не работает.
Размер указателя на метод класса в общем случае не равен размеру указателя на void или функцию.
Нельзя приводить или алиасить в union.

#2
14:14, 3 фев 2014

RPGman
> По закону это вообще не работает.
По какому закону это не работает, если это рабочий пример?

#3
14:40, 3 фев 2014

Ух у тебя и "рабочий пример".

Для начала:

main.cpp:5:10: Constructor cannot be declared 'virtual'

И еще предупреждение:
Calling convention '__stdcall' ignored for this target это на

int __stdcall test_call_method ( int i, float f )

PS: это в Xcode

#4
14:42, 3 фев 2014

Yotce
> По какому закону это не работает
Закон тут один - ISO/IEC 14882.

> если это рабочий пример?
Это НЕ рабочий пример. Если он у тебя не упал, то только по какому-то счастливому стечению обстоятельств.
Попробуй для проверки занулить юнион мемсетом перед тем, как делать "void_func = v_func" в теле цикла.
Ну и принтфни sizeof'ы для cFunc и voidFunc, чисто поржать.
Примерно: http://ideone.com/QdKjqD

#5
15:15, 3 фев 2014

Yotce
Тут полностью разжёвано: http://www.parashift.com/c++-faq/pointers-to-members.html

#6
15:37, 3 фев 2014

Yotce
Открой для себя Listeners

#7
17:14, 3 фев 2014

А что ты вообще хотел добиться? Передача функции член класса как указатель? Тогда тебе  в направлении std::function если С++11, иначе поищи "С++ делегаты".
Ну а альтернативный вариант уже сказали Listeners.

#8
18:22, 3 фев 2014

Sergio
> Для начала:
>
> main.cpp:5:10: Constructor cannot be declared 'virtual'

Опечатка вышла должно быть

///
#include <map>
#include <string>
#include <fstream>
#include <iostream>


class IObject
{
public :

    IObject ( void ) {}
    virtual ~IObject ( void ) {}

};

///
class TestCall :
    public IObject

{
private :
    int     m_i;
    float   m_f;

public :

    TestCall ( void ) { m_i = 999; m_f = 1.7777f; }
    virtual ~TestCall ( void ) {}

public :

    int __stdcall test_call_method ( int i, float f )
    {
        this->m_i = i;
        this->m_f = f;

        return m_i;
    }

    int get_i ( void )
    {
        return m_i ;
    }

};

///
int main ( int argc, char *argv[] )
{

    TestCall          test_call;
    IObject          *test_call_obj = &test_call;
    void             *v_func        = 0;

    union
    {
        int ( __stdcall TestCall::*cFunc ) ( int, float );
        void *voidFunc;
    }; 
    memset( &cFunc, 0, sizeof ( cFunc ) );
    cFunc = ( &TestCall::test_call_method );
    v_func = voidFunc;


    for ( int i = 0; i < 10000; i++ )
    {
        union 
        { 
            int ( __stdcall IObject::*Method )( int, float ); 
            void  *void_func; 
        }; 
        memset( &Method, 0, sizeof ( Method ) );
        void_func = v_func;
        int ret = ( ( test_call_obj )->* ( Method ) ) ( i, 7.999f );
        std::cout << " call method \"test_call_method\" ret = "  << ret << " get_i = " << test_call.get_i() << std::endl;
    }

    return 0;
}
 

RPGman
> Тут полностью разжёвано:
> http://www.parashift.com/c++-faq/pointers-to-members.html

int f(char x, float y);
int g(char x, float y);

typedef int(*FunctPtr)(char,float);

int callit(FunctPtr p, char x, float y)
{
  return p(x, y);
}

int main()
{
  FunctPtr p = f;
  void* p2 = (void*)p;              // ← illegal!!
  callit(p, 'x', 3.14f);            // okay
  callit(FunctPtr(p2), 'x', 3.14f); // might fail!!
  ...
}
а так действительно делать нельзя потому, что здесь указатель на функцию приводится к void *

в этом случаи берётся адрес указателя
union
        {
            int ( __stdcall IObject::*Method )( int, float );
            void  *void_func;
        };
        memset( &Method, 0, sizeof ( Method ) );
        void_func = v_func;

это равносильно записи вида

{
        char *test_str = "TEST\0";
        union{
            char  **buf;
            void  *addr;
        }; buf = &test_str;

        void *p = addr;

        union{
            char **bf;
            void *adr;
        }; adr = p;
        std::cout << *bf << std::endl;
    }

#9
18:56, 3 фев 2014

Можно ещё с jitasm или asmjit повеселиться.
Я юзаю jitasm.

#10
19:37, 3 фев 2014

Yotce
> typedef int(*FunctPtr)(char,float);
> int callit(FunctPtr p, char x, float y)
Это не в тему, это указатели на функции, а не на методы класса.

> в этом случаи берётся адрес указателя
...
> это равносильно записи вида
Твой синтетический случай слишком тривиальный, поэтому некорректное поведение замаскировано стечением обстоятельств.

Получай демонстрацию бага в более реалистичном случае:
http://ideone.com/wUDisI

+ Показать

Этого достаточно, чтобы больше не кастовать указатели на методы в указатели на void?

Если мой пример тебя не удовлетворяет, замени 0 в своём memset на i (нам же "всё-равно", что там было в указателе на метод, да?),
получишь не то что левые данные, а вообще крашнешься.

#11
19:38, 3 фев 2014

Методы stdcall не бывают же.

#12
20:24, 3 фев 2014

RPGman
> Получай демонстрацию бага в более реалистичном случае:

Баги я тоже писать могу, И править их...

struct Base1
{
    int val;
    Base1(): val(1) {}
    virtual ~Base1() {} /// добавим virtual

    virtual int method1() /// на всякий случай virtual
    {
        // обязано всегда выводить 1
        printf("Base1 method, %d\n", val);
        return val;
    }
};

struct Base2
{
    int val;
    Base2(): val(2) {}
    virtual ~Base2() {} /// добавим virtual

    virtual int method2() /// на всякий случай virtual
    {
        // обязано всегда выводить 2
        printf("Base2 method, %d\n", val);
        return val;
    }
};

struct Foo: public Base1, Base2
{
    int val;
    Foo()
        : val(3)
    { }

    virtual ~Foo () {}
};


int main ( int argc, char *argv[] )
{
    // класс
    Foo a;
    Base1 *base_1 = &a;
    Base2 *base_2 = &a;

  
    {
        union
        {
            int (Base2::*cFunc ) (); /// Вить у нас множественное наследование!!!
            void *voidFunc;
        };

        // берём метод 2
        cFunc = &Base2::method2;
        // читаем "как бы" указатель
        void* void_func = voidFunc;

        // чистим юнион
        memset(&cFunc, 0, sizeof cFunc);
        voidFunc = void_func;

        // зовем a.method2()
        // должно вывести "base2 method, 2"

        (base_2->*cFunc)(); // по факту выводит "base2 method, 2"
    }

    {
        // делаем по твоему
        union
        {
            int (Base1::*cFunc ) (); /// Вить у нас множественное наследование!!!
            void *voidFunc;
        };

        // берём метод 1
        cFunc = &Base1::method1;
        // читаем "как бы" указатель
        void* void_func = voidFunc;

        // чистим юнион
        memset(&cFunc, 0, sizeof cFunc);
        voidFunc = void_func;

        // зовем a.method2()
        // должно вывести "base1 method, 1"

        (base_1->*cFunc)(); // по факту выводит "base1 method, 1"
    }


    return 0;
}
#13
20:47, 3 фев 2014

Yotce
> Баги я тоже писать могу,
Ты и написал

>И править их...
Нет. Ты берёшь и подстраиваешь ситуацию под устраивающий тебя вариант UB.

> int (Base2::*cFunc ) (); /// Вить у нас множественное наследование!!!
Ну и что, что множественное?
Foo наследует эту базу, а значит этот метод можно звать для объекта.
Пример на ideone показывает, что это работает.

>/// на всякий случай virtual
Не надо мне виртульных методов, особенно "на всякий случай". Зачем платить за то, что я не использую?

> Base2 *base_2 = &a;
И не надо читить, вызывая методы не на самом объекте, а на кастах к конкретной базе.
Это всего-лишь маскировка некорректного поведения, рассчитанная на определённую реализацию указателя на метод, и работающая только при определённых условиях.
Ты в юнионе потерял инфу о классе, потом вручную её "восстановил", зная заранее у какой базы был взят метод.
Представь, что у Foo есть метод, принимающий на вход указатель на один из собственных методов. Типа void Foo::dispatch(int (Foo::*cb)()) { (this->*cb)(); }
Как тут будешь химичить?

> memset(&cFunc, 0, sizeof cFunc);
Я же уже тебе предложил для теста вписать там не ноль. Почему игнорируешь неудобные вопросы?

#14
1:10, 4 фев 2014

RPGman
> Представь, что у Foo есть метод, принимающий на вход указатель на один из
> собственных методов. Типа void Foo::dispatch(int (Foo::*cb)()) { (this->*cb)();
> }
> Как тут будешь химичить?

Что тут химичить? берем и пишем

+ Показать


RPGman
> > memset(&cFunc, 0, sizeof cFunc);
> Я же уже тебе предложил для теста вписать там не ноль. Почему игнорируешь
> неудобные вопросы?

 /// По поводу memcpy
    {
        char *test_str = "TEST\0";
        union{
            char  **buf;
            void  *addr;
        }; buf = &test_str;

        memset ( buf, 0, sizeof( buf ) );

        void *p = addr;

        union{
            char **bf;
            void *adr;
        }; adr = p;
        
        printf ( " This = NULL %p \n", *bf );
        if ( *bf != NULL )
        {
            printf ( " This != NULL %s \n", *bf );
        }
        /// Можно memset ( buf, 10, sizeof( buf ) ); тогда поменяем адрес test_str = 0x0a0a0a0a0a
    }

Не совсем понятно почему оно не падает когда memset вызывается

Страницы: 1 2 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.