Объявления переменных в файлах заголовков - статические или нет?
При реорганизации некоторых #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, но при условии, что заголовок включен в две отдельные библиотеки, и приложение связано, будут включены два экземпляра.