Объедините два назначенных инициализатора с помощью макроса

Во встроенном проекте я использую библиотеку, которая предоставляет макрос для инициализации структуры. Это обеспечивает разумные значения по умолчанию, но значения по умолчанию зависят от других параметров. Я хочу переопределить одно или несколько значений этого назначенного инициализатора, потому что последующая инициализация значений приводит к накладным расходам.

В идеале я не хочу копировать и вставлять все макросы, потому что тогда мне придется управлять сторонним кодом. Если библиотека изменит свои значения по умолчанию, я тоже не хочу этого делать.

Есть ли способ объединить или переопределить назначенные инициализаторы, чтобы не было накладные расходы? Код должен быть совместимым с C99 и переносимым.

Пример кода для демонстрации проблемы:

#if SITUATION
#define LIBRARY_DEFAULTS \
{ \
  .field_a = 1, \
  .field_b = 2, \
  .field_c = 3 \
}
#else
#define LIBRARY_DEFAULTS \
{ \
  .field_a = 100, \
  .field_b = 200, \
  .field_c = 300, \
  .field_d = 400, \
  .field_e = 500 \
}
#endif

/* The following is what I want (or similar), but (of course) doesn't 
   work. */
// #define MY_DEFAULTS = LIBRARY_DEFAULTS + { .field_a = 100 }

int main(void) {
    /* The exact definition of something also depends on situation. */
    struct something library_thing = LIBRARY_DEFAULTS;

    /* This generates overhead, and I want to avoid this. It is certain
       that the field exists. */
    library_thing.field_a = 100;
}

person BasilFX    schedule 20.02.2016    source источник
comment
Не будет простого способа сделать это. Если бы макрос был написан как #define LIBRARY_DEFAULTS { .field_a = 1, .field_b = 2, .field_c = 3, OPTIONAL_USER_DEFAULTS }, где вы должны выбрать, определить ли этот дополнительный макрос, тогда вы могли бы сделать это (#define OPTIONAL_USER_DEFAULTS .field_a = 100) и вы не получили бы никакого предупреждения от GCC, если бы вы не указали -Woverride-init (который устанавливается -Wextra, но не -Wall). Но зацепа нет. Вы могли бы подумать о том, чтобы предварительно обработать заголовок библиотеки, чтобы предоставить хук, но это было бы беспорядочно.   -  person Jonathan Leffler    schedule 20.02.2016
comment
@JonathanLeffler: обертывание структуры дает довольно простой способ сделать это; см. мой ответ. (Назначенные инициализаторы в C99 действительно мощны!)   -  person nneonneo    schedule 20.02.2016
comment
Что вы имеете в виду под накладными расходами? Заголовочный код не генерируется.   -  person too honest for this site    schedule 22.02.2016
comment
Компилятор (arm-gcc-embedded, -Os) генерирует дополнительную инструкцию для library_thing.field_a = 100. Я называю это накладными расходами, если просто хочу переопределить значение по умолчанию сразу после инициализации значений по умолчанию.   -  person BasilFX    schedule 24.02.2016


Ответы (2)


Вы можете обернуть свой library_thing во внешнюю структуру и выполнить свои переопределения из инициализатора внешней структуры:

#include <stdio.h>

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

#define FOO_DEFAULTS { .a = 1, .b = 2, .c = 3 }

int main() {
    struct {
        struct foo x;
    } baz = {
        .x = FOO_DEFAULTS,
        .x.a = 4,
    };

    printf("%d\n", baz.x.a); // prints 4
}

На самом деле, вы даже можете сделать

.x = FOO_DEFAULTS,
.x = {.a = 4},

если вам действительно нужно "объединить" два инициализатора.

Это нормально компилируется в Clang (7.0.2), но генерирует предупреждение в разделе -Winitializer-overrides. Проверка сгенерированного кода подтверждает, что структура инициализирована с помощью 4, 2, 3, поэтому этот трюк не требует дополнительных затрат.

person nneonneo    schedule 20.02.2016
comment
Что ж, это работает, но дополнительный слой означает, что вам нужно изменить весь код, который использовал struct foo x; …; printf("a = %d\n", x.a);, на использование struct baz x; …; printf("a = %d\n", x.x.a);, что довольно агрессивно. Если бы вы везде использовали указатели, это было бы не так уж плохо — вы бы инициализировали struct baz, но использовали бы указатель на struct foo, содержащийся в struct baz (и адреса такие же). Я буду придерживаться своего, это не простой способ сделать это; Я не думаю, что это можно назвать легким. - person Jonathan Leffler; 20.02.2016
comment
Если вам действительно нужен struct foo, вы можете поместить код инициализатора в static struct foo init_foo() {...; return baz.x;}. Приличный компилятор исключит копию, и вы просто сделаете struct foo x = init_foo(); в своей основной функции. - person nneonneo; 20.02.2016
comment
Я завернул «шаблон» в макрос, который генерирует предлагаемую структуру. Теперь я могу повторно использовать это решение в нескольких местах. Адресация полей через .x.field допустима. Поэтому я принял этот ответ. - person BasilFX; 22.02.2016

Вот одно из возможных решений. Сначала удалите фигурные скобки из макроса

#define LIBRARY_DEFAULTS .a=1, .b=2, .c=3

Затем для переменных, где значения по умолчанию в порядке, вы заключаете макрос в фигурные скобки.

struct something standard = { LIBRARY_DEFAULTS };

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

struct something tweaked = { LIBRARY_DEFAULTS, .a=100 };

Почему это работает? Раздел 6.7.9 спецификации C обсуждает использование позиционных обозначений в списках инициализации и говорит об указании одного и того же позиционного обозначения более одного раза:

19 Инициализация должна происходить в порядке списка инициализаторов, каждый инициализатор, предоставленный для конкретного подобъекта, переопределяет любой ранее указанный инициализатор для того же подобъекта;151) все подобъекты, которые не инициализированы явно, должны быть инициализированы неявно так же, как объекты со статической продолжительностью хранения.

и примечание 151 говорит об этом

151) Любой инициализатор для подобъекта, который переопределен и поэтому не используется для инициализации этого подобъекта, может вообще не оцениваться.

Это означает, что компилятор должен использовать последний назначенный инициализатор и может реализовать это решение без каких-либо накладных расходов.

person user3386109    schedule 20.02.2016
comment
Это работает только в том случае, если вы действительно можете изменить код библиотеки - OP предполагает, что он не может. - person nneonneo; 20.02.2016
comment
Обратите внимание, что и clang, и gcc установлены достаточно суетливо, чтобы предупредить о повторяющихся инициализаторах (-Winitializer-overrides и -Woverride-init соответственно, где для GCC -Wextra включает более конкретный -Woverride-init). Кроме того, проблема заключается в том, что заголовок предоставляется поставщиком библиотеки, поэтому OP должен использовать текущую версию заголовка библиотеки — простое удаление фигурных скобок на самом деле не вариант. - person Jonathan Leffler; 20.02.2016
comment
Я бы сказал, что у OP есть возможность удалить фигурные скобки из заголовка библиотеки, если OP не перекомпилирует код библиотеки, используя измененный заголовок. OP также имеет возможность скопировать/вставить LIBRARY_DEFAULTS в файл заголовка проекта. Обратите внимание, что вы изменяете не код библиотеки, вы изменяете макрос, который предназначен только для использования в пользовательском коде. - person user3386109; 20.02.2016