Що стосується стандарту C, якщо ви приводите покажчик функції до покажчика функції іншого типу, а потім викликаєте це, це невизначена поведінка . Див. Додаток J.2 (інформативний):
Поведінка невизначена за таких обставин:
- Вказівник використовується для виклику функції, тип якої не сумісний із вказаним на тип (6.3.2.3).
Розділ 6.3.2.3, пункт 8 говорить:
Покажчик на функцію одного типу може бути перетворений на покажчик на функцію іншого типу і назад; результат повинен порівнюватися рівним вихідному покажчику. Якщо перетворений вказівник використовується для виклику функції, тип якої не сумісний із вказівним типом, поведінка не визначена.
Отже, іншими словами, ви можете передати вказівник на функцію на інший тип вказівника на функцію, повернути його назад і викликати, і все буде працювати.
Визначення сумісного є дещо складним. Це можна знайти в розділі 6.7.5.3, пункт 15:
Щоб два типи функцій були сумісними, обидва повинні вказати сумісні типи повернення 127 .
Більше того, списки типів параметрів, якщо вони присутні, повинні узгоджувати кількість параметрів та використання термінатора багатоточия; відповідні параметри повинні мати сумісні типи. Якщо один тип має список типів параметрів, а інший тип заданий декларатором функції, який не є частиною визначення функції і який містить порожній список ідентифікаторів, у списку параметрів не повинно бути терміналу крапки, і тип кожного параметра повинен бути сумісним із типом, який є результатом застосування рекламних акцій за замовчуванням. Якщо один тип має список типів параметрів, а інший тип визначається визначенням функції, що містить (можливо, порожній) список ідентифікаторів, обидва повинні узгодити кількість параметрів, і тип кожного параметра-прототипу повинен бути сумісним із типом, який є результатом застосування стандартних підвищення аргументів до типу відповідного ідентифікатора. (При визначенні сумісності типів та складеного типу кожен параметр, оголошений типом функції або масиву, приймається як відрегульований тип, а кожен параметр, оголошений кваліфікованим типом, приймається як некваліфікована версія свого оголошеного типу.)
127) Якщо обидва типи функцій є `` старим стилем '', типи параметрів не порівнюються.
Правила визначення сумісності двох типів описані в розділі 6.2.7, і я не буду цитувати їх тут, оскільки вони досить довгі, але ви можете прочитати їх у проекті стандарту C99 (PDF) .
Відповідне правило тут є в розділі 6.7.5.1, пункт 2:
Щоб два типи покажчиків були сумісними, обидва повинні бути однаково кваліфікованими, і обидва повинні бути вказівниками на сумісні типи.
Отже, оскільки a void*
не є сумісним з a struct my_struct*
, покажчик функції типу void (*)(void*)
не сумісний з покажчиком функції типу void (*)(struct my_struct*)
, тож це лиття покажчиків функцій є технічно невизначеною поведінкою.
На практиці, однак, у деяких випадках можна безпечно впоратися з покажчиками функцій кастингу. У домовленості виклику x86 аргументи натискаються на стек, і всі вказівники мають однаковий розмір (4 байти в x86 або 8 байтів у x86_64). Виклик покажчика функції зводиться до висунення аргументів у стек та непрямого переходу до цілі покажчика функції, і поняття типів на рівні машинного коду, очевидно, відсутнє.
Те, що ви точно не можете зробити:
- Трансляція між покажчиками функцій різних конвенцій викликів. Ви зіпсуєте стек і в кращому випадку впадете, в гіршому - безшумно досягнете успіху з величезною роззявленою діркою в безпеці. У програмуванні Windows ви часто передаєте вказівники на функції. Win32 очікує , що всі функції зворотного виклику використовувати
stdcall
угоду про виклики (які макроси CALLBACK
, PASCAL
і WINAPI
все розширюватися). Якщо ви передасте покажчик функції, який використовує стандартну умову виклику C ( cdecl
), це призведе до несправності.
- У C ++, передавання між покажчиками на функції члена класу та звичайними вказівниками на функції. Це часто піднімає початківців на C ++. Функції-члени класу мають прихований
this
параметр, і якщо ви додасте функцію-член до звичайної функції, немає this
об’єкта для використання, і знову ж таки, це призведе до великої бідності.
Ще одна погана ідея, яка іноді може спрацювати, але також невизначена поведінка:
- Трансляція між покажчиками функцій та звичайними вказівниками (наприклад, приведення а
void (*)(void)
до а void*
). Покажчики функцій не обов'язково мають такий самий розмір, як звичайні вказівники, оскільки на деяких архітектурах вони можуть містити додаткову контекстну інформацію. Можливо, це буде нормально працювати на x86, але пам’ятайте, що це невизначена поведінка.