статические переменные в встроенной функции - PullRequest
69 голосов
/ 09 октября 2008

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

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

Ответы [ 9 ]

90 голосов
/ 10 октября 2008

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

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

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

Имя, имеющее область имен (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)

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

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

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

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» к «static» функции со «static» переменной внутри:

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

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

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

36 голосов
/ 09 октября 2008

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

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

11 голосов
/ 06 сентября 2009

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

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

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

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

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

5 голосов
/ 09 октября 2008

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

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

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

3 голосов
/ 09 октября 2008

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

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

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

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

0 голосов
/ 29 августа 2009

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

0 голосов
/ 09 октября 2008

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

0 голосов
/ 09 октября 2008

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

0 голосов
/ 09 октября 2008

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

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