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

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

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

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

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


person Rob    schedule 18.09.2008    source источник
comment
связанные: stackoverflow.com/questions/177437/   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 21.12.2018


Ответы (12)


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

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


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

person Justsalt    schedule 18.09.2008
comment
Я фанат последнего предложения, невероятно полезно. Я не голосовал за ответ, потому что 42 лучше. редактировать: грамматика - person RealDeal_EE'18; 01.11.2013
comment
Статический означает, что будет создана одна копия VAL для каждого исходного файла, в который он включен. Похоже, это означает, что будет две копии VAL, если два исходных файла будут включать файл заголовка. Я надеюсь, что это неправда, и что всегда существует единственный экземпляр VAL, независимо от того, сколько файлов включает заголовок. - person Brent212; 16.07.2014
comment
@ Brent212 Компилятор не знает, откуда взялось объявление / определение: из файла заголовка или из основного файла. Значит, напрасно надеешься. Будет две копии VAL, если кто-то поступит глупо и поместит статическое определение в файл заголовка, и оно будет включено в два источника. - person Justsalt; 25.07.2014
comment
Значения const имеют внутреннюю связь в C ++ - person adrianN; 15.04.2016

Теги 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, имеют внутреннюю связь.

person bk1e    schedule 18.09.2008

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

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

person slicedlime    schedule 18.09.2008
comment
Спасибо за пример! - person Kyrol; 10.12.2013
comment
Интересно, если бы TEST был const, сможет ли LTO оптимизировать его в одну ячейку памяти. Но -O3 -flto из GCC 8.1 этого не сделал. - person Ciro Santilli 新疆再教育营六四事件ۍ 21.12.2018
comment
Это было бы незаконно - даже если оно постоянное, статическое гарантирует, что каждый экземпляр является локальным по отношению к модулю компиляции. Возможно, он мог бы встроить само постоянное значение, если бы оно использовалось как константа, но, поскольку мы берем его адрес, он должен возвращать уникальный указатель. - person slicedlime; 17.01.2019

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 (из-за внешней связи).

person Nitin    schedule 27.04.2010
comment
Что ж, использование static приводит к тому, что оно четко сигнализирует о намерении и осведомленности о том, что кодируете, что никогда не бывает плохим. Для меня это похоже на включение virtual при переопределении: мы не обязаны это делать, но когда мы это делаем, все выглядит намного более интуитивно понятным - и согласуется с другими декларациями. - person underscore_d; 25.07.2016
comment
Вы можете получить множественные ошибки определения в C. Это неопределенное поведение, не требующее диагностики. - person M.M; 15.11.2017

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

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

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

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

person Mark    schedule 18.09.2008
comment
Преимущество «статического» здесь заключается в том, что в противном случае вы объявляете несколько глобальных объектов с одним и тем же именем, по одному для каждого модуля, который включает заголовок. Если компоновщик не жалуется, то это только потому, что он прикусывает язык и проявляет вежливость. - person ; 18.09.2008
comment
В этом случае из-за const static подразумевается и, следовательно, является необязательным. Следствием этого является отсутствие подверженности множественным ошибкам определения, как утверждал Майк Ф. - person underscore_d; 25.07.2016

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

person Jan de Vos    schedule 18.09.2008

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

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

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

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

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

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

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

  • параметры отладки
  • адрес взят из файла
  • компилятор всегда выделяет память (сложные константные типы не могут быть легко встроены, поэтому становится особым случаем для базовых типов)
person itj    schedule 18.09.2008
comment
Примечание: В абстрактной машине есть одна копия VAL в каждой отдельной единице перевода, которая включает заголовок. На практике компоновщик может решить объединить их в любом случае, а компилятор может сначала оптимизировать некоторые или все из них. - person M.M; 15.11.2017

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

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

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

person Seb Rose    schedule 18.09.2008
comment
Я считаю, что компилятор должен выделять место для const int во всех случаях, поскольку другой модуль всегда может сказать extern const int что угодно; что-то (и что угодно); - person ; 18.09.2008

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

person Gajendra Kumar    schedule 29.09.2013
comment
... но это приведет к тому, что каждый исходный файл, включающий файл заголовка, будет иметь свою собственную частную копию переменной, что, вероятно, не то, что планировалось. - Из-за фиаско с порядком статической инициализации, может потребоваться наличие копии в каждой единице перевода. - person jww; 12.11.2017

Переменные 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
person bruziuz    schedule 23.01.2015
comment
Есть есть смысл в том, чтобы по-прежнему включать static. Он сигнализирует о намерении / осведомленности о том, что делает программист, и поддерживает паритет с другими типами объявлений (и, fwiw, C), в которых отсутствует неявное static. Это похоже на включение virtual, а в последнее время override в объявления переопределяющих функций - не обязательно, но гораздо более самодокументируется и, в случае последнего, способствует статическому анализу. - person underscore_d; 25.07.2016
comment
Я абсолютно согласен. например Что касается меня, то в реальной жизни я всегда пишу прямо. - person bruziuz; 25.07.2016
comment
Итак, если вы используете C ++, не имеет смысла, какую конструкцию использовать ... - Хм ... Я только что скомпилировал проект, который использовал const только для переменной в заголовке с g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). В результате получилось около 150 многократно определенных символов (по одному для каждой единицы перевода, в которую был включен заголовок). Я думаю, что нам нужно static, inline или анонимное / безымянное пространство имен, чтобы избежать внешней связи. - person jww; 12.11.2017
comment
Я пробовал baby-example с gcc-5.4 с объявлением const int внутри области пространства имен и в глобальном пространстве имен. И он скомпилирован и следует правилу. Объекты, объявленные как const, а не явно объявленные как extern, имеют внутреннюю связь ..... Может быть, в проекте по какой-то причине этот заголовок включен в скомпилированные исходники C, где правила совершенно другие. - person bruziuz; 15.11.2017
comment
@jww Я загрузил пример с проблемой связывания для C и без проблем для C ++ - person bruziuz; 15.11.2017

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

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

person Jim Buck    schedule 18.09.2008

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

person Superpolock    schedule 18.09.2008
comment
предполагая, что под библиотеками вы имеете в виду единицы перевода, тогда нет, include-guard абсолютно ничего не делает для предотвращения множественных определений, поскольку они только защищают от повторных включений внутри того же единица перевода. так что они ничего не делают, чтобы сделать static менее важным. и даже с обоими, вы можете получить несколько внутренне связанных определений, что, вероятно, не предназначено. - person underscore_d; 25.07.2016