Что такое «статическая» функция в C?

506

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

Я понимаю, что такое staticпеременная, но что такое staticфункция?

И почему, если я объявляю функцию, скажем void print_matrix, скажем так a.c(БЕЗ a.h) и включаю "a.c"- я получаю "print_matrix@@....) already defined in a.obj", НО, если я объявлю ее так, как static void print_matrixона компилируется?

ОБНОВЛЕНИЕ Просто чтобы прояснить ситуацию - я знаю, что в том числе .cплохо, как многие из вас указали. Я просто делаю это, чтобы временно освободить место, main.cпока у меня не появится лучшее представление о том, как сгруппировать все эти функции в нужные файлы .hи .cфайлы. Просто временное, быстрое решение.

Слава В
источник

Ответы:

685

staticфункции - это функции, которые видны только другим функциям в том же файле (точнее, той же единице перевода ).

РЕДАКТИРОВАТЬ : Для тех, кто думал, что автор вопросов означал «метод класса»: так как вопрос помечен, Cон означает обычную старую функцию C. Для методов класса (C ++ / Java / ...) staticозначает, что этот метод может быть вызван для самого класса, экземпляр этого класса не требуется.

Йоханнес Вайс
источник
2
На самом деле я не помечал это c ++, некоторые администраторы, вероятно, делали, но это было о C ++, так в чем же разница в C ++?
Слава V
16
Методы C ++ часто называют «функциями-членами», поэтому я согласен, что C ++ вносит некоторую двусмысленность. Это не ваша вина - язык просто использует ключевое слово для двух разных вещей.
Чак
2
Нет, он все еще означает функцию C ++. Свободная от C ++ функция, а не функция-член C ++.
Гонки легкости на орбите
3
@Chuck: терминология C ++ никогда не использует слово «метод»; это терминология Java - в стандартных документах C ++ она всегда называется «функцией члена» (см. этот ответ или этот глоссарий терминов C ++ против Java (например, C ++ использует «член данных», а Java использует «поле» и т. д.)).
ShreevatsaR
6
Чтобы прояснить этот ответ немного: имя функции видимо только для других частей того же модуля перевода, ниже первого объявления этого имени. Функция может быть вызвана из других модулей (и более ранних частей того же модуля) с помощью других средств, например указателя функции.
ММ
200

Существует большая разница между статическими функциями в C и статическими функциями-членами в C ++. В Си статическая функция не видна за пределами ее единицы перевода, то есть объектного файла, в который она компилируется. Другими словами, создание статической функции ограничивает область ее применения. Вы можете думать о статической функции как о «закрытой» для ее файла * .c (хотя это не совсем правильно).

В C ++ «static» также может применяться к функциям-членам и данным-членам классов. Статический член данных также называется «переменной класса», в то время как нестатический член данных является «переменной экземпляра». Это терминология Smalltalk. Это означает, что существует только одна копия статического члена данных, совместно используемого всеми объектами класса, в то время как каждый объект имеет свою собственную копию нестатического члена данных. Таким образом, статический член данных по сути является глобальной переменной, то есть членом класса.

Нестатические функции-члены могут получить доступ ко всем данным-членам класса: статическим и нестатическим. Статические функции-члены могут работать только со статическими данными-членами.

Один из способов думать об этом состоит в том, что в C ++ статические члены-данные и статические функции-члены принадлежат не какому-либо объекту, а всему классу.

Дима
источник
42
C ++ также имеет файловую статичность. Не нужно вводить С в это.
Гонки легкости на орбите
17
В C ++ статическая функция является статической функцией. Статическая функция-член - это статическая функция-член, также известная как метод. Тот факт, что C не имеет членов, не означает, что функции являются «C».
Gerasimos R
3
Есть ли разница между глобальным var и статическим классом var (кроме пространства имен)?
Александр Малахов
3
Пространство имен является основным отличием. Другое отличие состоит в том, что вы можете сделать статический элемент данных закрытым и, таким образом, доступным только из функций-членов класса. Другими словами, вы имеете гораздо больший контроль над статическим элементом данных по сравнению с глобальной переменной.
Дима
2
Может ли кто-нибудь объяснить, почему представление о статической функции как частной к ее файлу .c не совсем корректно? Что еще сказать?
YoTengoUnLCD
78

Существует два варианта использования ключевого слова static в отношении функций в C ++.

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

// inside some .cpp file:

static void foo();    // old "C" way of having internal linkage

// C++ way:
namespace
{
   void this_function_has_internal_linkage()
   {
      // ...
   }
}

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

Брайан Нил
источник
1
Вопрос о статике в ц.
Deqing
8
@ Задание вопроса было изначально помечено C ++, и автор говорит об использовании файлов .cpp.
Брайан Нил
57

Пример минимальной работоспособной многофайловой области

Здесь я проиллюстрирую, как 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
 */
/*void f() { puts("a f"); }*/

/* OK: only declared, not defined. Will use the one in main. */
void f(void);

/* OK: only visible to this file. */
static void sf() { puts("a sf"); }

void a() {
    f();
    sf();
}

main.c

#include <stdio.h>

void a(void);        

void f() { puts("main f"); }

static void sf() { puts("main sf"); }

void m() {
    f();
    sf();
}

int main() {
    m();
    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
./main

Вывод:

main f
main sf
main f
a sf

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

  • Есть две отдельные функции sf, по одной для каждого файла
  • есть одна общая функция f

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

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

Обычный шаблон C - это передача thisструктуры в качестве первого аргумента «метода», что в основном и делает C ++ под капотом.

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

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 f() { return 0; }
static int sf() { return 0; }

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

readelf -s main.o

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

Num:    Value          Size Type    Bind   Vis      Ndx Name
  5: 000000000000000b    11 FUNC    LOCAL  DEFAULT    1 sf
  9: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 f

таким образом, связывание - единственное существенное различие между ними. 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, sfсимвол полностью удаляется из таблицы символов: его нельзя использовать извне. TODO зачем вообще хранить статические функции в таблице символов, когда нет оптимизации? Могут ли они быть использованы для чего-либо?

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

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

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

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
3
примечание: void f() { puts("sf"); }(то есть два определения f()) вызывает неопределенное поведение без необходимости диагностики. Это проблема качества компоновщика, чтобы увидеть сообщение об ошибке.
ММ
2
Это лучшее и точное объяснение! Чем ты!
Aqua
20

Ниже приведены простые функции C - в классе C ++ модификатор «static» имеет другое значение.

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

В C каждый «модуль» (комбинация sample.c и sample.h) компилируется независимо, и после этого каждый из этих скомпилированных объектных файлов (sample.o) связывается компоновщиком с исполняемым файлом.

Допустим, у вас есть несколько файлов, которые вы включаете в свой основной файл, и два из них имеют функцию, которая используется только для удобства, add(int a, b)- для удобства компилятор легко создает объектные файлы для этих двух модулей, но компоновщик выдаст ошибку, потому что он находит две функции с одинаковыми именами и не знает, какую из них использовать (даже если нечего связывать, потому что они не используются где-то еще, кроме как в его собственном файле).

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

dersimn
источник
16

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

Во-вторых:

C ++ имеет здесь некоторую запутанную терминологию - я не знал об этом, пока не было указано в комментариях.

а) static functions- унаследовано от C, и о чем вы тут говорите. Вне любого класса. Статическая функция означает, что она не видна вне текущего модуля компиляции - поэтому в вашем случае a.obj имеет копию, а ваш другой код имеет независимую копию. (Вздутие конечного исполняемого файла с несколькими копиями кода).

б) static member function- что объектная ориентация называет статическим методом . Живет внутри класса. Вы вызываете это с помощью класса, а не через экземпляр объекта.

Эти два разных определения статических функций совершенно разные. Будьте осторожны - здесь будут драконы.

Дуглас Лидер
источник
Ну, я делаю это только для того, чтобы ВРЕМЕННО освободить место в main.cpp, пока не решу, как организовать файл в библиотеки вместе с соответствующими .hpp. Есть ли лучшая идея, как это сделать?
Слава V
1
Правильная терминология в C ++ - функция-член, а не метод. В легальном языке C ++ нет «методов». Метод является общим термином ОО. C ++ реализует их через функции-члены.
Брайан Нил
14

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

raimue
источник
7

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

Например, нестатический будет:

Person* tom = new Person();
tom->setName("Tom");

Этот метод работает с экземпляром класса, а не с самим классом. Однако у вас может быть статический метод, который может работать без экземпляра. Это иногда используется в шаблоне Factory:

Person* tom = Person::createNewPerson();
Попугаи
источник
2
Мне кажется, что вы говорите о статическом «методе», а не «функции» ??
Слава V
Я предположил, что вы имеете в виду статические функции в классе.
Попугаи
Если бы я знал, что «методы» называются «функциями метода» в C ++, я бы сказал об этом более четко. Ну, теперь я делаю :) В любом случае, спасибо
Slava V
5
В C ++ нет «методов», только функции. Стандарт C ++ никогда не упоминает «методы», только «функции».
Брайан Нил
1
@ Puddle Я знаю, что вы говорите, но в стандарте C ++ нет определения «метода». C ++ имеет только функции различных типов. «Метод» является общим термином ОО и используется в других языках и неофициально в C ++. Метод формально известен как «функция-член» в C ++.
Брайан Нил
7

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

Стандарт, вероятно, говорит что-то вроде:

«Каждая программа должна содержать ровно одно определение каждой не встроенной функции или объекта, которая используется в этой программе; диагностика не требуется».

Это C-взгляд на статические функции. Это не рекомендуется в C ++, однако.

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

Я бы добавил к примеру Parrot шаблон Singleton, который основан на статической функции-члене такого типа, чтобы получать / использовать один объект в течение всей жизни программы.

dirkgently
источник
7

Ответ на статическую функцию зависит от языка:

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

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

user2410022
источник
Это неправда. Объяснение вашего второго абзаца относится к « статическим функциям-членам » класса, а не « статическим функциям ». В C ++ функция, квалифицированная как, также staticимеет файловую область, как и в C.
RobertS поддерживает Monica Cellio
0

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

Вот простой пример.

main.c

#include <stdio.h>

static void test() 
{
    ghost(); // This is an unexist function.
}

int main()
{
    int ret = 0;

#ifdef TEST
#else
    test();
#endif
    return (ret);
} 

И скомпилировать с

gcc -o main main.c

Вы увидите, что это не удалось. Потому что вы даже не реализуете функцию ghost ().

Но что, если мы используем следующую команду?

gcc -DTEST -O2 -o main main.c

Это успех , и эту программу можно выполнить нормально.

Почему? Есть 3 ключевых момента.

  1. -O2: уровень оптимизации компилятора не менее 2.
  2. -DTEST: определить TEST, поэтому test () не будет вызываться.
  3. Определен «статический» для теста ().

Только если все эти 3 условия выполнены, вы можете пройти компиляцию. Из-за этого «статического» объявления компилятор может подтвердить, что test () НИКОГДА не будет вызываться в другом файле. Ваш компилятор может удалить test () при компиляции. Поскольку нам не нужен test (), не имеет значения, определен или реализован ghost ().

PoJyun Chiou
источник
0

« Что такое« static»функция в C? »

Давайте начнем с самого начала.

Все это основано на том, что называется «связь»:

« Идентификатор, объявленный в разных областях или в одной и той же области более одного раза, может быть создан для ссылки на один и тот же объект или функцию с помощью процесса, называемого связыванием. 29) Существует три вида связывания: внешнее, внутреннее и ни одного ».

Источник: С18, 6.2.2 / 1.


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

Источник: С18, 6.2.2 / 2.


Если функция определена без спецификатора класса хранилища, функция externпо умолчанию имеет все связи:

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

Источник: С18, 6.2.2 / 5.

Это означает, что - если ваша программа состоит из нескольких единиц перевода / исходных файлов ( .cили .cpp) - функция видна во всех единицах перевода / исходных файлах, которые есть в вашей программе.

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

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

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


30) Объявление функции может содержать спецификатор класса хранения только в том случае, если он находится в области видимости файла; см. 6.7.1.

Источник: С18, 6.2.2 / 3.


Таким образом, staticфункция имеет смысл только тогда, когда:

  1. Ваша программа содержит несколько единиц перевода / исходных файлов ( .cили .cpp).

    а также

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

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


Примечания стороны:

RobertS поддерживает Монику Челлио
источник