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

StackOverflow https://stackoverflow.com/questions/92546

  •  01-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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


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

Другие советы

А 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- Если объявление идентификатора для функции не имеет спецификатора класса хранения, его связь определяется точно так же, как если бы он был объявлен с внешним спецификатором класса хранения.Если объявление идентификатора для объекта имеет объем файлов и отсутствие спецификатора класса хранилища, его связь является внешней.

Из проекта Спецификация С++:

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

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

тест.ч:

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

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

ааа

const int i = 10;

один.cpp

#include "a.h"

func()
{
   cout << i;
}

два.cpp

#include "a.h"

func1()
{
   cout << i;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top