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


91

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

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

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

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


Ответы:


107

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

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


Примечание редактора: в C ++ constобъекты, в объявлении которых staticнет externключевых слов или, являются неявными static.


Я фанат последнего предложения, невероятно полезно. Я не голосовал за ответ, потому что 42 лучше. редактировать: грамматика
RealDeal_EE'18,

«Статическое значение означает, что будет создана одна копия VAL для каждого исходного файла, в который он включен». Похоже, это означает, что было бы две копии VAL, если бы два исходных файла включали файл заголовка. Я надеюсь, что это неправда, и что всегда существует единственный экземпляр VAL, независимо от того, сколько файлов включает заголовок.
Brent212

4
@ Brent212 Компилятор не знает, было ли объявление / определение взято из файла заголовка или из основного файла. Значит, вы напрасно надеетесь. Будет две копии VAL, если кто-то поступит глупо и поместит статическое определение в файл заголовка, и оно будет включено в два источника.
Justsalt

1
Значения const имеют внутреннюю связь в C ++
adrianN

112

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

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

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

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

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

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

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

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

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

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


47

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

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


5
Спасибо за пример!
Кайрол

Интересно , если бы TESTбыли const, если LTO сможет оптимизировать его в одну ячейку памяти. Но -O3 -fltoиз GCC 8.1 не вышло.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

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

6

constпеременные в C ++ имеют внутреннюю связь. Итак, использование не staticимеет никакого эффекта.

ах

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

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


2
Что ж, использование staticимеет эффект, который четко сигнализирует о намерении и осведомленности о том, что кодируете, что никогда не бывает плохим. Для меня это похоже на включение virtualпри переопределении: нам не нужно, но все выглядит намного более интуитивно понятным - и согласуется с другими декларациями - когда мы это делаем.
underscore_d

Вы можете получить множественные ошибки определения в C. Это неопределенное поведение, не требующее диагностики
MM

5

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

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

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

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


Преимущество «статического» здесь заключается в том, что в противном случае вы объявляете несколько глобальных объектов с одним и тем же именем, по одному для каждого модуля, включающего заголовок. Если компоновщик не жалуется, то это только потому, что он прикусывает язык и вежлив.

В этом случае, из-за const, staticподразумевается и, следовательно, необязательно. Следствием этого является отсутствие подверженности множественным ошибкам определения, как утверждал Майк Ф.
underscore_d


2

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

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

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

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

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

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

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

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

Примечание. В абстрактной машине есть одна копия VAL в каждой отдельной единице перевода, которая включает заголовок. На практике компоновщик может решить их все равно объединить, а компилятор может сначала оптимизировать некоторые или все из них.
MM

1

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

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

Если исключить static, по умолчанию будет установлена внешняя связь . Опять же , вы были спасены сопзИ Несса - компилятор может оптимизировать / рядное использование. Если вы отбросите константу, вы получите ошибку с несколькими определенными символами во время ссылки.


Я считаю, что компилятор должен выделять место для const int во всех случаях, поскольку другой модуль всегда может сказать «extern const int any; something (& something);»

1

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


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

1

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

(7.11.6 C ++ 2003 и в Apexndix C есть образцы)

Пример сравнения источников компиляции / ссылки как программы 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

Там является смысл еще в том числе static. Он сигнализирует о намерении / осведомленности о том, что делает программист, и поддерживает паритет с другими типами объявлений (и, fwiw, C), в которых отсутствует неявное static. Это похоже на включение virtualи в последнее время overrideв объявления переопределяющих функций - не обязательно, но гораздо более самодокументируется и, в случае последнего, способствует статическому анализу.
underscore_d

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

«Так что, если вы используете C ++, то не имеет смысла, какую конструкцию использовать ...» - Хм ... Я только что скомпилировал проект, который использовал constтолько переменную в заголовке с g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). В результате получилось около 150 многократно определенных символов (по одному для каждой единицы перевода, в которую был включен заголовок). Я думаю , что нам нужны либо static, inlineили анонимные / неназванные имена , чтобы избежать внешних связей.
jww

Я попробовал baby-example с gcc-5.4 с объявлением const intвнутри области пространства имен и в глобальном пространстве имен. И он скомпилирован и следует правилу «Объекты, объявленные как const и явно не объявленные как extern, имеют внутреннюю привязку». «... Может быть, в проекте по какой-то причине этот заголовок включен в скомпилированные исходники C, где правила совершенно другие.
bruziuz

@jww Я загрузил пример с проблемой связывания для C и без проблем для C ++
bruziuz

0

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

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


-2

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


если предположить, что под «библиотеками» вы подразумеваете единицы перевода , то нет, include-guard абсолютно ничего не делает для предотвращения множественных определений, поскольку они защищают только от повторных включений в пределах одной единицы перевода . так что они ничего не делают, чтобы сделать static«менее важным». и даже с обоими способами вы можете получить несколько внутренне связанных определений, что, вероятно, не предназначено.
underscore_d
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.