статические против внешних "C" / "C ++" - PullRequest
22 голосов
/ 26 февраля 2009

В чем разница между статической функцией-членом и внешней функцией связи "C"? Например, при использовании «makecontext» в C ++ мне нужно передать указатель на функцию. Google рекомендует использовать для этого внешнюю связь «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 ]

33 голосов
/ 26 февраля 2009

Да, вам просто повезло :) Extern "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 ++ в строгом режиме выдаст ошибку, например, если вы попытаетесь присвоить адрес указанной выше внешней функции "C" для (void(*)()), потому что это указатель на функцию с C ++ связь.

5 голосов
/ 26 февраля 2009

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

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

4 голосов
/ 26 февраля 2009

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

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

2 голосов
/ 06 августа 2013

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

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

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

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

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

Область:

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

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

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

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

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

2 голосов
/ 26 февраля 2009

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

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

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

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

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

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

...