По спецификации C приведение указателя на функцию приводит к неопределенному поведению. Фактически, на некоторое время предварительные версии GCC 4.3 возвращали NULL всякий раз, когда вы приводили указатель на функцию, что полностью соответствовало спецификации, но они отменяли это изменение перед выпуском, потому что оно ломало множество программ.
Предполагая, что GCC продолжает делать то, что делает сейчас, он будет нормально работать с соглашением о вызовах по умолчанию x86 (и большинством соглашений о вызовах на большинстве архитектур), но я бы не стал зависеть от этого. Тестирование указателя функции на NULL на каждом месте вызова не намного дороже, чем вызов функции. Если вы действительно хотите, вы можете написать макрос:
#define CALL_MAYBE(func, args...) do {if (func) (func)(## args);} while (0)
Или вы можете использовать разные фиктивные функции для каждой подписи, но я понимаю, что вы хотели бы избежать этого.
Редактировать
Чарльз Бейли позвал меня по этому поводу, поэтому я пошел и посмотрел детали (вместо того, чтобы полагаться на свою дырявую память). В спецификации C указано
766 Указатель на функцию одного типа может быть преобразован в указатель на функцию другого типа и обратно;
767 результат должен сравниваться равным исходному указателю.
768 Если преобразованный указатель используется для вызова функции, тип которой не совместим с указанным типом, поведение не определено.
Пререлизы
и GCC 4.2 (это было решено до 4.3) следовали этим правилам: приведение указателя функции, как я писал, не приводило к NULL, но пыталось вызвать функцию через несовместимый тип, т.е.
func = (cb_t)nothing;
func(1);
из вашего примера приведет к abort
. Они вернулись к поведению 4.1 (разрешить, но предупредить), отчасти потому, что это изменение нарушило OpenSSL, но OpenSSL тем временем был исправлен, и это неопределенное поведение, которое компилятор может изменять в любое время.
OpenSSL только приводил указатели функций на другие типы функций, получая и возвращая одинаковое количество значений одинакового точного размера, и это (при условии, что вы не имеете дело с плавающей запятой) оказывается безопасным на всех платформах и соглашения о вызовах, о которых я знаю. Однако все остальное потенциально небезопасно.