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

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

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

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

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

Ответы [ 12 ]

106 голосов
/ 18 сентября 2008

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

100 голосов
/ 18 сентября 2008

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

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


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

45 голосов
/ 18 сентября 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

6 голосов
/ 27 апреля 2010

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

5 голосов
/ 18 сентября 2008

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

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

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

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

2 голосов
/ 29 сентября 2013

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

2 голосов
/ 19 сентября 2008

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

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

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

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

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

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

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

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

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

1 голос
/ 23 января 2015

const переменные по умолчанию являются статическими в C ++, но внешними 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
1 голос
/ 18 сентября 2008

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...