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

983

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

  • 3
    Связанный: Static (ключевое слово) @ Wikipedia
  • 11
    Какова причина удаления "в программе на C" из конца заголовка, @Lundin? Он немного избыточен при наличии тега c , но позволяет мне увидеть классификацию быстрее, без проверки тегов. Эта избыточность очень удобна, когда я подхожу к вопросу из направления, которое может содержать вопросы и о других языках, например, о статическом поиске или поиске в Google.
Показать ещё 3 комментария
Теги:
static
syntax

20 ответов

1250
Лучший ответ
  • Статическая переменная внутри функции сохраняет свое значение между вызовами.
  • Статическая глобальная переменная или функция "видятся" только в файле, объявленном в

(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, чтобы пользователь не смог получить к ним доступ. Это инкапсуляция, хорошая практика.

Цитирование Wikipedia:

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

Подробнее см. здесь и здесь.

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

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

  • 142
    Будучи немного педантичным, это блок компиляции, а не файл.
  • 165
    Пакс, ОП не знает о статичности, так что ты предлагаешь вникнуть в разницу между единицами компиляции и файлами? :-)
Показать ещё 26 комментариев
218

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

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

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

  • 2
    Я не думал, что C имеет аргументы массива? Линус Торвальдс сердито кричит о том, что люди делают это.
  • 11
    @jamieb: C не имеет аргументов массива, но этот специфический синтаксис означает, что функция ожидает от arg[0] до arg[9] значений (что также означает, что функция не принимает нулевой указатель). Компиляторы могут использовать эту информацию для оптимизации, а статические анализаторы могут использовать эту информацию, чтобы гарантировать, что функция никогда не получает нулевой указатель (или, если он может сказать, массив с меньшим количеством элементов, чем указано).
Показать ещё 2 комментария
156

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

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

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

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

  • 7
    Значит ли «статическая функция» и «частная функция» одно и то же? Точно так же «статические глобальные переменные» и «частные глобальные переменные» - это одно и то же?
  • 38
    Это о C. В C. нет частного / публичного
Показать ещё 1 комментарий
50

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

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

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

#include <stdio.h>

/*
Undefined behavior: already defined in main.
Binutils 2.24 gives an error and refuses to link.
https://stackoverflow.com/questions/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++ вы можете захотеть использовать анонимные пространства имен вместо статических, что дает аналогичный эффект, но дополнительно скрывает определения типов: Безымянный/анонимный пространства имен против статических функций

35

Это зависит:

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

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

a.c:

static int foo()
{
}

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

В большинстве библиотек C все функции "private" являются статическими и большинство "общедоступных" не являются.

  • 18
    +1 за упоминание х не в стеке или куче. Это на статической памяти.
  • 1
    @ Gob00st статическая память? Вы имели в виду "сегмент данных" ...?
19

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

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

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

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

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

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

12

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

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

12

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

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

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

    static void foo() { ... }
    
6

Мне не нравится отвечать на старый вопрос, но я не думаю, что кто-то упомянул, как K & R объясняет это в разделе A4.1 "Язык программирования C".

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

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

Питер Ван Дер Линден дает эти два значения в "Программе программирования экспертов":

  • Внутри функции сохраняется ее значение между вызовами.
  • На уровне функции отображается только в этом файле.
  • 0
    Там третий класс хранения, зарегистрируйтесь . Некоторые люди также приводят аргументы в пользу четвертого класса хранилища, выделенного для хранилища, возвращаемого malloc и друзьями.
5

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

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

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

   i++;
}

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

5

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

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

4

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

static int my_variable;

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

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

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

3

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

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

Например:

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

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

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

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

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

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

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

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

Чтобы попробовать следующее:

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, используемую в файле2.c.

Литература:

  • 0
    Я понимаю, что данные являются постоянными, то есть они не будут потеряны после каждого вызова функции, но почему не static int var = 1; изменить значение обратно на единицу каждый раз
3

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

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

2

Также следует отметить, что static может использоваться 4 различными способами.

to create permanent storage for local variables in a function.
to specify internal linkage.
to declare member functions that act like non-member functions.
to create a single copy of a data member.
2

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

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,...

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

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

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

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

1

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

0

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

bubblesort.c показывает функцию, называемую bubblesort, вместе со вспомогательными функциями для отслеживания количества выполненных обменов. Переменная count имеет статический класс хранения, потому что объявляется вне любого блока. Статический определитель заставляет эту переменную иметь внутреннюю связь, гарантируя, что переменная count не может быть напрямую доступна какой-либо функцией, кроме bubblesort.c. Функция clearcount и обмен в функции onepass являются единственными сегментами кода, которые изменяют count. Внутренняя связь позволяет другим файлам, связанным с bubblesort.c, использовать идентификатор, счетчик, не мешая целому числу count в этом файле.

Три функции clearcount, getcount и bubblesort имеют внешнюю связь и доступны извне. Обратите внимание, что статический классификатор для onepass дает эту внутреннюю привязку этой функции, чтобы она не была доступна извне этого файла. Используя соответствующие классы хранения и привязки, bubblesort скрывает детали своей реализации от своих вызывающих абонентов.

bubblesort.c

Функция, которая сортирует массив целых чисел и подсчитывает количество обменов, сделанных в процессе.

static int count = 0;

static int onepass(int a[], int n) { /* return true if interchanges are made */
   int i;
   int interchanges = 0;
   int temp;

   for (i = 0; i < n - 1; i++)
      if (a[i] > a[i+1]) {
         temp = a[i];
         a[i] = a[i+1];
         a[i+1] = temp;
         interchanges = 1;
         count++;
      }
   return interchanges;
}

void clearcount(void) {
   count = 0;
}

int getcount(void) {
   return count;
}

void bubblesort(int a[], int n) {               /* sort a in ascending order */
   int i;
   for (i = 0; i < n - 1; i++)
      if (!onepass(a, n - i))
         break;
}

bubblesorttest.c

#include <stdio.h>

#define ARRAYSIZE 10

void bubblesort(int a[], int n);
int getcount();

int main(void) {
   int i;

   int a[ARRAYSIZE];
   printf("Enter %d integers to sort\n", ARRAYSIZE);
   for (i = 0; i < ARRAYSIZE; i++)
      scanf("%d", a+i);
   printf("Array follows:\n");
   for (i = 0;i < ARRAYSIZE; i++)
      printf("%2d: %4d\n", i, a[i]);
   bubblesort(a, ARRAYSIZE);
   printf("Sorted array follows:\n");
   for (i = 0; i < ARRAYSIZE; i++)
      printf("%2d: %4d\n", i, a[i]);
   printf("Number of ineterchanges: %d\n", getcount());
   return 0;
}

Для каждого объекта и функции в bubblesort.c укажите хранилище и класс привязки, где это необходимо.

Ответ:

Функция onepass имеет внутреннюю связь. Другие функции внешняя связь. Функции не имеют класса хранения. countидентификатор имеет внутреннюю связь и статическое хранилище. Все остальные переменные не имеют привязки и автоматического хранения.

Источник: UNIX Systems Programming: Communication, Concurrency и Threads 2nd Edition, Kay Robbins и Steven Robbins

-2

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

Пример -

public class Temperature
       {
  public static double CelsiusToFahrenheit(string temperatureCelsius)
{
    // Convert argument to double for calculations.
    double celsius = Double.Parse(temperatureCelsius);

    // Convert Celsius to Fahrenheit.
    double fahrenheit = (celsius * 9 / 5) + 32;

    return fahrenheit;
}
       }

class Converter
   {
 void Main()
{
  double F= Temperature.CelsiusToFahrenheit(78);
  Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
}
    }

Ещё вопросы

Сообщество Overcoder
Наверх
Меню