Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

TL; DR

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

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

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

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

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

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

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

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

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

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

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

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

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.

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

If you have a nervous disposition, stop reading now. This gets hairy!

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

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(), то код openness.c может вызвать эту другую функцию (по имени), чтобы получить указатель на скрытую функцию.

reveal1.h

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

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

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

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

Смотрите Хорошая ли идея набрать указатели для общего обсуждения предмета typedef и указателей; вкратце: "это не очень хорошая идея, разве что с указателями на функции".

reveal1.c

#include <stdio.h>
#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() может быть возвращен revealer() и может быть вызван кодом внутри reveal.c. Вы можете удалить extern, не меняя смысла программы.

openness1.c

#include <stdio.h>
#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 <stdio.h>
#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 <stdio.h>
#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
$

Ответ 4

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

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

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

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

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

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

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

Нет связи

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

Срок хранения

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

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

Статическая

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

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

Это означает, что если вы используете ключевое слово 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;
}

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

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

#include <stdio.h>

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 - спецификатор класса хранения и продолжительность хранения static - это две разные вещи. Срок хранения - это атрибут объектов, который в некоторых случаях может быть изменен статически, но ключевое слово имеет многократное использование.

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

Ответ 5

Статическая Статические переменные объявлены с ключевым словом static. Начальное значение статической переменной равно 0. Статические переменные имеют область видимости файла блока.

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

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

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