В чем разница между static и extern в C?

В чем разница между static и extern в C?

С http://wiki.answers.com/Q/What_is_the_difference_between_static_and_extern :

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

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

static означает, что переменная будет общеизвестна только в этом файле. extern означает, что глобальная переменная, определенная в другом файле, также будет известна в этом файле и также используется для доступа к функциям, определенным в других файлах.

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

Я не эксперт по C, поэтому я могу ошибаться в этом, но так я понял static и extern . Надеюсь, кто-то более осведомленный сможет предоставить вам лучший ответ.

EDIT: Исправленный ответ в соответствии с комментарием, предоставленным JeremyP.

Вы можете применять static как к переменным, так и к функциям. Существует два ответа, которые обсуждают поведение static и extern отношению к переменным, но ни один из них не охватывает функции. Это попытка исправить этот недостаток.

TL; DR

  • По возможности используйте статические функции.
  • Только объявлять внешние функции в заголовках.
  • Используйте заголовки, где определены функции и где используются функции.
  • Не объявлять функции внутри других функций.
  • Не используйте расширение GCC с определениями функций, вложенными в другие функции.

Внешние функции

По умолчанию функции в C видны вне единицы перевода (TU – в основном исходный файл C и включенные заголовки), в котором они определены. Такие функции можно вызывать по имени из любого кода, который уведомляет компилятор о существовании этой функции – обычно в объявлении в заголовке.

Например, заголовок делает видимыми объявления функций, таких как printf() , fprintf() , scanf() , fscanf() , fopen() , fclose() и т. Д. Если исходный файл содержит заголовок, он может вызывать функции. Когда программа привязана, необходимо указать правильную библиотеку для соответствия определению функции. К счастью, компилятор C автоматически предоставляет библиотеку, которая предоставляет (большинство) функции в стандартной библиотеке C (и обычно она предоставляет гораздо больше функций, чем только те). «Большая часть» оговорки применяется, потому что во многих системах (например, Linux, но не в macOS), если вы используете функции, объявленные в заголовке , вам нужно связать с библиотекой maths (библиотека «math» if вы американцы), что обычно указывается опцией -lm в командной строке компоновщика.

Обратите внимание, что внешние функции должны быть объявлены в заголовках. Каждая внешняя функция должна быть объявлена ​​в одном заголовке, но один заголовок может объявлять множество функций. Заголовок должен использоваться как в ТУ, где каждая функция определена, так и в каждом ТУ, который использует эту функцию. Вам не нужно писать декларацию для глобальной функции в исходном файле (в отличие от файла заголовка) – для объявления функции должен быть заголовок, и вы должны использовать этот заголовок, чтобы объявить его.

Статические функции

В качестве альтернативы общедоступным функциям вы можете ставить свои собственные функции. Это означает, что функция не может быть вызвана по имени извне TU, в котором она определена. Это скрытая функция.

Основным преимуществом статических функций является скрытие деталей, о которых внешнему миру не нужно знать. Это базовая, но мощная техника скрытия информации. Вы также знаете, что если функция статическая, вам не нужно искать возможности использования вне текущего TU, что может значительно упростить поиск. Однако, если функции являются static , могут быть несколько TU, каждый из которых содержит определение функции с тем же именем – каждый TU имеет свою собственную функцию, которая может или не может делать то же самое, что функция с тем же именем в другой ТУ.

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

Объявление функций внутри других функций

Возможно, но очень нецелесообразно объявлять функцию внутри области действия другой функции. Такие декларации пролетают перед лицом максимумов Agile Development, таких как SPOT (Single Point of Truth) и DRY (Do not Repeat Yourself). Они также являются ответственностью за обслуживание.

Однако вы можете, если хотите, написать код, например:

 extern int processor(int x); int processor(int x) { extern int subprocess(int); int sum = 0; for (int i = 0; i < x; i++) sum += subprocess((x + 3) % 7); return sum; } extern int subprocess(int y); int subprocess(int y) { return (y * 13) % 37; } 

Объявление в processor() достаточно для использования subprocess() , но в остальном неудовлетворительно. Объявление extern перед определением необходимо, если вы используете параметры компилятора GCC, такие как:

 $ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \ > -c process.c process.c:12:5: error: no previous prototype for 'subprocess' [-Werror=missing-prototypes] int subprocess(int y) ^~~~~~~~~~ cc1: all warnings being treated as errors $ 

Это, я считаю, хорошая дисциплина, аналогичная тому, что обеспечивает C ++. Это еще одна причина, по которой я делаю большинство функций статическими и определяю функции до их использования. Альтернативой является объявление статических функций в верхней части файла, а затем определение их в любом порядке. Есть некоторые достоинства для обоих методов; Я предпочитаю избегать необходимости объявлять и определять одну и ту же функцию в файле, определяя перед использованием.

Обратите внимание: вы не можете объявить static функцию внутри другой функции, и если вы попытаетесь определить такую ​​функцию, как subprocess() как статическую функцию, компилятор выдаст ошибку:

 process.c:12:16: error: static declaration of 'subprocess' follows non-static declaration static int subprocess(int y) ^~~~~~~~~~ process.c:5:20: note: previous declaration of 'subprocess' was here extern int subprocess(int); ^~~~~~~~~~ 

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

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

С помощью GCC опция -Wnested-externs идентифицирует вложенные объявления extern .

Вызывается по имени vs, вызванному указателем

Если у вас нервное расположение, перестаньте читать. Это становится волосатым!

Комментарий «по имени» означает, что если у вас есть объявление, например:

 extern int function(void); 

вы можете написать в своем коде:

 int i = function(); 

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

Теперь о статических функциях? Предположим, что TU reveal.c определяет функцию static void hidden_function(int) { … } . Затем, в другой TU openness.c , вы не можете писать:

 hidden_function(i); 

Только TU, который определяет скрытую функцию, может использовать его напрямую. Однако, если есть функция в reveal.c которая возвращает указатель на hidden_function() , тогда код hidden_function() может вызывать эту другую функцию (по имени), чтобы получить указатель на скрытую функцию.

reveal1.h

 extern void (*(revealer(void)))(int); 

Очевидно, что это функция, которая не принимает аргументов и возвращает указатель на функцию, которая принимает аргумент int и не возвращает значения. Нет; это некрасиво. Один раз, когда имеет смысл использовать typedef для указателей, есть указатели на функции ( reveal2.h ):

 typedef void (*HiddenFunctionType)(int); extern HiddenFunctionType revealer(void); 

Там: гораздо проще понять.

См. Это хорошая идея для указателей typedef для общей дискуссии по теме typedef и указателей; краткое резюме - «это не очень хорошая идея, кроме, быть может, указателей функций».

reveal1.c

 #include  #include "reveal1.h" static void hidden_function(int x) { printf("%s:%s(): %d\n", __FILE__, __func__, x); } extern void (*(revealer(void)))(int) { return hidden_function; } 

Да, это законно (но очень необычно), чтобы определить функцию с явным extern - я очень, очень редко это делаю, но здесь он подчеркивает роль extern и противопоставляет его static . Функция hidden_function() может быть возвращена hidden_function() и может быть вызвана кодом внутри reveal.c . Вы можете удалить extern без изменения значения программы.

openness1.c

 #include  #include "reveal1.h" int main(void) { void (*revelation)(int) = revealer(); printf("%s:%s: %d\n", __FILE__, __func__, __LINE__); (*revelation)(37); return 0; } 

Этот файл не может с пользой содержать прямой вызов по имени hidden_function() поскольку он скрыт в другом TU. Однако функция revealer() объявленная в reveal.h может быть вызвана по имени и возвращает указатель на скрытую функцию, которая затем может быть использована.

reveal2.c

 #include  #include "reveal2.h" static void hidden_function(int x) { printf("%s:%s(): %d\n", __FILE__, __func__, x); } extern HiddenFunctionType revealer(void) { return hidden_function; } 

openness2.c

 #include  #include "reveal2.h" int main(void) { HiddenFunctionType revelation = revealer(); printf("%s:%s: %d\n", __FILE__, __func__, __LINE__); (*revelation)(37); return 0; } 

Примеры выходов

Не самый захватывающий выход в мире!

 $ openness1 openness1.c:main: 7 reveal1.c:hidden_function(): 37 $ openness2 openness2.c:main: 7 reveal2.c:hidden_function(): 37 $ 

Оба эти модификатора имеют какое-то отношение к распределению памяти и привязке вашего кода. Стандарт C [3] относится к ним как спецификаторы classа хранения. С их помощью вы можете указать, когда выделять память для вашего объекта и / или как связать его с остальной частью кода. Давайте посмотрим, что именно нужно указать в первую очередь.

Связывание в C

Существует три типа связей: внешний, внутренний и ни один. Каждый объявленный объект в вашей программе (т. Е. Переменная или функция) имеет какую-то связь, обычно определяемую обстоятельствами декларации. Связывание объекта говорит, как объект распространяется по всей программе. Связывание может быть изменено с помощью ключевых слов extern и static.

Внешняя связь

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

Внутренняя связь

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

Нет связи

Никакая привязка делает объекты полностью закрытыми для области, в которой они были определены. Как видно из названия, связь не выполняется. Это относится ко всем локальным переменным и функциональным параметрам, доступным только изнутри тела функции, нигде больше.

Длительность хранения

Другая область, затронутая этими ключевыми словами, – это время хранения, то есть время жизни объекта через время выполнения программы. В C-статическом и автоматическом режиме существует два типа хранения.

Объекты со статическим временем хранения инициализируются при запуске программы и остаются доступными в течение всего времени выполнения. Все объекты с внешней и внутренней связью также имеют статическую продолжительность хранения. Автоматическая продолжительность хранения по умолчанию для объектов без привязки. Эти объекты выделяются при входе в блок, в котором они были определены и удалены при завершении выполнения блока. Длительность хранения может быть изменена ключевым словом static.

статический

В языке C есть два разных использования этого ключевого слова. В первом случае статическая модификация связывает переменную или функцию. Стандарт ANSI гласит:

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

Это означает, что если вы используете ключевое слово static на уровне файла (т. Е. Не в функции), это изменит привязку объекта к внутреннему, делая его закрытым только для файла или, точнее, для единицы компиляции.

 /* This is file scope */ int one; /* External linkage. */ static int two; /* Internal linkage. */ /* External linkage. */ int f_one() { return one; } /* Internal linkage. */ static void f_two() { two = 2; } int main(void) { int three = 0; /* No linkage. */ one = 1; f_two(); three = f_one() + two; return 0; } 

Переменная и функция () будут иметь внутреннюю связь и не будут видны из любого другого модуля.

Другое использование ключевого слова static в C – это указать продолжительность хранения. Ключевое слово можно использовать для изменения продолжительности автоматического хранения до статического. Статическая переменная внутри функции выделяется только один раз (при запуске программы), и поэтому она сохраняет свое значение между вызовами

 #include  void foo() { int a = 10; static int sa = 10; a += 5; sa += 5; printf("a = %d, sa = %d\n", a, sa); } int main() { int i; for (i = 0; i < 10; ++i) foo(); } 

Результат будет выглядеть так:

 a = 15, sa = 15 a = 15, sa = 20 a = 15, sa = 25 a = 15, sa = 30 a = 15, sa = 35 a = 15, sa = 40 a = 15, sa = 45 a = 15, sa = 50 a = 15, sa = 55 a = 15, sa = 60 

внешний

Ключевое слово extern означает, что «этот идентификатор объявлен здесь, но определен в другом месте». Другими словами, вы сообщаете компилятору, что какая-то переменная будет доступна, но ее память выделяется где-то в другом месте. Дело в том, где? Давайте сначала рассмотрим разницу между объявлением и определением некоторого объекта. Объявляя переменную, вы говорите, какой тип является переменной и какое имя она будет позже в вашей программе. Например, вы можете сделать следующее:

 extern int i; /* Declaration. */ extern int i; /* Another declaration. */ 

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

 int i = 0; /* Definition. */ 

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

 /* definition, external linkage */ int i1 = 1; /* definition, internal linkage */ static int i2 = 2; /* tentative definition, external linkage */ int i3; /* valid tentative definition, refers to previous */ int i1; /* valid tenative definition, refers to previous */ static int i2; /* valid tentative definition, refers to previous */ int i3 = 3; /* refers to previous, whose linkage is external */ extern int i1; /* refers to previous, whose linkage is internal */ extern int i2; /* refers to previous, whose linkage is external */ extern int i4; int main(void) { return 0; } 

Это будет скомпилировано без ошибок.

Резюме

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

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