Что означает "const static" в C и С++?

const static int foo = 42;

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

Ответ 1

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

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

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

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

Ответ 2

Многие люди дали основной ответ, но никто не указал, что в С++ const по умолчанию используется static на уровне namespace (а некоторые неверные данные). См. Раздел 3.5.3 стандарта С++ 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 означает, что оно не изменяется.

Ответ 3

Эта строка кода может фактически отображаться в нескольких разных контекстах и, как правило, ведет себя примерно одинаково, есть небольшие отличия.

Область пространства имен

// 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, неявно статична. Если вы подумали об этом, было бы желательно, чтобы комитет С++ разрешил объявлять переменные const в заголовочных файлах, не требуя при этом ключевого слова static, чтобы избежать нарушения ODR.

Класс Scope

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

Ответ 4

Это небольшая оптимизация пространства.

Когда вы говорите

const int foo = 42;

Вы не определяете константу, а создаете переменную только для чтения. Компилятор достаточно умен, чтобы использовать 42 всякий раз, когда он видит foo, но он также выделяет пространство в области инициализированных данных для него. Это делается потому, что, как определено, foo имеет внешнюю связь. Другая единица компиляции может сказать:

extern const int foo;

Чтобы получить доступ к его значению. Это не очень хорошая практика, поскольку эта единица компиляции не знает, что такое значение foo. Он просто знает, что это const int и должен перезагружать значение из памяти всякий раз, когда он используется.

Теперь, объявив, что это статично:

static const int foo = 42;

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

Я также должен отметить, что в С++ предпочтительный способ предотвратить имена экранов текущего блока компиляции заключается в использовании анонимного пространства имен:

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

Ответ 5

Это пропускает 'int'. Должно быть:

const static int foo = 42;

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

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

Ответ 6

В С++,

static const int foo = 42;

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

#define foo 42

потому что он не разрушает систему безопасности типа.

Ответ 7

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

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

  • Система САПР загружает плагин A, который имеет "const int foo = 42;" в нем.
  • Система загружает плагин B, который имеет "const int foo = 23;" в нем.
  • В результате плагин B будет использовать значение 42 для foo, так как плагин-загрузчик поймет, что есть уже "foo" с внешней связью.

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

У меня была эта проблема один раз с двумя вспомогательными функциями (одно имя, другое поведение) в двух плагинах. Объявление их статических решение проблемы.

Ответ 8

В соответствии со спецификацией C99/GNU99:

  • static

    • - спецификатор класса хранения

    • Объекты области уровня файла по умолчанию имеют внешнюю связь

    • объекты области уровня файла со статическим спецификатором имеют внутреннюю связь
  • const

    • является классификатором типа (является частью типа)

    • ключевое слово, примененное к непосредственному левому экземпляру - т.е.

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

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

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

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

ТАКИМ:

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

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

Ответ 9

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

static const int foo = 42;

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

Ответ 10

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

namespace
{
  enum
  {
     foo = 42
  };
}

Ответ 11

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

Если вы гуглили "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 upstream.

Смотрите также: Как работают встроенные переменные?

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.

До 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 upstream.

Предварительно 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++ pre C++ 17, я загрузил пример по адресу: Что такое "статический"? значит в С?

Единственное отличие состоит в том, что в C++ const подразумевает static для глобалов, но не в семантике C: C++ "static const" против "const"

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

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

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

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

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

Связанный:

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

Ответ 12

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