Какие функции C ++ можно поместить в указатель функции C?

У меня есть библиотека C, которая использует структуру указателей на функции для обратных вызовов. Обратные вызовы будут вызываться из кода C.

extern "C" {
typedef struct callbacks_t {
    void (*foo) (const char*);
    int  (*bar) (int);  
} callbacks_t;
}// extern C

Какие типы функций C ++ я могу безопасно разместить в указателях на функции, которые будут вызываться из библиотеки C? Статические функции-члены? Полностью определенные функции шаблона? Не захватывающие лямбды?

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


person user1526370    schedule 29.04.2016    source источник
comment
но я сомневаюсь в безопасности, когда для функций C и C ++ используются разные соглашения о вызовах и языковые привязки. Нет таких различий, когда все было согласованно скомпилировано для целевой среды.   -  person πάντα ῥεῖ    schedule 29.04.2016
comment
Статические функции-члены, специализированные функции-шаблоны и лямбда-выражения без захвата не могут иметь внешнее соглашение о вызовах C, что формально неуместно. Но на практике это зависит от компилятора. И на практике главная проблема заключается не в том, что это не сработает, а в том, что некоторые компиляторы могут выдавать глупые предупреждения о формальном.   -  person Cheers and hth. - Alf    schedule 29.04.2016
comment
@ Cheersandhth.-Альф, это не имеет значения. Библиотека C должна иметь возможность без проблем вызывать функцию, имя которой изменено компилятором C ++.   -  person R Sahu    schedule 29.04.2016
comment
@RSahu: Дело не в изменении имени, а в соглашении о вызовах.   -  person Cheers and hth. - Alf    schedule 29.04.2016
comment
@ Cheersandhth.-Альф, просто любопытно. Существуют ли платформы, которые используют разные соглашения о вызовах для C и C ++?   -  person R Sahu    schedule 29.04.2016
comment
@RSahu: Никогда о таком не слышал. Я полагаю, что единственный компилятор, который, как сообщается (когда это обсуждалось в clc ++ однажды) выдает глупые предупреждения, был для Solaris. Так что я думаю, что это та же проблема, что и возможность дополнения или знакового представления целых чисел со знаком.   -  person Cheers and hth. - Alf    schedule 29.04.2016


Ответы (2)


У меня есть библиотека C, которая использует структуру указателей на функции для обратных вызовов. Обратные вызовы будут вызываться из кода C.

Библиотека C понимает только C. Таким образом, вы можете передавать только те вещи, которые явно поддерживаются и понимаются C.

Поскольку в C ++ нет определения соглашений о вызовах для любых типов функций, вы не можете явно передавать обратно функции C ++. Вы можете передать обратно только функции C (явно объявленные с extern "C") и гарантировать их совместимость.

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

extern "C" {
    typedef struct callbacks_t {
        void (*foo) (const char*);
        int  (*bar) (int);  
    } callbacks_t;

    // Any functions you define in here.
    // You can set as value to foo and bar.

}// extern C

Какие типы функций C ++ я могу безопасно разместить в указателях на функции, которые будут вызываться из библиотеки C?

Статические функции-члены?

Нет. Но это работает на многих платформах. Это распространенная ошибка, которая может укусить людей на некоторых платформах.

Полностью определенные функции шаблона?

No.

Не захватывающие лямбды?

No.

g ++, похоже, позволяет мне использовать все вышеперечисленное,

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

person Martin York    schedule 29.04.2016
comment
Несколько лет назад вы упомянули о встрече с компилятором C ++, у которого было соглашение о вызовах для статических членов, отличное от соглашения о вызовах C: stackoverflow.com/questions/1738313/ Я знаю это было очень давно, но я думаю, что любые подробности, которые вы сможете вспомнить об этом, были бы очень интересны. - person Michael Burr; 30.04.2016
comment
Это было во время моего пребывания в VERITAS (4 рабочих места назад). Я отвечал за систему сборки, которая построила программное обеспечение на 25 вариантах компилятора / ОС. К сожалению, я не могу вспомнить, какой именно. Но именно этот опыт заставляет меня держаться подальше от всего, что близко к неопределенному или неопределенному поведению. - person Martin York; 30.04.2016

В общем, если вы не используете приведение, вам следует доверять g ++.

Хотя верно, что ни один из упомянутых вами типов функций не может быть экспортирован для использования изнутри C, это не то, о чем вы спрашиваете. Вы спрашиваете, какие функции можно передавать в качестве указателя на функцию.

Чтобы ответить, что вы можете пройти, я думаю, что более конструктивно понять, что вы не можете пройти. Вы не можете передать ничего, что требует дополнительных аргументов, явно не указанных в списке аргументов.

Итак, никаких нестатических методов. Им нужно неявное «это». C не знает, как пройти. Опять же, компилятор вам не позволит.

Никаких лямбд. Им требуется неявный аргумент с фактическим телом лямбда.

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

  • Указатель на функцию. Неважно, стандартная это функция или шаблон, если шаблон полностью разрешен. Это не является проблемой. Любой написанный вами синтаксис, который приводит к указателю на функцию, автоматически полностью разрешит шаблон.
  • Не захватывающие лямбды. Это специальный обходной путь, представленный C ++ 11, когда он представил лямбда-выражения. Поскольку это возможно, компилятор выполняет явное преобразование, необходимое для этого.
  • Статические методы. Поскольку они статичны, им не передается неявный this, поэтому с ними все в порядке.

Последний требует дальнейшего развития. Многие механизмы обратного вызова C получают указатель на функцию и void * opaq. Следующее является стандартным и достаточно безопасным для использования с классом C ++:

class Something {
  void callback() {
    // Body goes here
  }

  static void exported_callback(void *opaq) {
    static_cast<Something*>(opaq)->callback();
  }
}

А затем сделайте:

Something something;

register_callback(Something::exported_callback, &something);

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

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

Однако это характерно не только для статических методов, лямбда-выражений и шаблонных функций. При таких обстоятельствах даже стандартная функция не сработает.

К сожалению, когда вы определяете указатель функции на тип stdcall, gcc игнорирует вас:

#define stdcall __attribute__((stdcall))
typedef stdcall void (*callback_type)(void *);

приводит к:

test.cpp:2:45: warning: ‘stdcall’ attribute ignored [-Wattributes]
 typedef stdcall void (*callback_type)(void *);
person Shachar Shemesh    schedule 29.04.2016
comment
Это очень похоже на практическую точку зрения. В этом нет ничего плохого. Но на формальном уровне нельзя смешивать соглашения о вызовах с четко определенным поведением. Думаю, стоит упомянуть и о формальном. Об этом стоит знать. - person Cheers and hth. - Alf; 29.04.2016
comment
Это все неправильно. Вы не можете передать какую-либо функцию C ++ коду C через указатель функции. Это связано с тем, что соглашения о вызовах для обоих языков не определены стандартом. Единственное, что вы можете перейти от C ++ к C и гарантировать его работу, - это функция C (явно объявленная с extern "C"). Может, он сломается, может быть, не так, как все неопределенное поведение, может показаться, что он отлично работает на вашей платформе. - person Martin York; 29.04.2016
comment
@LokiAstari Это тот ответ, которого я ожидал и боялся. Я пошел дальше и написал маленькие extern "C" прокладки для всех моих обратных вызовов, которые пересылают мои функции C ++. Функции C ++ встроены в прокладку, поэтому нет никаких накладных расходов, просто неудобно делать это таким образом. - person user1526370; 30.04.2016