Выборочная инициализация массивов и структур
При всей своей примитивности язык C иногда преподносит приятные сюрпризы. Например, в совершенный восторг меня приводит способ инициализации структур и массивов под названием designated initializers (я предпочитаю переводить это как «выборочная инициализация»). Суть в том, что при инициализации переменной агрегатного типа можно явно указать, какие части структуры данных мы инициализируем, а какие — предоставляем обнулить компилятору. Звучит немного заумно, поэтому сразу приведу пару простых примеров.
Выборочная инициализация массива (работает в C, но не в C++!):
int a[] = { [2] = 2, [7] = 7 };
Выборочная инициализация структуры:
struct { int a; int b; int c; } s = { .a = 1, .c = 2 };
Нетрудно догадаться, что здесь происходит. В первом случае мы получаем массив из 8 элементов, два из которых инициализированы, а остальным присваивается 0. Со структурой еще проще: поля a и c получают значения, а поле b обнуляется. Рассмотрим примеры посложнее.
struct { int a; struct { char c; int d; } b; int c; } s = { .a = 2, .b.d = 3 };
static short grid[3] [4] = { [0][0]=8, [0][1]=6, [0][2]=4, [0][3]=1, [2][0]=9, [2][1]=3, [2][2]=1, [2][3]=1 };
int a[] = {2, 4, [8]=9, 10}
Если первые два случая еще как-то интуитивно понятны, то последний поначалу заставляет задуматься. Здесь действует простое правило, такое же, как и при объявлении enum. По умолчанию индексы в инициализаторе начинаются с 0 и с каждым новым значением увеличиваются на единицу. Но если указать индекс явно, он становится новым значением по умолчанию. Приведенный выше пример раскрывается в следующее:
int a[] = {[0]=2, [1]=4, [8]=9, [9]=10}
Таким образом, получим массив из 10 элементов.
К сожалению, из всякого правила есть исключения. C настолько гибок, что позволяет задурить самого себя. Дело в том, что разрешается инициализировать элементы массива в произвольном порядке. Как вы думаете, что произойдет при такой инициализации?
int a[] = {2, [3] = 3, [2] = 2, 5};
Могу сказать сразу, что синтаксически конструкция безупречна.
Основная цель выборочной инициализации — повысить читабельность кода. Особенно это касается, пожалуй, многомерных массивов. Еще одно из возможных применений — создание словарей, индексированных по значению некоторых констант. Например, можно сделать словарь имен сигналов:
#define SIGNAME(s) [s] = #s const char * const sigNames[] = { SIGNAME(SIGHUP), SIGNAME(SIGINT), SIGNAME(SIGQUIT), /* ... */ }; #undef SIGNAME
Теперь, если численные значения идентификаторов сигналов изменятся, нам ничего не придется менять, массив сам подстроится. Это, конечно, достаточно примитивный случай, но вполне демонстрирующий идею — как можно соблюсти принцип DRY и сделать код более аккуратным.


Легко читается при высокой информативности. И в целом очень интересный блог. Спасибо)
int a[] = {2, [3] = 3, [2] = 2, 5};
Справедливо не просто для С, а для С99… Спасибо за статью))
А вам спасибо за уточнение!
#define SIGNAME(s) [s] = #s
…
А что значит второй # в этой строке?
Это превращение аргумента в строковый литерал. Например, вызов макроса
SIGNAME(SIGINT)
раскрывается в
[SIGINT] = «SIGINT»