С++ эквивалентен назначенным инициализаторам?

Недавно я работал над некоторыми встраиваемыми устройствами, где у нас есть некоторые структуры и объединения, которые необходимо инициализировать во время компиляции, чтобы мы могли хранить определенные вещи во флэш-памяти или ПЗУ, которые не нужно изменять, и немного сэкономить. флэш-память или SRAM с небольшим снижением производительности. В настоящее время код компилируется как допустимый C99, но без этой настройки он также компилировался как код C++, и было бы здорово поддерживать компиляцию таким образом. Одна из ключевых вещей, которая предотвращает это, заключается в том, что мы используем назначенные C99 инициализаторы, которые не работают в подмножестве C C++. Я не большой любитель C++, поэтому мне интересно, какие простые способы могут быть, чтобы это произошло либо в C++-совместимом C, либо в C++, которые по-прежнему позволяют инициализировать во время компиляции, так что структуры и объединения не нужно инициализируется после запуска программы в SRAM.

Еще одно замечание: основной причиной использования назначенного инициализатора является инициализация НЕ как первого члена объединения. Кроме того, использование стандартного C++ или ANSI C является плюсом для обеспечения совместимости с другими компиляторами (я знаю о расширениях GNU, которые предоставляют что-то вроде назначенных инициализаторов без C99).


person James Snyder    schedule 13.05.2009    source источник
comment
Обратите внимание, что назначенные инициализаторы теперь работают в g++. У меня версия 4.8.1, и я мог использовать инициализаторы из перечисления, и это сработало так, как ожидалось.   -  person Alexis Wilke    schedule 21.05.2014


Ответы (6)


Я не уверен, что вы можете сделать это на C++. Для вещей, которые вам нужно инициализировать с помощью назначенных инициализаторов, вы можете поместить их отдельно в файл .c, скомпилированный как C99, например:

// In common header file
typedef union my_union
{
    int i;
    float f;
} my_union;

extern const my_union g_var;

// In file compiled as C99
const my_union g_var = { .f = 3.14159f };

// Now any file that #include's the header can access g_var, and it will be
// properly initialized at load time
person Adam Rosenfield    schedule 13.05.2009
comment
Я подумал об этом и, возможно, в конечном итоге пойду по этому пути. К сожалению, есть одна платформа, на которую мы хотели бы перенести использование библиотеки C++ для поддержки периферийных устройств. Они предоставляют доступ к компилятору онлайн, но ограничили его режимом только для C++ (для простоты). Я попытался получить предварительно скомпилированную библиотеку и использовать локальную цепочку инструментов GCC, но есть некоторые символы, которые не разрешаются из их библиотеки (они используют RealView от Keil/ARM) при связывании с GCC и Newlib. Я мог бы предварительно скомпилировать весь код C99 локально и связать его онлайн. Я просто стараюсь не усложнять :-) - person James Snyder; 13.05.2009
comment
Это может сработать, но я считаю, что это довольно быстро запутается, если вы используете нестандартные типы для членов ваших союзов/структур. - person Zimano; 23.12.2015

Основываясь на ответе Shing Yip и благодаря 3-летнему времени, С++ 11 теперь может гарантировать инициализацию времени компиляции:

union Bar
{
    constexpr Bar(int a) : a_(a) {}
    constexpr Bar(float b) : b_(b) {}
    int a_;
    float b_;
};

extern constexpr Bar bar1(1);
extern constexpr Bar bar2(1.234f);

Сборка:

    .globl  _bar1                   ## @bar1
    .p2align    2
_bar1:
    .long   1                       ## 0x1

    .globl  _bar2                   ## @bar2
    .p2align    2
_bar2:
    .long   1067316150              ## float 1.23399997
person Howard Hinnant    schedule 21.07.2012

#ifdef __cplusplus
struct Foo
{
    Foo(int a, int b) : a(a), b(b) {}
    int a;
    int b;
};

union Bar
{
    Bar(int a) : a(a) {}
    Bar(float b) : b(b) {}
    int a;
    float b;
};

static Foo foo(1,2);
static Bar bar1(1);
static Bar bar2(1.234f);
#else 
 /* C99 stuff */
#endif // __cplusplus

В C++ объединение также может иметь конструкторы. Может быть, это то, что вы хотели?

person Shing Yip    schedule 13.05.2009
comment
Выполняется ли инициализация во время выполнения или во время компиляции? Это, я полагаю, критическая проблема здесь. - person James Snyder; 13.05.2009
comment
Мне нужно будет проконсультироваться со стандартом холли, чтобы быть уверенным, но я думаю, что все глобальные статические переменные инициализируются и хранятся в разделе данных исполняемого образа. Лучше всего попробовать это с вашим компилятором и посмотреть, что он делает. - person Shing Yip; 14.05.2009
comment
Нет, пространство будет выделено в исполняемом образе, инициализировано нулями, а конструктор будет вызван во время выполнения. Просто чтобы добавить веселья, встроенные системы могут быть немного непоследовательными в этом последнем пункте - теоретически компоновщик собирает список вызовов статического конструктора, а затем libgcc вызывает их во время начальной загрузки процесса, но в зависимости от вашей платформы это просто не может произойдет или произойдет только в том случае, если вы выберете правильные параметры сборки. - person Tom; 30.01.2012

Это своего рода и ответ, и вопрос. Я понимаю, что эта ветка мертва, но это именно то, что я искал сегодня вечером.

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

#include <iostream>

using namespace std;

extern "C" 
{
    typedef struct stuff
    {
        int x;
        double y;
    } things;
}

int main()
{
    things jmcd = { jmcd.x = 12, jmcd.y = 10.1234 };
    cout << jmcd.x << " " << jmcd.y << endl;
    return 0;
}

Он очень похож на инициализаторы, назначенные в стиле C99, с одной оговоркой, о которой я упомяну позже. (Возможно, вы бы обернули это в #ifdef __cplusplus, если хотите, чтобы структура была скомпилирована одним из них.) Вторая версия кода, на которую я смотрел, такова:

#include <iostream>

using namespace std;

extern "C" 
{
    typedef struct stuff
    {
        int x;
        double y;
    } things;
}


int main()
{
    things jmcd;
    jmcd.x = 12;
    jmcd.y = 10.1234;
    cout << jmcd.x << " " << jmcd.y << endl;
    return 0;
}

По сути, глядя на дизассемблирование, кажется, что первый пример на самом деле медленнее. Я посмотрел на результат сборки и, должно быть, немного заржавел. Может быть, кто-то может дать мне некоторое представление. Вывод сборки первого cpp скомпилирован и выглядел так:

main:
.LFB957:
    .cfi_startproc
    .cfi_personality 0x0,__gxx_personality_v0
    pushl   %ebp
    .cfi_def_cfa_offset 8
    movl    %esp, %ebp
    .cfi_offset 5, -8
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $0, 12(%esp)
    movl    $0, 16(%esp)
    movl    $0, 20(%esp)
    movl    $12, 12(%esp)
    movl    12(%esp), %eax
    movl    %eax, 12(%esp)
    fldl    .LC0
    fstpl   16(%esp)
    fldl    16(%esp)
    fstpl   16(%esp)
    movl    12(%esp), %eax
    movl    %eax, 4(%esp)
    fildl   4(%esp)
    fldl    16(%esp)
    faddp   %st, %st(1)
    fnstcw  2(%esp)
    movzwl  2(%esp), %eax
    movb    $12, %ah
    movw    %ax, (%esp)
    fldcw   (%esp)
    fistpl  4(%esp)
    fldcw   2(%esp)
    movl    4(%esp), %eax
    leave
    ret
    .cfi_endproc

Второй пример выглядел так:

main:
.LFB957:
    .cfi_startproc
    .cfi_personality 0x0,__gxx_personality_v0
    pushl   %ebp
    .cfi_def_cfa_offset 8
    movl    %esp, %ebp
    .cfi_offset 5, -8
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $12, 12(%esp)
    fldl    .LC0
    fstpl   16(%esp)
    movl    12(%esp), %eax
    movl    %eax, 4(%esp)
    fildl   4(%esp)
    fldl    16(%esp)
    faddp   %st, %st(1)
    fnstcw  2(%esp)
    movzwl  2(%esp), %eax
    movb    $12, %ah
    movw    %ax, (%esp)
    fldcw   (%esp)
    fistpl  4(%esp)
    fldcw   2(%esp)
    movl    4(%esp), %eax
    leave
    ret
    .cfi_endproc

Оба они были созданы с помощью команды g++ -O0 -S main.cpp. Очевидно, что интуитивно менее эффективный пример генерирует более эффективный код операции с точки зрения количества инструкций. С другой стороны, есть несколько случаев, когда я мог бы представить, что несколько инструкций являются критическими. (С другой стороны, у меня действительно проблемы с пониманием ассемблера, написанного не людьми, поэтому, возможно, я что-то упускаю...) Я думаю, что это дает решение, хотя и запоздалое, на вопрос, заданный Джеймсом. Следующее, что я должен проверить, — разрешена ли такая же инициализация в C99; если это сработает, я думаю, это полностью решит проблему Джеймса.

Отказ от ответственности: я понятия не имею, работает ли это или ведет себя аналогично для любых других компиляторов, кроме g++.

person Andrew    schedule 12.07.2011
comment
У меня работает - во всяком случае, не в разделе быстрого доступа. Лучшее решение, чем использование другого файла C! Голосуйте за! - person Sam; 29.11.2011
comment
Опять же с опозданием, но это просто не работает, если вы поменяете местами jmcd.x и jmcd.y в первом решении. Это потому, что это не специальная конструкция, это просто обычная инициализация с большим количеством выражений. Итак, выполняется jmcd.x = 12, а затем значение результата для этого выражения (12) присваивается первому полю структуры (x). То же самое для у. Если вы поменяете их местами, в обоих полях будет 12. - person Asaf; 04.01.2012
comment
Поддержка назначенных инициализаторов в C++ является расширением GNU, а не частью C++11. Даже с расширением GCC назначенная инициализация разрешена только для типов POD. - person chys; 17.01.2013

Отчет о сухой скважине:

Данный

struct S {
  int mA;
  int mB;
  S() {}
  S(int b} : mB(b) {} // a ctor that does partial initialization
};

Я попытался вывести S1 из S, где встроенный конструктор S1 по умолчанию вызывает S(int) и передает жестко запрограммированное значение...

struct S1 {
  S1() : S(22) {}
} s1;

... а затем скомпилирован с помощью gcc 4.0.1 -O2 -S. Была надежда, что оптимизатор увидит, что s1.mB обязательно будет 22 и присвоит ему значение во время компиляции, но из ассемблера...

    movl    $22, 4+_s1-"L00000000002$pb"(%ebx)

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

person Thomas L Holaday    schedule 13.05.2009
comment
Спасибо. Это не обязательно должно компилироваться как C99. Инициализаторы находятся в нескольких местах, но они определены с помощью некоторых макросов, так что я могу использовать ifdef __cplusplus с минимальной модификацией, это тоже нормально. - person James Snyder; 13.05.2009

Следующий код компилируется без проблем с g++:

#include <iostream>

struct foo
{
  int a;
  int b;
  int c;
};

union bar
{
  int a;
  float b;
  long c;
};

static foo s_foo1 = {1,2,3};
static foo s_foo2 = {1,2};
static bar s_bar1 = {42L};
static bar s_bar2 = {1078523331}; // 3.14 in float


int main(int, char**)
{
  std::cout << s_foo1.a << ", " <<
               s_foo1.b << ", " <<
               s_foo1.c << std::endl;

  std::cout << s_foo2.a << ", " <<
               s_foo2.b << ", " <<
               s_foo2.c << std::endl;

  std::cout << s_bar1.a << ", " <<
               s_bar1.b << ", " <<
               s_bar1.c << std::endl;

  std::cout << s_bar2.a << ", " <<
               s_bar2.b << ", " <<
               s_bar2.c << std::endl;

  return 0;
}

Вот результат:

$ g++ -o ./test ./test.cpp
$ ./test
1, 2, 3
1, 2, 0
42, 5.88545e-44, 42
1078523331, 3.14, 1078523331

Единственное, что касается инициализаторов C++, это то, что вам нужно инициализировать все элементы структуры, иначе остальные будут инициализированы нулями. Вы не можете выбрать и выбрать. Но это все еще должно быть в порядке для вашего варианта использования.

Еще одно замечание: основной причиной использования назначенного инициализатора является инициализация НЕ как первого члена объединения.

Для этого вам нужно использовать «обходной путь», показанный в примере, где я установил член «float», предоставив эквивалентное значение int. Это немного взломать, но если это решит вашу проблему.

person lothar    schedule 13.05.2009
comment
Это только потому, что 42L неявно приводится к целому числу. Если вы хотите инициализировать член с плавающей запятой, скажем, 3.5, вы не можете сделать это в C++. - person Adam Rosenfield; 13.05.2009
comment
Вы не можете инициализировать более одного члена объединения. В конце концов, это объединение ;-) Однако, если вы хотите инициализировать часть с плавающей запятой, вам нужно будет инициализировать ее целочисленным эквивалентом (возможно, как шестнадцатеричное число) - person lothar; 13.05.2009
comment
Да, но назначенные инициализаторы C99 позволяют вам инициализировать элемент объединения, отличный от первого, не прибегая к хакам, таким как вычисление целочисленного эквивалента float, определенного реализацией. - person Adam Rosenfield; 13.05.2009
comment
Я не прошу инициализировать более одного члена, просто не всегда первый. Причина, по которой это немного сложнее, чем с эквивалентами, заключается в том, что некоторые из них являются более сложными членами объединения. Я мог бы указать на наши источники (модифицированный Lua для работы на небольших устройствах), но нужно немного покопаться, чтобы понять, как все работает. Если есть интерес, вот отправная точка: svn.berlios .de/svnroot/repos/elua/trunk/src/lua/lrotable.h Описание (да, оно отображается необработанным, документов пока нет нигде): svn.berlios.de/svnroot/repos/elua/trunk/doc/en/arch_ltr.html - person James Snyder; 13.05.2009
comment
Что ж, если вы хотите скомпилировать его как стандартный C++, боюсь, вам придется использовать хаки, подобные эквивалентам. И они вам понадобятся только для данных, которые попадают в ПЗУ, для всех других данных, которые попадают в ОЗУ, вы можете создавать обычные конструкторы, как указывали другие. - person lothar; 13.05.2009