Что означает «статический» в C? - PullRequest
987 голосов
/ 21 февраля 2009

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

Ответы [ 18 ]

1367 голосов
/ 21 февраля 2009
  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 используется для хранения переменной в статически выделенной памяти вместо автоматически распределяемого объем памяти. Пока язык не диктовать реализацию либо тип памяти, статически распределенный память обычно резервируется в данных сегмент программы при компиляции время, в то время как автоматически выделенная память обычно реализован как транзитный стек вызовов.

См. здесь и здесь для получения более подробной информации.

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

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

213 голосов
/ 01 мая 2013

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

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

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

154 голосов
/ 21 февраля 2009

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

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

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

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

51 голосов

Пример области действия нескольких файлов

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

a.c

#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 upstream .

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

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 draft 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 ++ вы можете использовать анонимные пространства имен вместо статических, что обеспечивает аналогичный эффект, но дополнительно скрывает определения типов: Безымянные / анонимные пространства имен и статические функции

36 голосов
/ 21 февраля 2009

Это зависит от:

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 все "частные" функции являются статическими, а большинство "публичных" - нет.

21 голосов
/ 27 апреля 2016

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

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

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

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

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

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

14 голосов
/ 21 февраля 2009

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

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

14 голосов
/ 21 февраля 2009

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

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

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

    static void foo() { ... }
    
7 голосов
/ 31 марта 2016

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

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

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

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

  • Внутри функции сохраняет значение между вызовами.
  • На уровне функций, видимый только в этом файле.
6 голосов
/ 21 февраля 2009

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

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

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

   i++;
}

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

...