Странное поведение при литье указателей функций в С++
Недавно я столкнулся с поведением в С++ относительно указателей на функции, которые я не могу полностью понять. Я попросил 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;
^^^^