Подтвердить что ты не робот

В С++, безопасно/переносимо использовать статический указатель функции-члена для обратных вызовов C API?

В С++, безопасно/переносимо использовать статический указатель функции элемента для обратных вызовов API C? Является ли ABI статической функции-члена такой же, как функция C?

4b9b3361

Ответ 1

Это не безопасно для стандарта С++. Как указано в этой публикации SO:

Функция обратного вызова C, реализованная в С++, должна быть extern "C". Может показаться, что он работает как статическая функция в классе, потому что функции класса-статики часто используют одно и то же соглашение о вызовах как функция C. Тем не менее, это ошибка, ожидающая появления (см. Комментарии ниже), поэтому, пожалуйста, не делайте этого - перейдите в оболочку extern "C".

И в соответствии с комментариями, сделанными Мартином Йорком в этом ответе, существуют реальные проблемы, пытающиеся сделать это на некоторых платформах.

Сделайте свои обратные вызовы C ABI extern "C".


Изменить: добавление некоторых поддерживающих котировок из стандартного (выделение):

3.5 "Программа и связь":

После всех настроек типов (в течение которых typedefs (7.1.3) заменяются их определениями), типы, указанные всеми объявлениями, относящимися к данному объекту или функции, должны быть идентичными, за исключением того, что объявления для объекта массива могут указывать типы массивов, которые отличаются наличием или отсутствием главной привязки массива (8.3.4). Нарушение этого правила по типу идентичности не требует диагностики. [3.5/10]

[Примечание: привязка к объявлениям, отличным от С++, может быть достигнута с использованием спецификации привязки (7.5). ] [3.5/11]

и

7.5 "Характеристики связи":

... Два типа функций с разными языковыми связями - это различные типы, даже если они идентичны друг другу. [7,5/1]

Итак, если код, делающий обратный вызов, использует привязки языка C для обратного вызова, то цель обратного вызова (в программе на С++) также должна быть.

Ответ 2

После поиска и нескольких разрывов при атаке других проблем я нашел ответ, который ясен и краток (для стандартного, во всяком случае):

Вызов функции через выражение, тип функции которого имеет языковое связывание, отличное от языковой привязки типа функции определения вызываемой функции undefined. [5.2.2/1]

Я все же утверждаю, что на фундаментальном уровне проблематично использовать текст из стандарта С++ для определения поведения библиотеки C, скомпилированной с помощью компилятора C, и точно, как работает эта межъязыковая интероперабельность, очень специфична для реализации; однако, это самое близкое, я думаю, что любой стандарт может (в настоящее время) надеяться определить такое взаимодействие.

В частности, это поведение undefined (и не использует библиотеку C, чтобы проблема не возникала):

void call(void (*pf)()) { pf(); } // pf() is the UB
extern "C" void f();
int main() { call(f); }
// though I'm unsure if a diagnostic is required for call(f)

Comeau дает диагностику в call(f) (хотя она может это сделать, даже если диагностика не требуется).

Это не поведение undefined и показывает, как включить ссылку на язык в виде указателя функции (который находится через typedef):

extern "C" typedef void F();
void call(F* pf) { pf(); }
extern "C" void f();
int main() { call(f); }

Или можно написать:

extern "C" {
typedef void F();
void f();
}
void call(F* pf) { pf(); }
int main() { call(f); }

Ответ 3

Для всех компиляторов Windows С++, о которых я знаю, ответ да, но ничто в стандарте языка не гарантирует этого. Я бы не позволил этому остановить вас, однако, это очень распространенный способ реализации обратных вызовов с использованием С++ - вы можете обнаружить, что вам нужно объявлять статические функции как WINAPI. Это взято из старой библиотеки потоков:

class Thread {
   ...
   static DWORD WINAPI ThreadFunction( void * args );
};

где это использование обратного вызова посредством API-интерфейсов потоковой передачи Windows.

Ответ 4

ABI не распространяется ни на стандарты C, ни на С++, хотя С++ дает вам "языковое соединение" через extern "C". Таким образом, ABI является основным компилятором/платформой. Оба стандарта оставляют много, многое до реализации, и это один из них.

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


Как я понимаю, правила ISO не позволяют стандарту чаще, чем раз в 10 лет (но могут быть различные публикации, такие как TC1 и TR1 для С++). Плюс есть идея (я не уверен, что это происходит от ИСО, переносится из комитета С или даже из других источников), чтобы "дистиллировать" /стандартизировать существующую практику, а не уходить в левое поле, и есть много существующие практики, некоторые из которых конфликтуют.