Что касается стандарта C, если вы приведете указатель функции к указателю на функцию другого типа, а затем вызовете его, это будет неопределенное поведение . См. Приложение J.2 (информативное):
Поведение не определено в следующих обстоятельствах:
- Указатель используется для вызова функции, тип которой не совместим с указанным
тип (6.3.2.3).
Раздел 6.3.2.3, пункт 8 гласит:
Указатель на функцию одного типа может быть преобразован в указатель на функцию другого
напечатайте и вернитесь снова; результат должен сравниваться равным исходному указателю. Если преобразованный
указатель используется для вызова функции, тип которой не совместим с указанным типом,
поведение не определено.
Другими словами, вы можете привести указатель функции к другому типу указателя на функцию, вернуть его снова и вызвать, и все будет работать.
Определение совместимого несколько сложно. Его можно найти в разделе 6.7.5.3, параграф 15:
Для совместимости двух типов функций оба должны указывать совместимые типы возврата 127 .
Кроме того, списки типов параметров, если присутствуют оба, должны совпадать по числу
параметры и при использовании терминатора многоточия; соответствующие параметры должны иметь
совместимые типы. Если один тип имеет список типов параметров, а другой тип указан
объявление функции, которое не является частью определения функции и которое содержит пустое
список идентификаторов, список параметров не должен иметь терминатора с многоточием и тип каждого
Параметр должен быть совместим с типом, который является результатом применения
продвижение аргумента по умолчанию. Если один тип имеет список типов параметров, а другой тип
определяется определением функции, которое содержит (возможно, пустой) список идентификаторов, оба должны
согласовать количество параметров, и тип каждого параметра прототипа должен быть
совместим с типом, который получается в результате применения аргумента по умолчанию
продвижение по типу соответствующего идентификатора. (При определении типа
совместимость и составного типа, каждый параметр объявлен с функцией или массивом
тип считается имеющим настроенный тип, а каждый параметр объявляется с квалифицированным типом
принимается за неквалифицированную версию объявленного типа.)
127) Если оба типа функций имеют «старый стиль», типы параметров не сравниваются.
Правила определения совместимости двух типов описаны в разделе 6.2.7, и я не буду их здесь цитировать, так как они довольно длинные, но вы можете прочитать их в черновике стандарта C99 (PDF) .
Соответствующее правило здесь в параграфе 2 раздела 6.7.5.1:
Для совместимости двух типов указателей оба должны иметь одинаковую квалификацию, и оба должны быть указателями на совместимые типы.
Следовательно, поскольку void*
не совместимо с struct my_struct*
, указатель функции типа void (*)(void*)
не совместим с указателем функции типа void (*)(struct my_struct*)
, поэтому это приведение указатели на функции - это технически неопределенное поведение.
На практике, однако, в некоторых случаях вы можете спокойно пользоваться указателями функций приведения. В соглашении о вызовах x86 аргументы помещаются в стек, и все указатели имеют одинаковый размер (4 байта в x86 или 8 байтов в x86_64). Вызов указателя функции сводится к передаче аргументов в стек и выполнению косвенного перехода к цели указателя функции, и, очевидно, нет понятия типов на уровне машинного кода.
Вещи, которые вы определенно не можете делать:
- Саst между указателями на функции различных соглашений о вызовах. Вы испортите стек и, в лучшем случае, потерпите крах, в худшем случае преуспеете в молчании с огромной дырой в безопасности. В программировании Windows вы часто передаете функциональные указатели. Win32 ожидает, что все функции обратного вызова будут использовать
stdcall
соглашение о вызовах (к которому относятся макросы CALLBACK
, PASCAL
и WINAPI
). Если вы передадите указатель на функцию, которая использует стандартное соглашение о вызовах C (cdecl
), это приведет к плохому результату.
- В C ++ приведение между указателями на функции-члены класса и обычными указателями на функции. Это часто сбивает с толку новичков в C ++. Функции-члены класса имеют скрытый параметр
this
, и если вы преобразуете функцию-член в обычную функцию, у вас не будет this
объекта для использования, и, опять же, будет много плохого.
Еще одна плохая идея, которая иногда может сработать, но также имеет неопределенное поведение:
- Приведение между указателями на функции и обычными указателями (например, приведение
void (*)(void)
к void*
). Указатели на функции не обязательно имеют тот же размер, что и обычные указатели, поскольку на некоторых архитектурах они могут содержать дополнительную контекстную информацию. Это, вероятно, будет хорошо работать на x86, но помните, что это неопределенное поведение.