Присвоение {указатель на функцию со связью C} на {указатель на функцию со связью C ++} и наоборот

Законен ли этот код?

extern "C" typedef void (ft_blah_c)();
/*extern "C++"*/ typedef void (ft_blah_cpp)();

extern "C" void fn_blah_c() {}
/*extern "C++"*/ void fn_blah_cpp() {}

ft_blah_c *g_Blah_c = fn_blah_cpp; // <--- ?
ft_blah_cpp *g_Blah_cpp = fn_blah_c; // <--- ?

У меня есть реальный код с похожими заданиями, он компилируется и выполняется без проблем (MSVC 2010).

2

Решение

В общем, это не должно работать. Проблема в том, что когда вы звоните fn_blah_c или же fn_blah_cppпрямо компилятор знает использовать функцию и соглашения о вызовах, но если вы сохраните их в указателе функции, компилятор видит только этот указатель и может использовать только тип указателя функции, чтобы определить, как передавать аргументы и возвращаемые типы.

Если соглашение о вызовах для C и C ++ одинаково в вашей среде, то оно может работать (и, возможно, именно поэтому ваш компилятор это разрешает), но это не так в общем случае, и назначение должно завершиться неудачей.

3

Другие решения

Нет, это не законно; преобразование между типами функций не может быть неявным.

Разрешается явно приводить между типами функций:

ft_blah_c *g_Blah_c = reinterpret_cast<ft_blah_c>(fn_blah_cpp);
ft_blah_cpp *g_Blah_cpp = reinterpret_cast<ft_blah_cpp>(fn_blah_c);

Однако на самом деле вызов функции через указатель на функцию другого типа — неопределенное поведение. Вы должны преобразовать обратно в исходный тип перед вызовом функции.

Основная причина этого, выходящая за рамки стандарта, заключается в том, что машинные инструкции для вызова функции могут различаться в зависимости от языковой связи функции. Это называется «Соглашения о вызовах» и включает в себя множество деталей, таких как соглашения о передаче аргументов и возвращаемых значений, пролог функции и эпилог и т. д.

Например, функция со связью языка C может ожидать нахождения своих аргументов в стеке, в то время как функция со связью языка C ++ может ожидать передачи аргументов в регистрах. Если вызывающий код не знает правильную связь, он поместит данные аргумента в одно место, а функция будет искать в другом и только читать мусор, поэтому не сможет работать правильно.

2

По вопросам рекламы [email protected]