Что означает «const static» в C и C ++?

117
const static int foo = 42;

Я видел это в каком-то коде здесь, на StackOverflow, и я не мог понять, что он делает. Затем я увидел несколько запутанных ответов на других форумах. Я предполагаю, что он используется в C, чтобы скрыть константу fooот других модулей. Это верно? Если так, зачем кому-то использовать его в контексте C ++, где вы можете просто сделать это private?

c0m4
источник

Ответы:

113

Он используется как в C, так и в C ++.

Как вы уже догадались, staticчасть ограничивает свою область действия этой единицей компиляции . Также предусмотрена статическая инициализация. constпросто говорит компилятору не позволять никому его изменять. Эта переменная помещается либо в сегмент данных, либо в сегмент bss, в зависимости от архитектуры, и может находиться в памяти, помеченной только для чтения.

Все это то, как C обрабатывает эти переменные (или как C ++ обрабатывает переменные пространства имен). В C ++ отмеченный член staticиспользуется всеми экземплярами данного класса. Независимо от того, является ли она частной или нет, это не влияет на то, что одна переменная используется несколькими экземплярами. Наличие constтам предупредит вас, если какой-либо код попытается это изменить.

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

Крис Аргуин
источник
1
Исходный пример говорит о «частной переменной». Следовательно, это мебмер, и статика не влияет на связь. Вы должны удалить «статическая часть ограничивает область действия этим файлом».
Ричард Корден,
«Специальный раздел» известен как сегмент данных, который он разделяет со всеми другими глобальными переменными, такими как явные «строки» и глобальные массивы. Это противоположно сегменту кода.
spoulson
@ Ричард - почему ты думаешь, что он член класса? В вопросе нет ничего, что говорило бы об этом. Если это член класса, то вы правы, но если это просто переменная, объявленная в глобальной области видимости, тогда Крис прав.
Грэм Перроу,
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 за указание на то, что static const совпадает с const в области пространства имен.
Plumenator 01
Фактически нет никакой разницы между помещением в "foo.h" или в "foo.cc", поскольку .h просто включается при компиляции единицы перевода.
Михаил
2
@ Михаил: Вы правы. Существует предположение, что заголовок может быть включен в несколько единиц 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 упоминается без использования static "для него также будет выделено пространство в области инициализированных данных". и используя static "нет необходимости выделять для нее какое-либо пространство". (откуда компилятор тогда выбирает val?), можете ли вы объяснить в терминах кучи и стека, где хранится переменная. Исправьте меня, если я ее интерпретирую неправильно .
Нихар
@ N.Nihar - область статических данных - это блок памяти фиксированного размера, который содержит все данные со статической связью. Он «выделяется» процессом загрузки программы в память. Это не часть стека или кучи.
Ферруччо
Что произойдет, если у меня есть функция, возвращающая указатель на foo? Это нарушает оптимизацию?
nw.
@nw: Да, должно быть.
Ферруччо
8

Отсутствует int. Так должно быть:

const static int foo = 42;

В C и C ++ он объявляет целочисленную константу со значением 42 в области локального файла.

Почему 42? Если вы еще не знаете (и трудно поверить, что вы не знаете), это отсылка к Ответу на жизнь, Вселенную и все остальное .

Kevin
источник
Спасибо ... теперь каждый раз ... всю оставшуюся жизнь ... когда я увижу 42, я всегда буду думать об этом. ха-ха
Инишир
Это убедительное доказательство того, что Вселенная была создана с помощью 13 пальцев (вопрос и ответ фактически совпадают в базе 13).
paxdiablo
Это мыши. По 3 пальца на каждой ступне плюс хвост - основа 13.
KeithB
На самом деле вам не нужно использовать int в объявлении, хотя записывать его - определенно хороший вкус. C по умолчанию всегда принимает типы int. Попытайся!
эфемент,
"с областью локального файла со значением 42" ?? или это для всего модуля компиляции?
aniliitb10
4

В C ++

static const int foo = 42;

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

#define foo 42

потому что это не подрывает систему безопасности типов.

paxos1977
источник
4

Ко всем отличным ответам я хочу добавить небольшую деталь:

Если Вы пишете плагины (например, библиотеки DLL или .so для загрузки в систему САПР), то static - это спасатель, который позволяет избежать конфликтов имен, подобных этому:

  1. Система САПР загружает плагин 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; - неквалифицированный указатель на квалифицированный тип объекта const

      • MyObj * const myVar; - квалифицированный указатель const на неквалифицированный тип объекта

    • Крайнее левое использование - применяется к типу объекта, а не к переменной

      • const MyObj * myVar; - неквалифицированный указатель на квалифицированный тип объекта const

ТАКОЙ:

static NSString * const myVar; - постоянный указатель на неизменяемую строку с внутренней связью.

Отсутствие staticключевого слова сделает имя переменной глобальным и может привести к конфликту имен в приложении.

Алексей Пелех
источник
4

inlineПеременные C ++ 17

Если вы погуглили "C ++ const static", то, скорее всего, вы действительно хотите использовать встроенные переменные 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 ++ гарантирует, что адреса будут такими же. Стандартный черновик 10.1.6 C ++ 17 N4659 «Встроенный спецификатор»:

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.

До C ++ 17: extern const

До C ++ 17 и в C мы могли добиться очень похожего эффекта с помощью 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 вверх по течению .

Альтернативы только для заголовков до C ++ 17

Это не так хорошо, как externрешение, но они работают и занимают только одну ячейку памяти:

constexprФункция, потому что constexprподразумеваетinline и inline позволяет (сил) определение появляться на каждом ЕП :

constexpr int shared_inline_constexpr() { return 42; }

и я уверен, что любой достойный компилятор встроит вызов.

Вы также можете использовать переменную constили constexprstatic, как в:

#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 ++ static const и const.

Есть ли способ полностью встроить его?

TODO: есть ли способ полностью встроить переменную без использования памяти?

Очень похоже на то, что делает препроцессор.

Это потребует как-то:

  • запрещение или обнаружение, если адрес переменной взят
  • добавить эту информацию в объектные файлы ELF и позволить LTO оптимизировать ее

Связанный:

Протестировано в Ubuntu 18.10, GCC 8.2.0.

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
2

Да, он скрывает переменную в модуле от других модулей. В C ++ я использую его, когда мне не нужно / не нужно изменять файл .h, который вызовет ненужную перестройку других файлов. Кроме того, я сначала поставил статику:

static const int foo = 42;

Кроме того, в зависимости от его использования, компилятор даже не будет выделять для него хранилище, а просто «встроит» значение, в котором оно используется. Без статики компилятор не может предположить, что он не используется где-либо еще и не может быть встроен.

Джим Бак
источник
2

Это глобальная константа, видимая / доступная только в модуле компиляции (файл .cpp). Кстати, использование static для этой цели устарело. Лучше использовать анонимное пространство имен и перечисление:

namespace
{
  enum
  {
     foo = 42
  };
}
Roskoto
источник
это вынудит компилятор не рассматривать foo как константу, что затрудняет оптимизацию.
Нильс Пипенбринк,
значения перечислений всегда постоянны, поэтому я не понимаю, как это помешает какой-либо оптимизации
Роското
ах - правда .. моя ошибка. думал, что вы использовали простую int-переменную.
Нильс Пипенбринк,
Роското, я не понимаю, какая польза от enumэтого в данном контексте. Хотите уточнить? Такие enums, как правило , используется только для предотвращения компилятору выделения любого пространства для значения (хотя современные компиляторы не нужен этот enumхак для него) и предотвратить создание указателей на значения.
Конрад Рудольф
Конрад, В чем именно проблема использования перечисления в данном случае? Перечисления используются, когда вам нужны постоянные целые числа, что и есть.
Роското,
1

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

YRP
источник