Що таке "статична" функція в C?


505

Питання було про просто функції, не 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Функції - це функції, які видно лише іншим функціям того ж файлу (точніше того ж блоку перекладу ).

EDIT : Для тих, хто думав, що автор запитань мав на увазі "метод класу": оскільки питання позначено тегом, 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
Щоб трохи уточнити цю відповідь: ім’я функції видно лише в інших частинах тієї ж одиниці перекладу, під першою заявою цього імені. Функція може бути викликана з інших одиниць (і більш ранніх частин одного блоку) за допомогою інших засобів, наприклад, вказівника функції.
ММ

199

Існує велика різниця між статичними функціями в C і статичними функціями члена в C ++. У C статична функція не видно поза її блоком перекладу, який є об'єктним файлом, в який вона компілюється. Іншими словами, створення функції статики обмежує її сферу застосування. Ви можете вважати статичну функцію "приватною" для свого файлу * .c (хоча це не зовсім коректно).

У C ++ "статичний" може також застосовуватися до функцій-членів та членів даних класів. Статичний член даних також називається "змінною класу", а нестатичний член даних - "змінною екземпляра". Це термінологія Smalltalk. Це означає, що існує лише одна копія статичного члена даних, який спільно використовується всіма об'єктами класу, тоді як у кожного об’єкта є своя копія нестатичного члена даних. Отже, статичний член даних є по суті глобальною змінною, тобто членом класу.

Нестатичні функції членів можуть отримати доступ до всіх членів класу даних: статичних та нестатичних. Статичні функції членів можуть працювати лише на статичних елементах даних.

Один із способів подумати над цим - це те, що в C ++ статичні дані даних і статичні функції членів належать не до жодного об'єкта, а до всього класу.


42
C ++ також має файл-статичний. Не потрібно залучати С до цього.
Гонки легкості на Орбіті

17
У C ++ статична функція - це статична функція. Функція статичного члена - це статична функція члена, також відома як метод. Те, що у C немає членів, не означає, що функції - "C".
Герасимос R

3
Чи є різниця між глобальним var та статичним var var класу (крім простору імен)?
Олександр Малахов

3
Простір імен - головна відмінність. Інша відмінність полягає в тому, що ви можете зробити статичний член даних приватним і таким чином доступний лише з-за функцій-членів класу. Іншими словами, у вас набагато більше контролю над статичним членом даних порівняно з глобальною змінною.
Діма

2
Чи може хтось пояснити, чому розгляд статичної функції як приватної для її .c-файлу не є строго правильним? Що залишається сказати?
YoTengoUnLCD

77

Існує два варіанти використання ключового слова статичний, коли мова йде про функції в 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()
   {
      // ...
   }
}

Друге використання в контексті класу. Якщо клас має статичну функцію члена, це означає, що функція є членом класу (і має звичайний доступ до інших членів), але її не потрібно викликати через певний об'єкт. Іншими словами, всередині цієї функції немає "цього" вказівника.


1
Питання про статичність у c.
Декін

8
@Deqing питання спочатку було позначено тегом 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функції представляють "приватні" методи класу.

Поширений шаблон С - це пройти а this структуру навколо як перший аргумент "методу", який в основному те, що C ++ робить під кришкою.

Які стандарти про це говорять

У проекті 6.7.1 " Специфікатори класу зберігання" C99 N1256 говориться, що 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 ++ ви можете використовувати анонімні простори імен замість статичних, що досягає аналогічного ефекту, але додатково приховує визначення типів: Без назви / анонімні простори імен проти статичних функцій


3
Примітка: void f() { puts("sf"); }(тобто два визначення f()) викликає невизначеність поведінки без діагностики. Проблема якості зв’язків - це фактично побачити повідомлення про помилку.
ММ

2
Це найкраще і точне пояснення! Ніж ви!
Аква

20

Далі йдеться про прості функції С - у класі C ++ модифікатор "статичний" має інше значення.

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

У C кожен "модуль" (комбінація sample.c та sample.h) складається безперервно, після чого кожен із цих компільованих об'єктних файлів (sample.o) зв'язується разом з виконуваним файлом лінкером.

Скажімо, у вас є декілька файлів, які ви включаєте в свій основний файл, і два з них мають функцію, яка використовується лише для зручності під назвою add(int a, b)- компілятор легко створить об’єктні файли для цих двох модулів, але лінкер видасть помилку, оскільки він знаходить дві функції з однаковою назвою, і не знає, яку з них використовувати (навіть якщо нічого не має для зв'язку, оскільки вони використовуються не десь, а у власному файлі).

Ось чому ви робите цю функцію, яка використовується тільки внутрішню, статичну функцію. У цьому випадку компілятор не створює типовий "ви можете зв'язати цю річ" -флаг для лінкера, так що лінкер не бачить цю функцію і не генерує помилку.


16

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

По-друге:

У C ++ тут ​​є якась заплутана термінологія - я про це не знав, поки не зазначив у коментарях.

а) static functions - успадковано від С, і про що ви тут говорите. Поза будь-якого класу. Статична функція означає, що вона не відображається поза поточним блоком компіляції - тому у вашому випадку a.obj має копію, а ваш інший код має незалежну копію. (Здуття остаточного виконуваного файлу з кількома копіями коду).

б) static member function- те, що Об'єктна орієнтація називає статикою методом . Живе всередині класу. Ви називаєте це класом, а не об'єктом об'єкта.

Ці два визначення статичних функцій абсолютно різні. Будьте обережні - тут будуть дракони.


Ну, я роблю це просто для того, щоб очистити деякий простір TEMPORARILY в main.cpp, поки я не вирішу, як організувати файл у бібліотеки разом із належними .hpp. Чи є краща ідея, як це зробити?
Слава V

1
Правильна термінологія в C ++ - це функція-член, а не метод. У легалії C ++ немає "методів". Метод - загальний термін ОО. C ++ реалізує їх за допомогою функцій-членів.
Брайан Ніл

14

Визначення статичної функції позначать цей символ як внутрішній. Таким чином, воно не буде видно для посилання ззовні, а лише до функцій одного і того ж блоку компіляції, як правило, одного файлу.


7

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

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

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

Цей метод працює на екземплярі класу, а не на самому класі. Однак у вас може бути статичний метод, який може працювати, не маючи примірника. Іноді це використовується в заводській схемі:

Person* tom = Person::createNewPerson();

2
Мені здається, ви говорите про статичний "метод", а не про "функцію" ??
Слава V

Я припускав, що ви маєте на увазі статичні функції у класі.
Папуги

Якщо я знаю, що "методи" називаються "функціями методів" в C ++, я б це зрозуміло більше. Ну, тепер я :) Всім спасибі
Slava V

5
У C ++ немає "методів", просто функцій. Стандарт C ++ ніколи не згадує "методи", а лише "функції".
Брайан Ніл

1
@Puddle Я знаю, про що ви говорите, але в стандарті C ++ немає визначення "методу". C ++ має лише різні функції. "Метод" є загальним терміном ОО і використовується в інших мовах та неофіційно в C ++. Метод офіційно відомий як "член-функція" в C ++.
Брайан Ніл

7

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

Стандарт, ймовірно, говорить щось на кшталт:

"Кожна програма повинна містити точно одне визначення кожної нелінійної функції або об'єкта, що використовується в цій програмі; діагностика не потрібна."

Ось такий спосіб дивитися на статичні функції. Однак у C ++ це застаріле.

Крім того, в C ++ ви можете оголосити функції учасника статичними. Це здебільшого метафункції, тобто вони не описують / не змінюють поведінку / стан конкретного об'єкта, а діють на сам клас. Також це означає, що вам не потрібно створювати об'єкт для виклику функції статичного члена. Крім того, це також означає, що ви отримуєте доступ лише до статичних змінних членів зсередини такої функції.

Я додам до прикладу Папуга шаблон Singleton, який базується на такому вигляді статичної функції члена, щоб отримати / використовувати один об'єкт протягом усього життя програми.


7

Відповідь на статичну функцію залежить від мови:

1) У мовах без OOPS, таких як C, це означає, що функція доступна лише у файлі, де визначено її.

2) У мовах з OOPS, як C ++, це означає, що функцію можна викликати безпосередньо в класі, не створюючи її примірник.


Це не правда. Пояснення другого пункту стосується " статичних функцій членів " класу, а не " статичних функцій ". У C ++ функція, кваліфікована з staticфайлом, має також область файлів, як це є у C.
RobertS підтримує Моніку Челіо

0

Оскільки статична функція видно лише в цьому файлі. Насправді компілятор може зробити оптимізацію для вас, якщо ви оголосите "статичним" якусь функцію.

Ось простий приклад.

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 () під час компіляції. Оскільки нам не потрібен тест (), не має значення, визначено чи реалізовано ghost ().


0

" Що таке staticфункція " "в C? "

Почнемо з початку.

Все на основі речі, званої "зв'язок":

" Ідентифікатор, оголошений в різних сферах дії або в одному і тому ж обсязі не один раз, може бути зроблений для позначення одного і того ж об'єкта або функції процесом, який називається зв'язком. 29) Існує три види зв'язку: зовнішня, внутрішня та жодна. "

Джерело: C18, 6.2.2 / 1


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

Джерело: C18, 6.2.2 / 2


Якщо функція визначена без специфікатора класу зберігання, функція externза замовчуванням має посилання:

"Якщо в оголошенні ідентифікатора функції немає специфікатора класу зберігання, його зв'язок визначається точно так, як ніби він був оголошений зовнішнім специфікатором класу зберігання ."

Джерело: C18, 6.2.2 / 5

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

Це може бути проблемою в деяких випадках. Що робити, якщо ви хочете використовувати fe дві різні функції (визначення), але з однаковою назвою функції у двох різних контекстах (власне файловий контекст).

У C і C ++ staticкласифікатор класу зберігання, застосований до функції в області файлу (не статична функція члена класу в C ++ або функція в іншому блоці), тепер приходить на допомогу і означає, що відповідна функція видно лише всередині блок перекладу / вихідний файл, який було визначено, а не в інших TLU / файлах.

"Якщо декларація ідентифікатора області файлу для об'єкта або функції містить специфікатор класу зберігання статичний , ідентифікатор має внутрішню зв'язок. 30)"


30) Декларація функції може містити специфікатор класу зберігання, якщо воно знаходиться в області файлу; див. 6.7.1.

Джерело: C18, 6.2.2 / 3


Таким чином, staticфункція має лише сенс, iff:

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

    і

  2. Ви хочете обмежити область функції файлом, в якому визначена конкретна функція.

Якщо обидві ці вимоги не відповідають, вам не потрібно обертати голову про те, щоб кваліфікувати функцію як static.


Бічні примітки:

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.