it-swarm.com.ru

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

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

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

69
Dirk Groeneveld

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

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

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

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

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

3,5/3 - С ++ 14 (n3797)

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

3,5/2 - С ++ 14 (n3797)

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

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

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

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

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

7.1.2/2 - C++ 14 (n3797)

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

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

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

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

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

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

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

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

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

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

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

С тех пор как я написал вопрос, я попробовал его в 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++ правильное, и вы ошибаетесь в истинном значении слов "inline" и "static".

90
paercebal

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

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

36
Mark Ransom

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

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

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

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

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

11
Matt

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

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

Примечание: этот ответ был написан в ответ на ответ оригинального постера, размещенного для него.

5
Raphaël Saint-Pierre

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

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

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

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

3
Dirk Groeneveld