Чи має значення статична змінна constexpr всередині функції?


193

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

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

Чи staticнасправді щось там роблять з точки зору генерованого коду чи семантики?

Відповіді:


230

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

По-перше, зауважте, що staticі constexprабсолютно незалежні один від одного. staticвизначає термін експлуатації об'єкта під час виконання; constexprвказує, що об'єкт повинен бути доступний під час компіляції. Компіляція та виконання є непересічними та суперечливими як у часі, так і в просторі. Тож як тільки програма складена, constexprвона вже не актуальна.

Кожна змінна оголошена constexprнеявно , constале constі staticмайже ортогональні (для взаємодії з виключенням static constцілих чисел.)

C++Об'єктна модель (§1.9) вимагає , щоб всі об'єкти, крім бітових полів займають щонайменше , один байти пам'яті і мають адреси; крім того, всі такі об'єкти, що спостерігаються в програмі в даний момент, повинні мати чіткі адреси (параграф 6). Це не зовсім вимагає від компілятора створити новий масив у стеку для кожного виклику функції з локальним нестатичним масивом const, оскільки компілятор може притулитися за as-ifпринципом за умови, що він може довести, що жоден інший такий об'єкт не може бути спостерігається.

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

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

Тож вам обов’язково слід використовувати static constexprу своєму прикладі.

Однак є один випадок, коли ви не хочете користуватися static constexpr. Якщо constexprзадекларований об'єкт не використовується або ODR, або декларується static, компілятор не може взагалі його включати. Це досить корисно, оскільки дозволяє використовувати тимчасові constexprмасиви часу компіляції без забруднення складеної програми зайвими байтами. У такому випадку ви явно не хочете використовувати static, оскільки staticце, ймовірно, змусить об'єкт існувати під час виконання.


2
@AndrewLazarus, ви не можете відкинути constвід constоб'єкта, тільки з const X*яких вказує на X. Але це не суть; справа в тому, що автоматичні об'єкти не можуть мати статичні адреси. Як я вже говорив, constexprпісля закінчення компіляції вона перестає бути значимою, тому не можна нічого відкидати (і, можливо, взагалі нічого, тому що об’єкт навіть не гарантовано існує під час виконання.)
rici

17
Мені здається, не лише ця відповідь неймовірно заплутана, але й суперечлива. Наприклад , ви говорите , що ви майже завжди хочете staticі constexprале пояснити , що вони ортогональні і незалежні, робити різні речі. Потім ви згадуєте причину НЕ поєднувати два, оскільки це ігнорувало б використання ODR (що здається корисним). О, і я досі не бачу, чому статику слід використовувати з constexpr, оскільки статичний для виконання матеріалів. Ви ніколи не пояснювали, чому важлива статика з constexpr.
void.pointer

2
@ void.pointer: Ти маєш рацію щодо останнього абзацу. Я змінив вступ. Я думав, що я пояснив важливість static constexpr(це перешкоджає відновленню постійного масиву під час кожного виклику функції), але я переробив кілька слів, які можуть зробити його більш зрозумілим. Дякую.
rici

8
Також може бути корисним згадати константи часу компіляції проти констант виконання. Іншими словами, якщо constexprконстантна змінна використовується лише в контекстах компіляції часу і ніколи не потрібна під час виконання, то staticце не має сенсу, оскільки до моменту, коли ви дістанетесь до часу виконання, значення було ефективно "підкреслено". Однак, якщо constexprвін використовується в контекстах виконання (іншими словами, його constexprпотрібно буде перетворити на constнеявне і доступне з фізичною адресою для коду виконання), воно захоче staticзабезпечити відповідність ODR тощо. Принаймні, на моє розуміння.
void.pointer

3
Приклад мого останнього коментаря: static constexpr int foo = 100;. Немає причини, чому компілятор не міг би замінити використання fooскрізь буквальним 100, якщо тільки код не робив щось подібне &foo. Так що staticна fooнемає корисність в цьому випадку , так як fooне існує під час виконання. Знову все до компілятора.
void.pointer

10

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

Наступний код демонструє, як constexprзмінна ініціалізується кілька разів (хоча з одним і тим самим значенням), а static constexprнапевно ініціалізується лише один раз.

Крім того, код порівнює перевагу constexprпроти constу поєднанні з static.

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

Можливий вихід програми:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

Як ви бачите constexpr, ініціюється кілька разів (адреса не однакова), а staticключове слово забезпечує ініціалізацію лише один раз.


чи не можемо ми використати constexpr const short constexpr_shortпомилку, якщо constexpr_short знову ініціалізується
akhileshzmishra

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