статичні змінні у вбудованій функції


83

У мене є функція, яка оголошена та визначена у файлі заголовка. Це проблема сама по собі. Коли ця функція не вбудована, кожна одиниця перекладу, яка використовує цей заголовок, отримує копію функції, а коли вони зв’язані між собою, вони дублюються. Я "виправив" це, зробивши функцію вбудованою, але я боюся, що це крихке рішення, оскільки, наскільки мені відомо, компілятор не гарантує вбудовування, навіть коли ви вказуєте ключове слово "inline". Якщо це неправда, будь ласка, виправте мене.

У будь-якому випадку, справжнє питання полягає в тому, що відбувається зі статичними змінними всередині цієї функції? Скільки копій я отримую?

Відповіді:


105

Думаю, ти чогось тут пропустив.

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

Оголошення функції статичною зробить її "прихованою" в її модулі компіляції.

Ім'я, що має область простору імен (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)

(функції за замовчуванням є зовнішніми, тому, якщо ви спеціально не позначите свою функцію як статичну, це стосується цієї функції)

Це має перевагу "статичний" (тобто його можна визначити в заголовку) без вад (він існує щонайбільше один раз, якщо він не вбудований)

статична локальна змінна?

Статичні локальні змінні не мають зв’язку (їх не можна називати іменами поза їх сферою дії), але має статичну тривалість зберігання (тобто вона є глобальною, але її побудова та руйнування підпорядковуються певним правилам).

статичний + вбудований?

Потім змішування вбудованого та статичного призведе до наслідків, які ви описали (навіть якщо функція вбудована, статичної змінної всередині не буде, і ви закінчите стільки статичних змінних, скільки у вас є одиниць компіляції, включаючи визначення ваших статичних функцій ).

Відповідь на додаткове запитання автора

Оскільки я написав запитання, я спробував його за допомогою 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 ;
}

має такий самий результат, як не додавання цього вбудованого ключового слова, що стосується статичної змінної всередині.

Отже, поведінка VC ++ правильна, і ви помилково сприймаєте справжнє значення "вбудований" та "статичний".


Я думаю, вам не вистачає важливого моменту, щоб згадати, що на етапі зв’язування всі ті статичні змінні, оголошені у вбудованій функції, будуть вирішені до одного, я помиляюся?
user14416

1
Ні, оскільки кожна статична змінна знаходиться всередині своєї окремої функції: Незважаючи на те, що функції мають однакову назву, вони мають внутрішній зв’язок і, отже, не є спільними для одиниць перекладу.
paercebal

1
@paercebal in inline void doSomething() { static int value ; }, функція має зовнішній зв'язок; це порушення ОДР, якщо воно з’являється у заголовку, що входить до складу двох різних одиниць
MM

@MM що ти маєш на увазі? Ваша функція inline: вона не може порушувати ODR.
Руслан

@Ruslan that is non-sequitur
MM

39

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

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


2
AFAICT, ти абсолютно правильно говориш тут. Я не розумію, чому люди не голосують за цю відповідь. Єдине припущення, що вони читають аж "багато копій змінної", а потім зупиняються! :( У будь-якому випадку знак від мене (+1).
Річард Корден,

3
Коли люди запитують про те, що означає компілятор, вони мають на увазі компілятор + лінкер, оскільки ви не можете запускати об’єктні файли. Тож ця відповідь правильна, але абсолютно безглузда.
Еван Дарк

1
Тому що люди невігласи. Це питання є більш досконалим, і всі вони мають зробити різницю в дискусії.
Согартар,

13

Я знайшов відповідь Марка Ренсома корисною - компілятор створює багато копій статичної змінної, але компонувальник вибирає одну та застосовує її до всіх одиниць перекладу.

В іншому місці я знайшов це:

Див. [Dcl.fct.spec] / 4

[..] Вбудована функція із зовнішнім зв'язком повинна мати однакову адресу у всіх одиницях перекладу. Статична локальна змінна у вбудованій зовнішній функції завжди посилається на один і той же об'єкт. Рядовий літерал у вбудованій функції extern - це один і той же об’єкт у різних одиницях перекладу.

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


5

Це має бути так. "static" повідомляє компілятору, що ви хочете, щоб функція була локальною для одиниці компіляції, тому ви хочете одну копію на одиницю компіляції та одну копію статичних змінних на екземпляр функції.

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

Примітка: ця відповідь була написана у відповідь на відповідь, яку оригінальний плакат розмістив собі.


1
Він запитує про "статичні змінні" у "вбудованій функції", а не про змінні у статичній функції.
Річард Корден,

Ми погоджуємось із цим, але ви маєте рацію: потрібна редакція, щоб відповідь повернута в контекст.
Рафаель Сен-П'єр

Я теж зіткнувся з цим . То хто з двох це? inlineспричиняє вбудовування функції або це нормально мати кілька копій?
Василіс,

@Vassilis обидва мають рацію, хоча inlineце не спричиняє вбудовування, воно просто підказує це і допускає більше одного визначення (але не в одній одиниці компіляції).
Рафаель Сен-П'єр

3

Оскільки я написав запитання, я спробував його за допомогою Visual Studio 2008. Я спробував увімкнути всі параметри, які змушують VS діяти відповідно до стандартів, але цілком можливо, що я їх пропустив. Ось результати:

Коли функція просто "вбудована", існує лише одна копія статичної змінної.

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

Насправді справжнє питання полягає в тому, чи все має бути так, чи це ідеосинкратія компілятора Microsoft C ++.


1
"Коли функція" статична вбудована "," - Ваша початкова публікація нічого не говорила про це. Ви повинні очікувати різних результатів, оскільки статичний для функції має інше значення, ніж статичний для змінної. static у функції означає, що інші перекладацькі одиниці не бачитимуть цього визначення.
Програміст для Windows

не впевнений у ваших налаштуваннях, але в цьому випадку компілятор працює коректно. Однак, можливо, ви захочете включити модульний тест, якщо ви зіткнетеся з якимсь невідповідним компілятором по дорозі.
Роберт Гулд,

-1

Вбудовування означає, що виконуваний код (інструкції) вставляється в код викличної функції. Компілятор може зробити це незалежно від того, чи ви про це просили. Це не впливає на змінні (дані), оголошені у функції.


-1

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


-2

Я вірю, що ви отримаєте по одному на одиницю перекладу. Ви фактично отримали багато версій цієї функції (та її оголошену статичну змінну), по одній для кожного модуля перекладу, що включає заголовок.


-2

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.