С++ эквивалентен назначенным инициализаторам?
Недавно я работал над некоторыми встроенными устройствами, где у нас есть некоторые структуры и союзы, которые нужно инициализировать во время компиляции, чтобы мы могли хранить определенные вещи во флеш-памяти или ПЗУ, которые не нужно изменять, и сохраните небольшую вспышку или SRAM с небольшими затратами на производительность. В настоящее время код компилируется как действительный C99, но без этой настройки он также использовался для компиляции как кода на С++, и было бы здорово поддерживать то, что было скомпилировано таким же образом. Одна из ключевых моментов, которые мешают этому, заключается в том, что мы используем назначенные инициализаторы C99, которые не работают в подмножестве C на С++. Я не очень похож на С++, поэтому мне интересно, какие простые способы могут произойти в С++ или С++, которые до сих пор разрешают инициализацию во время компиляции, чтобы структуры и объединения не нуждались в инициализируется после запуска программы в SRAM.
Еще одна заметка: ключевая причина для использования назначенного инициализатора - инициализация как НЕ первого члена союза. Кроме того, придерживаться стандартного С++ или ANSI C является плюсом, чтобы поддерживать совместимость с другими компиляторами (я знаю о расширениях GNU, которые предоставляют что-то вроде назначенных инициализаторов без C99).
Ответы
Ответ 1
Я не уверен, что вы можете сделать это на С++. Для того, что вам нужно инициализировать с помощью назначенных инициализаторов, вы можете поместить их отдельно в файл .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 the header can access g_var, and it will be
// properly initialized at load time
Ответ 2
Основываясь на ответе 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
Ответ 3
Это как ответ, так и вопрос. Я понимаю, что эта нить мертва, но это именно то, что я смотрел сегодня вечером.
Я немного задумался, и самое близкое, что я могу дойти до того, что хочу (что похоже на то, что вы хотите... Я работал с фотографиями и не нуждаюсь в использовании С++, но мне любопытно, как это может быть сделано) является первым примером кода:
#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++.
Ответ 4
#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
В С++ союз может иметь и конструкторы. Может быть, это то, что вы хотели?
Ответ 5
Следующий код компилируется без проблем с 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
Единственное, что связано с инициализаторами С++, это то, что вам нужно инициализировать все элементы структуры, а остальные будут инициализированы нулями. Вы не можете выбирать. Но это должно быть хорошо для вашего случая использования.
Еще одна заметка: ключевая причина для использования назначенного инициализатора инициализируется как НЕ первый член союза.
Для этого вам нужно использовать "обходной путь", показанный в примере, где я устанавливаю член "float", предоставляя эквивалентное значение int. Это немного взломать, но если он решает вашу проблему.
Ответ 6
Отчет о сухом отверстии:
Учитывая
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 и было бы клочом для получения класса для каждого объекта, который вы хотели инициализировать; так что не беспокойтесь.