Что касается стандарта C, то если вы приведете указатель функции к указателю функции другого типа, а затем вызовете его, это будет неопределенное поведение . См. Приложение J.2 (справочное):
Поведение не определено в следующих случаях:
- Указатель используется для вызова функции, тип которой несовместим с указанным типом (6.3.2.3).
Раздел 6.3.2.3, параграф 8 гласит:
Указатель на функцию одного типа может быть преобразован в указатель на функцию другого типа и обратно; результат должен быть равен исходному указателю. Если преобразованный указатель используется для вызова функции, тип которой несовместим с указанным типом, поведение не определено.
Другими словами, вы можете привести указатель на функцию к другому типу указателя функции, вернуть его обратно и вызвать его, и все заработает.
Определение совместимости довольно сложно. Его можно найти в разделе 6.7.5.3, параграф 15:
Чтобы два типа функций были совместимыми, оба должны указывать совместимые возвращаемые типы 127 .
Более того, списки типов параметров, если присутствуют оба, должны согласовываться по количеству параметров и использованию ограничителя многоточия; соответствующие параметры должны иметь совместимые типы. Если один тип имеет список типов параметров, а другой тип задан декларатором функции, который не является частью определения функции и содержит пустой список идентификаторов, список параметров не должен иметь признака конца многоточия, а тип каждого параметра должен быть совместимым с типом, который является результатом применения аргументов по умолчанию Promotions. Если один тип имеет список типов параметров, а другой тип определяется определением функции, которое содержит (возможно, пустой) список идентификаторов, оба должны согласовывать количество параметров, и тип каждого параметра прототипа должен быть совместим с типом, полученным в результате применения промо-акций аргументов по умолчанию к типу соответствующего идентификатора. (При определении совместимости типов и составного типа каждый параметр, объявленный с функцией или типом массива, принимается как имеющий скорректированный тип, и каждый параметр, объявленный с квалифицированным типом, принимается как имеющий неквалифицированную версию своего объявленного типа.)
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
объекта для использования, и, опять же, это приведет к большому ущербу.
Еще одна плохая идея, которая иногда может работать, но также имеет неопределенное поведение:
- Преобразование между указателями на функции и обычными указателями (например, преобразование a
void (*)(void)
в a void*
). Указатели функций не обязательно того же размера, что и обычные указатели, поскольку на некоторых архитектурах они могут содержать дополнительную контекстную информацию. Это, вероятно, будет работать на x86, но помните, что это неопределенное поведение.