статический и внешний "C" / "C++"

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

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

против

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

РЕДАКТИРОВАТЬ: Можете ли вы показать компилятор или архитектуру, в которой версия статического члена не работает (и это не ошибка в компиляторе)?

Ответов (5)

Решение

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

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

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

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

void(*)(void)

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

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

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

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

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

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

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

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

Сфера:

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

Класс статического хранения:

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

Класс внешнего хранилища:

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

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

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

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

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

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

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

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

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

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

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