Static vs extern "C" / "С++"

В чем разница между статической функцией-членом и внешней связью "C"? Например, при использовании "makecontext" в С++ мне нужно передать указатель на функцию. Google рекомендует использовать внешнюю ссылку "C" для него, потому что "makecontext" - C. Но я обнаружил, что использование статических работ также. Мне просто повезло или...

class X {
   public:
   static void proxy(int i) {}
}
makecontext(..., (void (*)(void)) X::proxy, ...);

против

extern "C" void proxy(int i) {}
makecontext(..., (void (*)(void)) proxy, ...);

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

Ответ 1

Да, вам просто повезло:) Экстерн "C" - это одна языковая связь для языка C, которую должен поддерживать каждый компилятор С++, помимо внешнего "С++", который является значением по умолчанию. Компиляторы могут поддерживать другие языковые связи. GCC, например, поддерживает внешнюю "Java", которая позволяет взаимодействовать с Java-кодом (хотя это довольно громоздко).

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

extern "C" void foo() { ... }

Несмотря на то, что тип указателя функции не содержит спецификацию привязки, но выглядит скорее как

void(*)(void)

Связывание является неотъемлемой частью типа - вы просто не можете выразить его напрямую без typedef:

extern "C" typedef void(*extern_c_funptr_t)();

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

Ответ 2

Обратите внимание, что extern C - это рекомендуемый способ взаимодействия C/С++. Здесь мастер говорит об этом. Чтобы добавить к edifuly ответ: обратите внимание, что статические функции и переменные в глобальном пространстве имен устарели. По крайней мере используйте анонимное пространство имен.

Вернуться к extern C: если вы не используете extern C, вам нужно будет узнать точное искаженное имя и использовать его. Это гораздо больнее.

Ответ 3

extern "C" отключает компиляцию имени компилятора С++ (что необходимо для перегрузки).

Если вы объявляете функцию в A.cpp равной static, то она не может быть найдена B.cpp(она оставлена ​​от C и имеет тот же эффект, что и функция внутри анонимного пространства имен).

Ответ 4

Большая часть того, что extern "C" делает, в значительной степени зависит от компилятора. Многие платформы меняют соглашение об изъятии имени и вызова, основанное на объявлении, но ни один из них не указан стандартом. На самом деле единственное, что требуется стандарту, это то, что код в блоке можно вызвать из C-функций. Что касается вашего конкретного вопроса, стандарт говорит:

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

Это означает, что extern "C" void proxy(int i) {} и /*extern "C++"*/void proxy(int i) {} имеют разные типы, и в результате указатели на эти функции будут иметь разные типы. Компилятор не подводит ваш код по той же причине, что не провалил бы большой кусок работы, например:

int *foo = (int*)50;
makecontext(..., (void (*)(void)) foo, ...);

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

Что касается статических функций-членов, они не обязаны иметь указатель this, поэтому компилятор может рассматривать их как функцию, не являющуюся членом. Опять же, поведение здесь специфично для платформы.

Ответ 5

Вообще говоря,

Классы хранения:

Классы хранения

используются для указания продолжительности и объема переменной или идентификатора.

Продолжительность:

Продолжительность указывает продолжительность жизни переменной.

Область применения:

Область видимости указывает на видимость переменной.

Статический класс хранения:

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

Внешний класс хранения:

Класс extern storage используется для объявления глобальной переменной, которая будет известна функциям в файле и может быть известна всем функциям программы. Этот класс хранения имеет постоянную продолжительность. Любая переменная этого класса сохраняет свое значение до тех пор, пока не будет изменено другим назначением. Сфера охвата является глобальной. Переменная может быть известна или видима всеми функциями внутри программы.