При рефакторинге некоторых #defines я наткнулся на объявления, похожие на следующие в заголовочном файле C++:
static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;
Вопрос в том, какая разница, если таковая будет, от статики? Обратите внимание, что множественное включение заголовков невозможно из-за классического трюка #ifndef HEADER#define HEADER#endif (если это имеет значение).
Означает ли статика, что создается только одна копия VAL, если заголовок включен более чем в один исходный файл?





Статический не позволяет другому модулю компиляции извлекать эту переменную, так что компилятор может просто «встроить» значение переменной в то место, где оно используется, и не создавать для него хранилище памяти.
Во втором примере компилятор не может предположить, что какой-то другой исходный файл не будет его извлекать, поэтому он должен фактически сохранить это значение где-то в памяти.
Статический не позволяет компилятору добавлять несколько экземпляров. Это становится менее важным с защитой #ifndef, но при условии, что заголовок включен в две отдельные библиотеки, а приложение связано, будут включены два экземпляра.
если предположить, что под «библиотеками» вы подразумеваете единицы перевода, тогда нет, include-guard абсолютно ничего не делает для предотвращения множественных определений, поскольку они только защищают от повторных включений в пределах единицы трансляции одно и тоже. поэтому они ничего не делают, чтобы сделать static «менее важным». и даже с обоими, вы можете получить несколько внутренне связанных определений, что, вероятно, не предназначено.
static означает, что будет создаваться одна копия VAL для каждого исходного файла, в который он включен. Но это также означает, что несколько включений не приведут к нескольким определениям VAL, которые будут конфликтовать во время компоновки. В C без static вам нужно было бы убедиться, что только один исходный файл определил VAL, а другие исходные файлы объявили его extern. Обычно это можно сделать, определив его (возможно, с инициализатором) в исходном файле и поместив объявление extern в файл заголовка.
Переменные static на глобальном уровне видны только в их собственном исходном файле, независимо от того, попали ли они туда через включение или были в основном файле.
Примечание редактора: В C++ объекты const, в объявлении которых нет ключевых слов static или extern, неявно являются static.
Я фанат последнего предложения, невероятно полезно. Я не голосовал за ответ, потому что 42 лучше. редактировать: грамматика
«Статический означает, что будет создана одна копия VAL для каждого исходного файла, в который он включен». Похоже, это означает, что было бы две копии VAL, если бы два исходных файла включали файл заголовка. Я надеюсь, что это неправда, и что всегда есть один экземпляр VAL, независимо от того, сколько файлов включает заголовок.
@ Brent212 Компилятор не знает, откуда взялось объявление / определение: из файла заголовка или из основного файла. Значит, напрасно надеешься. Будет две копии VAL, если кто-то поступит глупо и поместит статическое определение в файл заголовка, и оно будет включено в два источника.
Значения const имеют внутреннюю связь в C++
Статическое объявление на этом уровне кода означает, что переменная видна только в текущей единице компиляции. Это означает, что только код в этом модуле увидит эту переменную.
если у вас есть файл заголовка, в котором объявлена статическая переменная, и этот заголовок включен в несколько файлов C / CPP, тогда эта переменная будет «локальной» для этих модулей. Будет N копий этой переменной для N мест, в которые включен заголовок. Они никак не связаны друг с другом. Любой код в любом из этих исходных файлов будет ссылаться только на переменную, объявленную в этом модуле.
В данном конкретном случае ключевое слово static, похоже, не приносит никакой пользы. Возможно, я что-то упускаю, но это, кажется, не имеет значения - я никогда раньше не видел ничего подобного.
Что касается встраивания, то в этом случае переменная, скорее всего, встроена, но только потому, что она объявлена как const. Компилятор мог бы с большей вероятностью будет встроить статические переменные модуля, но это зависит от ситуации и компилируемого кода. Нет никакой гарантии, что компилятор встроит «статику».
Преимущество «статического» здесь заключается в том, что в противном случае вы объявляете несколько глобальных объектов с одним и тем же именем, по одному для каждого модуля, который включает заголовок. Если компоновщик не жалуется, то это только потому, что он прикусывает язык и из вежливости.
В этом случае, из-за const, static подразумевается и, следовательно, является необязательным. Следствием этого является отсутствие подверженности множественным ошибкам определения, как утверждал Майк Ф.
Предполагая, что эти объявления находятся в глобальной области (т.е. не являются переменными-членами), тогда:
статический означает «внутренняя связь». В этом случае, поскольку он объявлен как const, он может быть оптимизирован / встроен компилятором. Если вы опустите const, компилятор должен выделить память в каждой единице компиляции.
Если опустить статический, по умолчанию будет установлено значение внешний. Опять же, вас спасла constness - компилятор может оптимизировать / встроить использование. Если вы отбросите const, вы получите ошибку многократно определенные символы во время компоновки.
Я считаю, что компилятор должен выделять место для const int во всех случаях, поскольку другой модуль всегда может сказать «extern const int any; something (& something);»
Статичность будет означать, что вы получаете одну копию для каждого файла, но, в отличие от других, это совершенно законно. Вы можете легко проверить это с помощью небольшого образца кода:
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
Спасибо за пример!
Интересно, был ли TESTconst, сможет ли LTO оптимизировать его в одну ячейку памяти. Но -O3 -flto из GCC 8.1 этого не сделал.
Это было бы незаконно - даже если оно постоянное, статическое гарантирует, что каждый экземпляр является локальным по отношению к модулю компиляции. Возможно, он мог бы встроить само постоянное значение, если бы оно использовалось как константа, но, поскольку мы берем его адрес, он должен возвращать уникальный указатель.
В книге C (бесплатно в Интернете) есть глава о связывании, в которой более подробно объясняется значение слова «статика» (хотя правильный ответ уже дан в других комментариях): http://publications.gbdirect.co.uk/c_book/chapter4/linkage.html
Теги 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 Linkages of identifiers ... -5- If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern. If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.
Из черновика Спецификация C++:
7.1.1 - Storage class specifiers [dcl.stc] ... -6- A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const. Objects declared const and not explicitly declared extern have internal linkage.
Чтобы ответить на вопрос, "статика означает, что создается только одна копия VAL, в случае, если заголовок включен более чем одним исходным файлом?" ...
НЕТ. VAL всегда будет определяться отдельно в каждом файле, который включает заголовок.
В этом случае стандарты для C и C++ действительно вызывают разницу.
In C, file-scoped variables are extern by default. If you're using C, VAL is static and ANOTHER_VAL is extern.
Обратите внимание, что современные компоновщики могут жаловаться на ANOTHER_VAL, если заголовок включен в разные файлы (одно и то же глобальное имя определено дважды), и определенно будут жаловаться, если ANOTHER_VAL был инициализирован другим значением в другом файле.
In C++, file-scoped variables are static by default if they are const, and extern by default if they are not. If you're using C++, both VAL and ANOTHER_VAL are static.
Также необходимо принять во внимание тот факт, что обе переменные обозначены как const. В идеале компилятор всегда выбирает встроенные эти переменные и не включает хранилище для них. Существует целый ряд причин, по которым можно выделить хранилище. Я могу вспомнить ...
Примечание: В абстрактной машине есть одна копия VAL в каждой отдельной единице трансляции, которая включает заголовок. На практике компоновщик может решить все равно объединить их, а компилятор может сначала оптимизировать некоторые или все из них.
Переменные 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 (из-за внешней связи).
Что ж, использование static приводит к тому, что он четко сигнализирует о намерении и осведомленности о том, что кодируете, что никогда не бывает плохим. Для меня это похоже на включение virtual при переопределении: нам это не обязательно, но когда мы это делаем, все выглядит намного более интуитивно понятным - и согласуется с другими декларациями.
Вы мог бы получаете множественную ошибку определения в C. Это неопределенное поведение, не требующее диагностики
Вы не можете объявить статическую переменную без ее определения (это потому, что модификаторы класса хранения static и extern являются взаимоисключающими). Статическая переменная может быть определена в файле заголовка, но это приведет к тому, что каждый исходный файл, который включает файл заголовка, будет иметь свою собственную частную копию переменной, что, вероятно, не то, что планировалось.
Переменные 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 в объявления переопределяющих функций - не обязательно, но гораздо более самодокументированный и, в случае последнего, способствующий статическому анализу.
Абсолютно согласен. например Что касается меня, то в реальной жизни я всегда пишу прямо.
const only on a variable in a header with g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). It resulted in about 150 multiply defined symbols (one for each translation unit the header was included). I think we need either static, inline or an anonymous/unnamed namespace to avoid the external linkage.
Я пробовал baby-example с gcc-5.4 с объявлением const int внутри области пространства имен и в глобальном пространстве имен. И он скомпилирован и следует правилу «Объекты, объявленные как const и явно не объявленные как extern, имеют внутреннюю привязку». «.... Может быть, в проекте по какой-то причине этот заголовок включен в скомпилированные исходники C, где правила совершенно другие.
@jww Я загрузил пример с проблемой связывания для C и без проблем для C++
связанные: stackoverflow.com/questions/177437/…