Странное поведение при литье указателей функций в С++

Недавно я столкнулся с поведением в С++ относительно указателей на функции, которые я не могу полностью понять. Я попросил Google помочь, а также некоторые из моих более опытных коллег, но даже они не могли помочь.

Следующий код демонстрирует это мистическое поведение:

class MyClass{
private:
    int i;

public:
    MyClass(): i(0) {}
    MyClass(int i): i(i) {}

    void PrintText() const { std::cout << "some text " << std::endl;}
};

typedef void (*MyFunction) (void*);

void func(MyClass& mc){
    mc.PrintText();
}

int main(){    
    void* v_mc = new MyClass;
    MyFunction f = (MyFunction) func; //It works!
    f(v_mc); //It works correctly!!!

    return 0;
}

Итак, сначала я определяю простой класс, который будет использоваться позже (особенно, его метод-член PrintText). Затем я определяю объект name void (*) (void*) как MyFunction - указатель на функцию с одним параметром void* и не возвращает значение.

После этого я определяю функцию func(), которая принимает ссылку на объект MyClass и вызывает свой метод PrintText.

И наконец, магия происходит в основной функции. Я динамически выделяю память для нового объекта MyClass, который возвращает возвращаемый указатель на void*. Затем я навел указатель на функцию func() на указатель MyFunction - я не ожидал, что это вообще скомпилируется, но это произойдет.

И, наконец, я называю этот новый объект аргументом void*, хотя базовая функция (func()) принимает ссылку на объект MyClass. И все работает правильно!

Я попытался скомпилировать этот код с Visual Studio 2010 (Windows) и XCode 5 (OSX), и он работает одинаково - никаких предупреждений не сообщается. Я предполагаю, что причина этого в том, что ссылки на С++ фактически реализованы как указатели за кулисами, но это не объяснение.

Я надеюсь, что кто-то сможет объяснить это поведение.

Ответы

Ответ 1

Формальное объяснение простое: поведение undefined undefined. Когда вы вызываете функцию с помощью указателя на другой тип функции, это поведение undefined, и программа может юридически что-либо делать (сбой, кажется, работает, закажите пиццу онлайн... anyting идет).

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

  • Ваш компилятор внутренне реализует ссылки в качестве указателей.
  • На вашей платформе все указатели имеют одинаковый размер и двоичное представление.
  • Так как PrintText() вообще не имеет доступа к *this, компилятор может фактически полностью игнорировать значение mc и просто вызвать функцию PrintText() внутри func.

Однако вы должны помнить, что, когда вы в настоящее время испытываете поведение, которое вы описали на своей текущей платформе, версию компилятора и на этом этапе луны, это может измениться в любое время без какой-либо видимой причины (например, изменение окружающего кода, инициирующего различные оптимизации). Помните, что поведение undefined - это просто undefined.


Что касается того, почему вы можете использовать &func to MyFunction, стандарт явно позволяет это (с reinterpret_cast, к которому в этом контексте выполняется трансляция C-стиля). Вы можете законным образом направить указатель на любой другой указатель на тип функции. Тем не менее, в значительной степени единственное, что вы можете с юридической точки зрения сделать, это переместить его или вернуть его к исходному типу. Как я сказал выше, если вы вызываете указатель на функцию неправильного типа, это поведение undefined.

Ответ 2

Я надеюсь, что кто-то сможет объяснить это поведение.

Поведение undefined.

MyFunction f = (MyFunction) func; //It works!

Он "работает", потому что вы используете c-style cast, который имеет такой же эффект, как reinterpret_cast, в этом случае я думаю. Если вы использовали static_cast или просто не использовали вообще, компилятор предупредил о вашей ошибке и не смог. Когда вы вызываете неверно интерпретируемый указатель функции, вы получаете поведение undefined.

Ответ 3

Это только случайно, что он работает. Составители не гарантируют, что это сработает. За кулисами ваш компилятор обрабатывает ссылку как указатель, поэтому ваша альтернативная сигнатура функции работает.

Ответ 4

Извините, мне непонятно, почему вы называете это странным поведением, я не вижу поведения undefined, которое зависит от цикла луны здесь, это способ использования указателей функций в C.

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

void PrintText() const { std::cout << "some text " << this << std::endl;}
                                                      ^^^^
void func(MyClass& mc){
    std::cout << (void *)&mc << std::endl;
                         ^^^
void *v_mc = new MyClass;
std::cout << (void *)v_mc << std::endl;
                     ^^^^