Что означает «статический» в C?

1140

Я видел слово, staticиспользуемое в разных местах в C-коде; это как статическая функция / класс в C # (где реализация разделяется между объектами)?

jww
источник
15
Какова причина удаления «в программе на C» из конца заголовка, @Lundin? Он немного избыточен при наличии тега c , но позволяет мне увидеть классификацию быстрее, без проверки тегов. Эта избыточность очень удобна, когда я подхожу к вопросу из направления, которое может содержать вопросы и о других языках, например, о статическом поиске или поиске в Google.
Палек
5
@Palec Существует SO-политика, согласно которой элементы, присутствующие в списке тегов, являются избыточными в заголовке. Сайт автоматически добавит C к фактическому веб-сайту. Google для "C static" дает этот ответ как главный хит. Причина, по которой это было изменено, заключается в том, что этот вопрос теперь является частью FAQ по языку SO C, и все добавленные посты немного отшлифованы.
Лундин
1
@Lundin Я предпочитаю держать «C» в заголовке, потому что SO добавляет только один тег в заголовок (самый распространенный?). Что, если когда-нибудь «синтаксис» достигнет большего количества вопросов, чем C (поскольку это межъязыковая вещь)? Я бы предпочел использовать явное поведение :-) Редактировать: ах, но есть мета-вопрос, говорящий иначе: meta.stackexchange.com/questions/19190/…
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
1
Это объяснение, которое я нашел на Quora. Определенно стоит прочитать!
nalzok

Ответы:

1521
  1. Статическая переменная внутри функции сохраняет свое значение между вызовами.
  2. Статическая глобальная переменная или функция «видна» только в файле, в котором она объявлена

(1) является более иностранной темой, если вы новичок, так что вот пример:

#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

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

(2) широко используется в качестве функции «контроля доступа». Если у вас есть файл .c, реализующий некоторые функции, он обычно предоставляет пользователям только несколько «публичных» функций. Остальные его функции должны быть выполнены staticтак, чтобы пользователь не мог получить к ним доступ. Это инкапсуляция, хорошая практика.

Цитируя Википедию :

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

И чтобы ответить на ваш второй вопрос, это не так, как в C #.

В C ++, однако, staticтакже используется для определения атрибутов класса (общих для всех объектов одного класса) и методов. В Си нет классов, поэтому эта особенность неактуальна.

Эли Бендерский
источник
179
Пакс, ОП не знает о статичности, так что ты предлагаешь вникнуть в разницу между единицами компиляции и файлами? :-)
Эли Бендерский
138
Единица компиляции - это отдельный файл, который видит компилятор. Ваш файл .c может включать в себя другие файлы .c, но после того, как препроцессор отсортирует включения, компилятор в конечном итоге увидит только одну «единицу компиляции».
Эли Бендерский
81
@robUK: компилятор даже не знает о файлах .h - они объединяются в файлы .c в препроцессоре. Так что да, вы можете сказать, что файл .c со всеми включенными в него заголовками является единым модулем компиляции.
Эли Бендерский
6
@TonyD, возможно, это сбивает с толку, но так работает компиляция. Обычно это может быть один .cи несколько заголовочных файлов, но дьявол всегда в том, что не типично.
Петер
7
@TonyD Компилятор выполняет компиляцию. Препроцессор выполняет предварительную обработку. Вызов цепочки инструментов «компилятором» не меняет того, что это такое или что он делает.
Майлз Рут
231

Здесь есть еще одно использование, которое не рассматривается, и оно является частью объявления типа массива в качестве аргумента функции:

int someFunction(char arg[static 10])
{
    ...
}

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

dreamlax
источник
3
Я не думал, что C имеет аргументы массива? Линус Торвальдс сердито кричит о том, что люди делают это.
СУПРЯМИ
13
@jamieb: C не имеет аргументов массива, но это конкретные синтаксиса означает , что функция ожидает arg[0]через к , arg[9]чтобы иметь значения (что также означает , что функция не принимает указатель NULL). Компиляторы могут использовать эту информацию для оптимизации, а статические анализаторы могут использовать эту информацию, чтобы гарантировать, что функция никогда не получает нулевой указатель (или, если он может сказать, массив с меньшим количеством элементов, чем указано).
сонлакс
19
@Qix - это было новое перегруженное значение, данное staticв C99. Ему более полутора десятилетий, но не все авторы компиляторов приняли все функции C99, поэтому C99 в целом остается в основном неизвестным.
Happy Green Kid Naps
@suprjami Я не уверен на 100%, что вы подразумеваете под «аргументами массива» , но если вы имеете в виду int arr[n];, то это VLA (массив переменной длины) , который был добавлен в C99. Это то, что вы имели в виду?
RastaJedi
170

Короткий ответ ... это зависит.

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

  2. Статические глобальные переменные не видны вне файла C, в котором они определены.

  3. Статические функции не видны вне файла C, в котором они определены.

cmcginty
источник
8
Значит ли «статическая функция» и «частная функция» одно и то же? Точно так же «статические глобальные переменные» и «частные глобальные переменные» - это одно и то же?
user1599964
40
Речь идет о C. В C. нет частного / публичного
Крис
19
@ user1599964, хотя privateв C этого нет, ваша аналогия хороша: static делает вещи "приватными" для данного файла. И файлы в C часто отображаются на классы в C ++.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
67

Пример многофайловой переменной

Здесь я иллюстрирую, как static влияет на область определения функций в нескольких файлах.

переменный ток

#include <stdio.h>

/*
Undefined behavior: already defined in main.
Binutils 2.24 gives an error and refuses to link.
/programming/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
*/
/*int i = 0;*/

/* Works in GCC as an extension: https://stackoverflow.com/a/3692486/895245 */
/*int i;*/

/* OK: extern. Will use the one in main. */
extern int i;

/* OK: only visible to this file. */
static int si = 0;

void a() {
    i++;
    si++;
    puts("a()");
    printf("i = %d\n", i);
    printf("si = %d\n", si);
    puts("");
}

main.c

#include <stdio.h>

int i = 0;
static int si = 0;

void a();    

void m() {
    i++;
    si++;
    puts("m()");
    printf("i = %d\n", i);
    printf("si = %d\n", si);
    puts("");
}

int main() {
    m();
    m();
    a();
    a();
    return 0;
}

GitHub вверх по течению .

Скомпилируйте и запустите:

gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o

Вывод:

m()
i = 1
si = 1

m()
i = 2
si = 2

a()
i = 3
si = 1

a()
i = 4
si = 2

интерпретация

  • есть две отдельные переменные для si, по одной для каждого файла
  • есть одна общая переменная для i

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

В программировании на C файлы часто используются для представления «классов», а staticпеременные представляют частные статические члены класса.

Что говорят об этом стандарты

C99 N1256 черновик 6.7.1 « Спецификаторы класса хранения» говорит, что staticэто «спецификатор класса хранения».

6.2.2 / 3 «Связи идентификаторов» говорит, что staticподразумевает internal linkage:

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

и 6.2.2 / 2 говорит, что internal linkageведет себя как в нашем примере:

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

где блок перевода - это исходный файл после предварительной обработки.

Как GCC реализует это для ELF (Linux)?

С STB_LOCALпривязкой.

Если мы скомпилируем:

int i = 0;
static int si = 0;

и разберите таблицу символов с помощью:

readelf -s main.o

вывод содержит:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 si
 10: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 i

таким образом, связывание - единственное существенное различие между ними. Valueэто просто их смещение в .bssразделе, поэтому мы ожидаем, что он будет отличаться.

STB_LOCALзадокументировано в спецификации ELF по адресу http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :

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

что делает его идеальным выбором для представления static.

Переменные без статики есть STB_GLOBAL, а в спецификации сказано:

Когда редактор ссылок объединяет несколько перемещаемых объектных файлов, он не позволяет использовать несколько определений символов STB_GLOBAL с одним и тем же именем.

что согласуется с ошибками ссылок в нескольких нестатических определениях.

Если мы запускаем оптимизацию с помощью -O3, siсимвол полностью удаляется из таблицы символов: его нельзя использовать извне. TODO зачем вообще хранить статические переменные в таблице символов, когда нет оптимизации? Могут ли они быть использованы для чего-либо? Возможно для отладки.

Смотрите также

C ++ анонимные пространства имен

В C ++ вы можете захотеть использовать анонимные пространства имен вместо статических, что дает аналогичный эффект, но дополнительно скрывает определения типов: Безымянный / анонимный пространства имен против статических функций

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
39

Это зависит:

int foo()
{
   static int x;
   return ++x;
}

Функция вернет 1, 2, 3 и т. Д. --- переменная не находится в стеке.

переменный ток:

static int foo()
{
}

Это означает, что эта функция имеет область действия только в этом файле. Так что у ac и bc могут быть разные foo()s, и foo не выставляется совместно используемым объектам. Так что, если вы определили foo в ac, вы не сможете получить к нему доступ b.cиз других мест.

В большинстве библиотек C все «частные» функции являются статическими, а большинство «открытых» - нет.

Артём
источник
18
+1 за упоминание х не в стеке или куче. Это на статической памяти.
Gob00st
1
@ Gob00st статическая память? Вы имели в виду "сегмент данных" ...?
Юша Алеауб
24

Люди продолжают говорить, что «статический» в Си имеет два значения. Я предлагаю альтернативный способ просмотра, который придает ему единственное значение:

  • Применение «статического» к элементу заставляет этот элемент иметь два свойства: (a) он не виден за пределами текущей области; (б) он постоянен

Причина, по которой он, кажется, имеет два значения, состоит в том, что в C каждый элемент, к которому может быть применен «статический», уже имеет одно из этих двух свойств , поэтому кажется, что это конкретное использование включает только другое.

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

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

ПРИМЕЧАНИЕ. Эти комментарии относятся только к C. В C ++ применение «static» к методам класса действительно дает ключевое слово в другом значении. Аналогично для расширения аргумента массива C99.

PMar
источник
Ваша (а) избыточна в лучшем случае. Никакая переменная не видна вне ее области видимости. Это просто определение объема. То, что вы имеете в виду, называется связью в стандарте C. staticдает внутреннюю связь с идентификатором.
Дженс
16

Из Википедии:

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

OscarRyz
источник
16

static означает разные вещи в разных контекстах.

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

    void foo ()
    {
    static int i = 0;
    printf("%d", i); i++
    }
  2. Другое использование статического - это когда вы реализуете функцию или глобальную переменную в файле .c, но не хотите, чтобы ее символ был виден вне .objсгенерированного файлом. например

    static void foo() { ... }
м-диез
источник
8

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

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

Джонатан Адельсон
источник
8

Ненавижу отвечать на старый вопрос, но я не думаю, что кто-либо упоминал, как K & R объясняет это в разделе A4.1 «языка программирования Си».

Короче говоря, слово static используется в двух значениях:

  1. Статический является одним из двух классов хранения (другой является автоматическим). Статический объект сохраняет свое значение между вызовами. Объекты, объявленные вне всех блоков, всегда статичны и не могут быть сделаны автоматически.
  2. Но когда static ключевое слово (большое внимание уделяется тому, чтобы оно использовалось в коде в качестве ключевого слова) используется с объявлением, оно дает этому объекту внутреннюю связь, поэтому его можно использовать только внутри этого модуля перевода. Но если ключевое слово используется в функции, оно меняет класс хранения объекта (в любом случае объект будет виден только внутри этой функции). Противоположностью статического является externключевое слово, которое дает объекту внешнюю связь.

Питер Ван Дер Линден дает следующие два значения в «Программировании Expert C»:

  • Внутри функции сохраняет свое значение между вызовами.
  • На уровне функций виден только в этом файле.
Jigglypuff
источник
Там третий класс хранения, зарегистрируйтесь . Некоторые люди также приводят аргументы в пользу четвертого класса хранилища, выделенного для хранилища, возвращаемого malloc и друзьями.
Йенс
@Jens 'register' - это только подсказка компилятору; хранение регистра не может быть применено из источника C. Так что я бы не стал считать это классом хранения.
GermanNerd
1
@GermanNerd Боюсь не согласен ISO C Standard с вашей точки зрения, так как он явно делает registerна хранение класса спецификатор (C99 6.7.1 хранения класса спецификаторы). И это больше, чем просто подсказка, например, вы не можете применить оператор address-of &к объекту с классом хранения registerнезависимо от того, выделяет ли компилятор регистр или нет.
Дженс
@Jens Спасибо, что напомнили мне о &. Я мог бы сделать слишком много C ++ ..... В любом случае, хотя 'register' является спецификатором класса хранилища, в действительности компилятор, скорее всего, создаст тот же машинный код для (бесполезного) спецификатора 'auto', что и для 'register' спецификатор. Таким образом, единственное, что остается, - это ограничение на уровне исходного кода невозможности получения адреса. Кстати, это небольшое обсуждение привело меня к поиску ошибки в Netbeans; со времени моего последнего обновления по умолчанию используется цепочка инструментов g ++ в новых проектах C!
GermanNerd
6

В Си static имеет два значения, в зависимости от области его использования. В глобальной области, когда объект объявляется на уровне файла, это означает, что этот объект виден только внутри этого файла.

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

void procedure(void)
{
   static int i = 0;

   i++;
}

значение 'i' инициализируется в ноль при первом вызове процедуры, и значение сохраняется при каждом последующем вызове процедуры. если бы «я» было напечатано, он вывел бы последовательность 0, 1, 2, 3, ...

Schot
источник
5

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

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

Starhowl
источник
5

Если вы объявите это в mytest.cфайле:

static int my_variable;

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

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

Статическая функция не может быть экспортирована из-за пределов файла. Таким образом, в *.cфайле вы скрываете функции и переменные, если объявляете их статическими.

ant2009
источник
4

Статические переменные в C имеют время жизни программы.

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

Например:

void function()
{
    static int var = 1;
    var++;
    printf("%d", var);
}

int main()
{
    function(); // Call 1
    function(); // Call 2
}

В вышеуказанной программе var хранится в сегменте данных. Его время жизни - вся программа на Си.

После вызова функции 1 varстановится 2. После вызова функции 2 varстановится 3.

Значение varне уничтожается между вызовами функций.

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

Инициализированные статические переменные хранятся в сегменте данных программы C, тогда как неинициализированные переменные хранятся в сегменте BSS.

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

Чтобы попробовать это:

file1.c

static int x;

int main()
{
    printf("Accessing in same file%d", x):
}

file2.c

    extern int x;
    func()
    {
        printf("accessing in different file %d",x); // Not allowed, x has the file scope of file1.c
    }

run gcc -c file1.c

gcc -c file2.c

Теперь попробуйте связать их, используя:

gcc -o output file1.o file2.o

Это даст ошибку компоновщика, так как x имеет область файла file1.c, и компоновщик не сможет разрешить ссылку на переменную x, используемую в file2.c.

Ссылки:

  1. http://en.wikipedia.org/wiki/Translation_unit_(programming)
  2. http://en.wikipedia.org/wiki/Call_stack
Сахиль Манчанда
источник
Я понимаю, что данные являются постоянными, что означает, что они не будут потеряны после каждого вызова функции, но почему не static int var = 1;меняет значение обратно на единицу каждый раз
Eames
3

Статическая переменная - это специальная переменная, которую вы можете использовать в функции, она сохраняет данные между вызовами и не удаляет их между вызовами. Например:

void func(){
    static int count; // If you don't declare its value, the value automatically initializes to zero
    printf("%d, ", count);
    ++count;
}

void main(){
    while(true){
        func();
    }
}

Выход:

0, 1, 2, 3, 4, 5, ...

Yagel
источник
Вы можете заменить printf("%d, ", count); count++;на `printf ("% d ", count ++) (не то, чтобы это имело значение: P).
RastaJedi
2

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

Джонатон
источник
2

Есть 2 случая:

(1) Объявлены локальные переменные static: размещены в сегменте данных вместо стека. Его значение сохраняется при повторном вызове функции.

(2) Объявлены глобальные переменные или функции static: невидимая внешняя единица компиляции (то есть являются локальными символами в таблице символов во время компоновки).

Джонни Конг
источник
1

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

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

#include<stdio.h> 
int fun() 
{ 
  static int count = 0; 
  count++; 
  return count; 
} 

int main() 
{ 
  printf("%d ", fun()); 
  printf("%d ", fun()); 
  return 0; 
}

Это выведет: 1 2

Как 1 остается в памяти, как он был объявлен статическим

Статические переменные (например, глобальные переменные) инициализируются как 0, если не инициализированы явно. Например, в приведенной ниже программе значение x выводится как 0, а значение y - что-то вроде мусора. Смотрите это для более подробной информации.

#include <stdio.h> 
int main() 
{ 
    static int x; 
    int y; 
    printf("%d \n %d", x, y); 
}

Это выведет: 0 [some_garbage_value]

Это основные из тех, что я обнаружил, но они не были объяснены выше для новичка!

erastone
источник
-1

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

#include<stdio.h> 
int counterFunction() 
{ 
  static int count = 0; 
  count++; 
  return count; 
} 

int main() 
{ 
  printf("First Counter Output = %d\n", counterFunction()); 
  printf("Second Counter Output = %d ", counterFunction()); 
  return 0; 
}

Выше программа даст нам такой вывод:

First Counter Output = 1 
Second Counter Output = 1 

Потому что как только мы вызываем функцию, она инициализирует count = 0. И пока мы выполняем, counterFunctionон уничтожит переменную count.

Макдия Хуссейн
источник
2
> Приведенная выше программа выдаст нам этот выход: первый выход счетчика = 1 второй выход счетчика = 1 <не соответствует действительности. Статические переменные инициализируются только один раз. Таким образом, результат будет 1, затем 2 и так далее.
GermanNerd