статические переменные во встроенной функции

85

У меня есть функция, которая объявлена ​​и определена в файле заголовка. Это проблема сама по себе. Когда эта функция не встроена, каждая единица перевода, использующая этот заголовок, получает копию функции, а когда они связаны вместе, они дублируются. Я «исправил» это, сделав функцию встроенной, но боюсь, что это хрупкое решение, потому что, насколько мне известно, компилятор не гарантирует встраивание, даже если вы укажете ключевое слово «inline». Если это не так, поправьте меня.

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

Дирк Греневельд
источник

Ответы:

106

Полагаю, вы здесь что-то упускаете.

статическая функция?

Объявление функции статической сделает ее «скрытой» в ее модуле компиляции.

Имя, имеющее область пространства имен (3.3.6), имеет внутреннюю связь, если это имя

- переменная, функция или шаблон функции, который явно объявлен статическим;

3.5 / 3 - C ++ 14 (n3797)

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

3.5 / 2 - C ++ 14 (n3797)

Если вы объявите эту статическую функцию в заголовке, тогда все единицы компиляции, включая этот заголовок, будут иметь свою собственную копию функции.

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

встроенная функция?

Объявление его встроенным делает его кандидатом для встраивания (в настоящее время это не имеет большого значения для C ++, поскольку компилятор будет встроенным или нет, иногда игнорируя факт наличия или отсутствия ключевого слова inline):

Объявление функции (8.3.5, 9.3, 11.3) со встроенным спецификатором объявляет встроенную функцию. Встроенный спецификатор указывает реализации, что встроенная подстановка тела функции в точке вызова предпочтительнее обычного механизма вызова функции. Реализация не требуется для выполнения этой встроенной замены в точке вызова; однако, даже если эта встроенная подстановка опущена, все равно должны соблюдаться другие правила для встроенных функций, определенные в 7.1.2.

7.1.2 / 2 - C ++ 14 (n3797)

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

Для статических переменных, объявленных внутри, в стандарте конкретно говорится, что есть одна и только одна из них:

Статическая локальная переменная во встроенной функции extern всегда ссылается на один и тот же объект.

7.1.2 / 4 - C ++ 98 / C ++ 14 (n3797)

(функции по умолчанию являются extern, поэтому, если вы специально не отметили свою функцию как статическую, это относится к этой функции)

У этого есть преимущество "статики" (т.е. он может быть определен в заголовке) без недостатков (он существует не более одного раза, если он не встроен)

статическая локальная переменная?

Статические локальные переменные не имеют связи (на них нельзя ссылаться по имени вне их области видимости), но имеют статическую продолжительность хранения (т. Е. Глобальные, но их создание и уничтожение подчиняются определенным правилам).

статический + встроенный?

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

Ответ на дополнительный вопрос автора

Поскольку я написал вопрос, я попробовал его с Visual Studio 2008. Я попытался включить все параметры, которые заставляют VS работать в соответствии со стандартами, но возможно, что я пропустил некоторые. Вот результаты:

Когда функция просто «встроенная», есть только одна копия статической переменной.

Когда функция является «статической встроенной», существует столько копий, сколько единиц перевода.

Настоящий вопрос теперь в том, должно ли быть так, или это идиосинкразия компилятора Microsoft C ++.

Полагаю, у вас есть что-то вроде этого:

void doSomething()
{
   static int value ;
}

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

Встраивание функции ничего не изменит:

inline void doSomething()
{
   static int value ;
}

Будет только одна скрытая глобальная переменная. Тот факт, что компилятор попытается встроить код, не изменит того факта, что существует только одна глобальная скрытая переменная.

Теперь, если ваша функция объявлена ​​статической:

static void doSomething()
{
   static int value ;
}

Затем он является «частным» для каждой единицы компиляции, что означает, что каждый файл CPP, включая заголовок, в котором объявлена ​​статическая функция, будет иметь свою собственную частную копию функции, включая свою собственную частную копию глобальной скрытой переменной, то есть столько переменных, сколько есть единицы компиляции, включая заголовок.

Добавление «inline» к «статической» функции со «статической» переменной внутри:

inline static void doSomething()
{
   static int value ;
}

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

Итак, поведение VC ++ правильное, и вы ошибаетесь в истинном значении слов «встроенный» и «статический».

паэрцебал
источник
Я думаю, вам не хватает важного момента, о котором следует упомянуть, что на этапе связывания все статические переменные, объявленные во встроенной функции, будут преобразованы в одну, я ошибаюсь?
user14416
1
Нет, потому что каждая статическая переменная находится внутри своей отдельной функции: несмотря на то, что функции имеют одно и то же имя, они имеют внутреннюю связь и, следовательно, не используются в единицах перевода.
paercebal
1
@paercebal in inline void doSomething() { static int value ; }, функция имеет внешнюю связь; это нарушение ODR, если оно появляется в заголовке, включенном в два разных блока
MM
@ ММ, что ты имеешь в виду? Ваша функция - inlineэто не может нарушать ODR.
Руслан
@Ruslan, это не продолжение
MM
39

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

Подумайте на мгновение как компилятор - как могло быть иначе? Каждая единица компиляции (исходный файл) не зависит от других и может быть скомпилирована отдельно; поэтому каждый должен создать копию переменной, считая ее единственной. Компоновщик может выходить за эти границы и настраивать ссылки как для переменных, так и для функций.

Марк Рэнсом
источник
2
AFAICT, вы совершенно правы в том, что говорите здесь. Я не понимаю, почему люди не голосуют за этот ответ. Я могу только предположить, что они читают до "множества копий переменной", а затем останавливаются! :( В любом случае, жетон (+1) от меня.
Ричард Корден,
3
Когда люди спрашивают, что за компилятор, они имеют в виду компилятор + компоновщик, поскольку вы не можете запускать объектные файлы. Итак, это правильный ответ, но совершенно бессмысленный.
Эван Дарк
1
Потому что люди невежественны. Это более сложный вопрос, и все должны проводить различие в обсуждении.
Sogartar
13

Я нашел ответ Марка Рэнсома полезным - компилятор создает много копий статической переменной, но компоновщик выбирает одну и применяет ее ко всем единицам перевода.

В другом месте я нашел это:

См. [Dcl.fct.spec] / 4

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

У меня нет копии стандарта для проверки, но он соответствует моему опыту изучения сборки в VS Express 2008

Мэтт
источник
5

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

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

Примечание: этот ответ был написан в ответ на ответ, который автор отправил самому себе.

Рафаэль Сен-Пьер
источник
1
Он спрашивает о «статических переменных» во «встроенной функции», а не о переменных в статической функции.
Ричард Корден,
Мы согласны с этим, но вы правы: необходимо отредактировать ответ, чтобы вернуть ответ в контекст.
Raphaël Saint-Pierre
Я тоже с этим сталкивался . Так что это из двух? inlineзаставляет функцию быть встроенной, или можно иметь несколько копий?
Василис
@Vassilis оба правы, хотя inlineне вызывают встраивания, он просто предлагает это и допускает более одного определения (но не в одном модуле компиляции).
Raphaël Saint-Pierre
3

Поскольку я написал вопрос, я попробовал его с Visual Studio 2008. Я попытался включить все параметры, которые заставляют VS работать в соответствии со стандартами, но возможно, что я пропустил некоторые. Вот результаты:

Когда функция просто «встроенная», есть только одна копия статической переменной.

Когда функция является «статической встроенной», существует столько копий, сколько единиц перевода.

Настоящий вопрос сейчас в том, должно ли быть так, или это идеосинкразия компилятора Microsoft C ++.

Дирк Греневельд
источник
1
«Когда функция является статической встроенной», - в исходном сообщении ничего не говорилось об этом. Вы должны ожидать других результатов, потому что static для функции имеет другое значение, чем static для переменной. static в функции означает, что другие единицы перевода не увидят это определение.
Программист Windows,
не уверен в ваших настройках, но в этом случае компилятор работает правильно. Однако вы можете захотеть включить модульный тест, если в будущем вы столкнетесь с каким-либо несовместимым компилятором.
Роберт Гулд,
-1

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

Программист Windows
источник
-2

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

Джейсон Этеридж
источник
-2

Помимо каких-либо проблем с дизайном, это все может означать, поскольку вы уже застряли в этом, вы должны использовать static в этом случае, а не inline. Таким образом, у всех одни и те же переменные. (Статическая функция)

Роберт Гулд
источник
-2

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


источник