Объявления переменных в файлах заголовков - статические или нет?

При реорганизации некоторых #defines я встретил объявления, похожие на следующие в заголовочном файле С++:

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

Вопрос в том, какая разница, если таковая будет, статичная? Обратите внимание, что множественное включение заголовков невозможно из-за классического трюка #ifndef HEADER #define HEADER #endif (если это имеет значение).

Создает ли статичность только одна копия VAL, если заголовок включен более чем одним исходным файлом?

Ответ 1

static означает, что будет создан один экземпляр VAL для каждого исходного файла, в который он включен. Но это также означает, что несколько включений не приведут к нескольким определениям VAL, которые будут сталкиваться по ссылке время. В C без static вам нужно убедиться, что только один исходный файл определен VAL, в то время как другие исходные файлы объявили его extern. Обычно это можно сделать, определив его (возможно, с инициализатором) в исходном файле и поместите объявление extern в файл заголовка.

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


Замечание редактора. В С++ объекты const, не содержащие ключевых слов static и extern в своем объявлении, неявно static.

Ответ 2

Теги static и extern для переменных с файловыми областями определяют, доступны ли они в других единицах перевода (т.е. другие файлы .c или .cpp).

  • static дает переменную внутреннюю связь, скрывая ее от других единиц перевода. Однако переменные с внутренней связью могут быть определены в нескольких единицах перевода.

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

Значение по умолчанию (если вы не укажете static или extern) - это одна из тех областей, в которых отличаются C и С++.

  • В C переменные с файловой системой по умолчанию extern (внешняя привязка). Если вы используете C, VAL - static и ANOTHER_VAL - extern.

  • В С++ переменные с файловой областью по умолчанию static (внутренняя привязка) по умолчанию, если они являются const и extern по умолчанию, если они не являются. Если вы используете С++, то VAL и ANOTHER_VAL static.

Из черновика спецификация C:

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

Из черновика Спецификация С++:

7.1.1 - Спецификаторы класса хранения [dcl.stc]... -6- Имя, объявленное в области пространства имен без спецификатора класса хранения, имеет внешнюю связь, если только оно не имеет внутренней связи из-за предыдущего объявления и при условии, что оно не объявлено как const. Объекты, объявленные const, а не явно объявленные extern, имеют внутреннюю связь.

Ответ 3

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

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

Выполнение этого дает вам этот результат:

0x446020
0x446040

Ответ 4

const переменные в С++ имеют внутреннюю связь. Таким образом, использование static не влияет.

хиджры

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

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

Ответ 5

Статическое объявление на этом уровне кода означает, что переменная отображается только в текущем блоке компиляции. Это означает, что только код внутри этого модуля увидит эту переменную.

если у вас есть файл заголовка, объявляющий переменную static, и этот заголовок включен в несколько файлов C/CPP, тогда эта переменная будет "локальной" для этих модулей. Будет N копий этой переменной для N мест, в которые включен заголовок. Они не связаны друг с другом вообще. Любой код в любом из этих исходных файлов будет ссылаться только на переменную, объявленную в этом модуле.

В данном конкретном случае ключевое слово "статические", похоже, не дает никакой пользы. Мне может быть что-то не хватает, но, похоже, это не имеет значения. Я никогда раньше не видел ничего подобного.

Что касается вложения, в этом случае переменная, скорее всего, вложена, но это только потому, что она объявила const. Компилятор может с большей вероятностью встроить статические переменные модуля, но это зависит от ситуации и скомпилированного кода. Нет никакой гарантии, что компилятор установит "статику".

Ответ 6

В книге C (бесплатно онлайн) есть глава о связи, которая более подробно объясняет значение "статического" (хотя правильный ответ уже дается в других комментариях): http://publications.gbdirect.co.uk/c_book/chapter4/linkage.html

Ответ 7

Чтобы ответить на вопрос, "создается ли статическое значение только одна копия VAL, если заголовок включен более чем одним исходным файлом?"...

НЕТ. VAL всегда будет определяться отдельно в каждом файле, который включает заголовок.

Стандарты для C и С++ действительно вызывают разницу в этом случае.

В C переменные с файловой областью по умолчанию являются внешними. Если вы используете C, VAL статичен, а ANOTHER_VAL - extern.

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

В С++ переменные с файловой областью по умолчанию статичны, если они являются константами, а extern - по умолчанию, если они не являются. Если вы используете С++, VAL и ANOTHER_VAL являются статическими.

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

  • параметры отладки
  • адрес, полученный в файле
  • компилятор всегда выделяет хранилище (сложные типы const не могут быть легко встроены, поэтому становится особым случаем для базовых типов)

Ответ 8

Вы не можете объявить статическую переменную, не определяя ее (это связано с тем, что модификаторы класса хранилища static и extern являются взаимоисключающими). Статическую переменную можно определить в файле заголовка, но это приведет к тому, что каждый исходный файл включит заголовочный файл в свою собственную копию переменной, что, вероятно, не является тем, что было предназначено.

Ответ 9

Предполагая, что эти объявления находятся в глобальном масштабе (т.е. не являются переменными-членами), тогда:

static означает "внутренняя связь". В этом случае, поскольку он объявлен const, он может быть оптимизирован/встроен компилятором. Если вы опустите const, тогда компилятор должен выделить хранилище в каждом модуле компиляции.

Отключив static, по умолчанию ссылка extern. Опять же, вы были сохранены с помощью const ness - компилятор может оптимизировать/использовать внутри строки. Если вы выйдете из const, вы получите ошибку с множественными значениями символов в момент ссылки.

Ответ 10

const переменные по умолчанию статические в С++, но extern C. Поэтому, если вы используете С++, это не значит, какую конструкцию использовать.

(7.11.6 С++ 2003 и Apexndix C имеют образцы)

Пример сравнения источников компиляции/ссылок как программы C и С++:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609

Ответ 11

Static предотвращает замену другой единицы компиляции той переменной, чтобы компилятор мог просто "встроить" значение переменной, где он используется, и не создавать для него память.

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

Ответ 12

Static не позволяет компилятору добавлять несколько экземпляров. Это становится менее важным при защите #ifndef, но при условии, что заголовок включен в две отдельные библиотеки, и приложение связано, будут включены два экземпляра.