Що означає "const статична" в C і C ++?


117
const static int foo = 42;

Я бачив це в деякому коді тут на StackOverflow, і я не міг зрозуміти, що це робить. Потім я побачив кілька заплутаних відповідей на інших форумах. Я найкраще здогадуюсь, що він використовується в C для приховування константи fooвід інших модулів. Це правильно? Якщо так, навіщо хтось використовувати його в контексті C ++, де ви можете просто зробити це private?

Відповіді:


113

Він використовує як C, так і C ++.

Як ви здогадалися, staticчастина обмежує сферу його дії на цю компіляційну одиницю . Він також передбачає статичну ініціалізацію. constпросто каже компілятору нікому не дозволяти його змінювати. Ця змінна або поміщається в сегмент даних або bss залежно від архітектури і може бути в пам'яті, позначеної лише для читання.

Все це, як C поводиться з цими змінними (або як C ++ ставиться до змінних простору імен). У C ++ позначений член staticподіляється всіма примірниками даного класу. Незалежно від того, приватна чи ні, це не впливає на те, що одну змінну поділяють декілька примірників. Наявність constна ньому попередить вас, чи якийсь код намагатиметься це змінити.

Якби він був суто приватним, то кожен екземпляр класу отримав би свою версію (незважаючи на оптимізатор).


1
Оригінальний приклад - це "приватна змінна". Тому це мебмер і статика не впливає на зв'язок. Вам слід видалити "статична частина обмежує її обсяг для цього файлу".
Річард Корден

"Спеціальний розділ" відомий як сегмент даних, яким він ділиться з усіма іншими глобальними змінними, такими як явні "рядки" та глобальні масиви. Це протилежно кодовому сегменту.
spoulson

@Richard - що змушує тебе думати, що це член класу? У питанні немає нічого, що б це сказало. Якщо це член класу, то ви маєте рацію, але якщо це просто змінна заявлена ​​в глобальному масштабі, то Кріс має рацію.
Graeme Perrow

1
В оригінальному плакаті згадувалося про приватне як можливе краще рішення, але не як про оригінальну проблему.
Кріс Аргейн

@Graeme, гаразд, це не є "точно" членом - однак ця відповідь робить заяви, які стосуються лише членів простору імен, і ці твердження неправильні для змінних членів. Враховуючи кількість голосів, ця помилка може збентежити когось не дуже знайомого з мовою - це слід виправити.
Річард Корден

212

Багато людей дали основний відповідь , але ніхто не вказав, що в C ++ по constзамовчуванням , щоб staticна namespaceрівні (а деякі дали невірну інформацію). Див. Стандартний розділ 3.5.3 C ++ 98.

Спочатку деякі відомості:

Блок перекладу: вихідний файл після того, як попередній процесор (рекурсивно) включив усі його файли, що включають.

Статична зв'язок: Символ доступний лише в його одиниці перекладу.

Зовнішня посилання: Символ доступний для інших перекладацьких одиниць.

В namespace рівні

Сюди входить глобальна область імен, також глобальних змінних .

static const int sci = 0; // sci is explicitly static
const int ci = 1;         // ci is implicitly static
extern const int eci = 2; // eci is explicitly extern
extern int ei = 3;        // ei is explicitly extern
int i = 4;                // i is implicitly extern
static int si = 5;        // si is explicitly static

На рівні функцій

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

Наclass рівні

staticозначає, що значення поділяється між усіма примірниками класу і constозначає, що воно не змінюється.


2
На функціональному рівні: статика НЕ reduntant з сопзЬ, вони можуть вести себе по- різному в const int *foo(int x) {const int b=x;return &b};залежностіconst int *foo(int x) {static const int b=x;return &b};
Hanczar

1
Питання стосується як C, так і C ++, тому вам слід включити примітку про те, constщо має на увазі лише staticв останньому.
Микола Рухе

@Motti: Чудова відповідь. Не могли б ви пояснити, що робить те, що є зайвим на рівні функції? Ви говорите, constдекларація також має на увазі staticтам? Як у випадку, якщо ви відкинете constта зміните значення, всі значення будуть змінені?
Cookie

1
@Motti constне передбачає статичного на функціональному рівні, це був би кошмар паралельності (const! = Постійний вираз), все на рівні функції неявно auto. Оскільки це питання позначене також [c], я повинен зазначити, що глобальний рівень const intнеявно вказаний externу C. Однак правила, які ви маєте тут, чудово описують C ++.
Райан Хайнін

1
І в C ++, у всіх трьох випадках, staticвказується, що змінна має статичну тривалість (існує лише одна копія, яка триває від початку програми до її закінчення), і має внутрішній / статичний зв'язок, якщо не вказано інше (це перекрито функцією зв'язок для локальних статичних змінних або зв'язок класу для статичних членів). Основні відмінності полягають у тому, що це має на увазі в кожній ситуації, коли staticце дійсно.
Час Джастіна - Відновіть Моніку

45

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

Сфера застосування імен

// foo.h
static const int i = 0;

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

// foo.cc
static const int i = 0;

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

Варто зазначити, що наступна декларація:

const int i1 = 0;

це точно так же , як static const int i = 0. Змінна в просторі імен, оголошена з, constа не явно декларована з extern, неявно статична. Якщо ви задумаєтесь над цим, комітет C ++ мав намір дозволити оголошення constзмінних у файлах заголовків, не потребуючи завжди staticключового слова, щоб уникнути порушення ODR.

Область застосування класу

class A {
public:
  static const int i = 0;
};

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

// a.h
class A {
public:
  static const int i = 0;
};

// a.cc
#include "a.h"
const int A::i;            // Definition so that we can take the address

2
+1 для вказівки, що статичний const такий самий, як просто const в області простору імен.
Плуменатор

Фактично немає різниці між розміщенням у "foo.h" або у "foo.cc", оскільки .h просто включається при складанні блоку перекладу.
Михайло

2
@Mikhail: Ти маєш рацію. Існує припущення, що заголовок може бути включений до декількох TU, тому було корисно поговорити про це окремо.
Річард Корден

24

Це невелика космічна оптимізація.

Коли ти кажеш

const int foo = 42;

Ви не визначаєте константу, а створюєте змінну, доступну лише для читання. Компілятор досить розумний, щоб використовувати 42, коли він бачить foo, але він також виділить простір в ініціалізованій області даних для нього. Це робиться тому, що, як визначено, foo має зовнішні зв'язки. Інша одиниця компіляції може сказати:

extern const int foo;

Щоб отримати доступ до його вартості. Це не є хорошою практикою, оскільки цей компілятор не має поняття, яке значення foo. Він просто знає, що це const int і повинен перезавантажувати значення з пам'яті кожного разу, коли воно використовується.

Тепер, заявивши, що це статично:

static const int foo = 42;

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

Слід також зазначити, що в C ++ кращим способом запобігти виходу імен з поточного блоку компіляції є використання анонімного простору імен:

namespace {
    const int foo = 42; // same as static definition above
}

1
, u згаданий без використання статичного "він також виділить простір в ініціалізованій області даних для нього". і, використовуючи статичний "не потрібно виділяти для цього жодного місця". (звідки компілятор вибирає val?), ви можете пояснити терміном купи та стеки, де зберігається змінна. Виправте мене, якщо я інтерпретую її неправильно.
Ніхар

@ N.Nihar - область статичних даних - це фрагмент пам'яті фіксованого розміру, який містить усі дані, що мають статичну зв'язок. Він "виділяється" процесом завантаження програми в пам'ять. Це не частина стека чи купи.
Ферруччо

Що станеться, якщо у мене функція поверне вказівник на foo? Це порушує оптимізацію?
nw.

@nw: Так, доведеться.
Ферруччо

8

У ньому відсутній "int". Вона повинна бути:

const static int foo = 42;

У C і C ++ він оголошує цілу константу з локальним обсягом файлу значення 42.

Чому 42? Якщо ви ще не знаєте (і важко повірити, що не знаєте), це посилання на відповідь на життя, Всесвіт та все .


Дякую ... тепер кожного разу ... протягом усього мого життя ... коли я бачу 42, я завжди буду робити це щодо цього. ха-ха
Inisheer

Це підтверджує те, що Всесвіт був створений, будучи з 13 пальцями (питання та відповіді насправді відповідають базі 13).
paxdiablo

Його миші. 3 пальці на кожній стопі, плюс хвіст дає вам базу 13.
KeithB

Насправді вам не потрібен "int" в декларації, хоча це, безумовно, хороший смак, щоб виписати це. C завжди передбачає типи 'int' за замовчуванням. Спробуй це!
ефемієнт

"з локальним діапазоном файлу значення 42" ?? чи це для цілого підрозділу компіляції?
aniliitb10

4

В C ++,

static const int foo = 42;

є кращим способом визначення та використання констант. Тобто це скоріше використовувати

#define foo 42

тому що вона не підриває систему безпеки типу.


4

До всіх чудових відповідей хочу додати невелику деталь:

Якщо ви пишете плагіни (наприклад, бібліотеки DLL або .so для завантаження системою CAD), то статичний спосіб збереження життя, який дозволяє уникнути зіткнень імен, як цей:

  1. Система CAD завантажує плагін A, який має "const int foo = 42;" в цьому.
  2. Система завантажує плагін B, який має "const int foo = 23;" в цьому.
  3. В результаті плагін B використовуватиме значення 42 для foo, оскільки завантажувач плагінів зрозуміє, що вже існує "foo" із зовнішнім зв'язком.

Ще гірше: крок 3 може поводитися по-різному залежно від оптимізації компілятора, механізму завантаження плагінів тощо.

У мене виникла ця проблема один раз з двома допоміжними функціями (однойменною назвою, різною поведінкою) у двох плагінах. Оголошення їх статичними вирішило проблему.


Щось здалося дивним у зіткненнях імен між двома плагінами, що змусило мене вивчити карту посилань на одну з моїх численних DLL-файлів, яка визначає m_hDfltHeap як ручку із зовнішнім зв’язком. Впевнений, що там весь світ може бачити та використовувати, вказаний на карті зв’язку як _m_hDfltHeap. Я забув усе про цей фактоїд.
Девід А. Грей

4

Відповідно до специфікації C99 / GNU99:

  • static

    • є специфікатором класу зберігання

    • Об'єкти області файлу за замовчуванням мають зовнішню зв'язок

    • Об'єкти області файлового рівня зі статичним специфікатором мають внутрішню зв'язок
  • const

    • є класифікатором типу (є частиною типу)

    • ключове слово, застосоване до безпосереднього лівого екземпляра - тобто

      • MyObj const * myVar; - некваліфікований покажчик для встановлення кваліфікованого типу об'єкта

      • MyObj * const myVar; - const кваліфікований покажчик на некваліфікований тип об'єкта

    • Найменше використання - застосовується до типу об'єкта, а не змінної

      • const MyObj * myVar; - некваліфікований покажчик для встановлення кваліфікованого типу об'єкта

ЦЕ:

static NSString * const myVar; - постійний вказівник на незмінний рядок із внутрішнім зв’язком.

Відсутність staticключового слова зробить ім’я змінної глобальним і може призвести до конфліктів імен у програмі.


4

C ++ 17 inlineзмінних

Якщо ви ввімкнули "C ++ const statik", то це дуже ймовірно, що ви дійсно хочете використовувати це C ++ 17 вбудовані змінні .

Ця дивовижна функція C ++ 17 дозволяє нам:

  • зручно використовувати лише одну адресу пам'яті для кожної константи
  • зберігати його як constexpr: Як оголосити constexpr extern?
  • зробіть це в одному рядку з одного заголовка

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Складіть і запустіть:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub вище за течією .

Дивіться також: Як працюють вбудовані змінні?

Стандарт C ++ для вбудованих змінних

Стандарт C ++ гарантує, що адреси будуть однаковими. C ++ 17 N4659 стандартний проект 10.1.6 "Вбудований специфікатор":

6 Вбудована функція або змінна із зовнішнім зв'язком має однакову адресу у всіх одиницях перекладу.

cppreference https://en.cppreference.com/w/cpp/language/inline пояснює, що якщо staticне вказано, то воно має зовнішню зв'язок.

Вбудована змінна версія GCC

Ми можемо спостерігати, як це реалізується за допомогою:

nm main.o notmain.o

який містить:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

і man nmговорить про u:

"u" Символ - це унікальний глобальний символ. Це розширення GNU до стандартного набору прив'язок символу ELF. Для такого символу динамічний лінкер переконається, що у всьому процесі є лише один символ із цим іменем та типом у використанні.

тому ми бачимо, що для цього є виділене розширення ELF.

Pre-C ++ 17: extern const

До C ++ 17 та C, ми можемо досягти дуже подібного ефекту за допомогою a extern const , що призведе до того, що буде використано єдине місце пам'яті.

Мінуси inline:

  • неможливо зробити змінну за constexprдопомогою цієї методики, inlineдозволяє лише : Як оголосити constexpr extern?
  • він менш елегантний, оскільки вам доведеться оголошувати та визначати змінну окремо у файлі заголовка та cpp

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub вище за течією .

Замінники заголовка Pre-C ++ 17 лише

Це не так добре, як externрішення, але вони працюють і займають лише одне місце пам'яті:

constexprФункція, тому що constexprмає на увазіinline і inline дозволяє (сил) визначення з'являтися на кожному ЕП :

constexpr int shared_inline_constexpr() { return 42; }

і я маю надію, що будь-який пристойний компілятор вкладе в дзвінок.

Ви також можете використовувати constабо constexprстатичну змінну, як у:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

але ви не можете робити такі речі, як прийняття його адреси, інакше він стає odr-використаний, див. також: Визначення членів статичних даних constexpr

С

У C ситуація така ж, як у C ++ до C ++ 17, я завантажив приклад за адресою: Що означає "статичний" у C?

Єдина відмінність полягає в тому, що в C ++, constмається staticна увазі для глобальних, але це не в C: C ++ семантиці `статичного const` проти` const`

Будь-який спосіб його повністю накреслити?

TODO: Чи є спосіб повністю вбудувати змінну, не використовуючи взагалі ніякої пам'яті?

Наче схоже на те, що робить препроцесор.

Для цього потрібно якось:

  • заборона або виявлення, чи взята адреса змінної
  • додайте цю інформацію до об’єктних файлів ELF, і дозвольте LTO оптимізувати її

Пов'язані:

Випробувано в Ubuntu 18.10, GCC 8.2.0.


2

Так, він приховує змінну в модулі від інших модулів. У C ++ я використовую його, коли мені не хочеться / не потрібно змінювати .h-файл, що спричинить непотрібну перебудову інших файлів. Також я ставлю статику першим:

static const int foo = 42;

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


2

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

namespace
{
  enum
  {
     foo = 42
  };
}

це змусить компілятора не сприймати foo як постійну і як такий заважає оптимізації.
Нілс Піпенбрінк

Значення перерахунків завжди постійні, тому я не бачу, як це буде заважати оптимізаціям
Роското,

ах - правда .. моя помилка. Ви думали, що ви використовували просту int-змінну.
Нілс Піпенбрінк

Роското, мені не ясно, яку користь enumмає в цьому контексті. Хочете допрацювати? Такі enumsзазвичай використовуються лише для того, щоб компілятор не міг виділити будь-який простір для значення (хоча сучасним компіляторам для цього не потрібен цей enumзлом) та для запобігання створенню покажчиків на значення.
Конрад Рудольф

Конрад, Яку саме проблему ти бачиш у використанні енту в цьому випадку? Переліки використовуються, коли потрібні постійні вставки, що саме так.
Роското

1

Зробити його приватним все одно означатиме, що він з’являється у заголовку. Я схильний використовувати «найслабший» спосіб, який працює. Дивіться цю класичну статтю Скотта Майєрса: http://www.ddj.com/cpp/184401197 (мова йде про функції, але їх можна застосувати і тут).

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