Как получить доступ к члену структуры динамически в C?
У меня есть две структуры и массив константных символов:
typedef struct {
int skip_lines;
int num; // count of files
int i; // number of the file to define order; extremly important to set correctly and then not to change!
char filename[70];
char main_directory[16];
char submain_directory[100];
} FILE_;
typedef struct {
FILE_ radiation_insolation[7];
FILE_ radiation_radiation[5];
FILE_ winds[9];
FILE_ pressure[1];
FILE_ humidity[1];
FILE_ temperature[4];
} FILES;
char *tables[] = {"radiation_insolation", "radiation_radiation", "winds", "pressure", "humidity", "temperature" };
у меня тоже есть FILES files;
в основной функции и инициировать функцию, которая загружает данные из файла. Таким образом, каждый член файлов содержит данные.
Тогда мне нужно получить доступ к данным, как это:
files->radiation_insolation[0].skip_lines
files->radiation_radiation[0].skip_lines
files->radiation_winds[0].skip_lines
files->pressure[0].skip_lines
files->humidity[0].skip_lines
files->temperature[0].skip_lines
Мой план - создать цикл для динамической обработки каждого члена.
for(i = 0; i<6; i++) {
// do some job
}
Мой вопрос, как это сделать, когда мне нужно получить доступ, например, к файлам->radi_insolation, используя таблицы [i] в цикле? Как создать имя члена, чтобы компилятор знал, к какому члену получить доступ?
В языке PHP можно использовать что-то вроде $files->$tables[i]. Но как это сделать на С?
4 ответа
Один из подходов заключается в использовании (ab) x-макросов. Они позволяют вам уменьшить количество повторений за счет потенциальной ярости ваших коллег по работе. Преимущество заключается в том, что вам нужно будет обновить список элементов только в одном месте, а препроцессор автоматически сгенерирует структуру и все необходимые метаданные.
Т.е. вы просто определяете список таких записей, где FILE_ENTRY
еще не определено:
#define X_FILE_LIST(X_FILE_ENTRY) \
X_FILE_ENTRY(radiation_insolation, 7) \
X_FILE_ENTRY(radiation_radiation, 5) \
X_FILE_ENTRY(winds, 9) \
X_FILE_ENTRY(pressure, 1) \
X_FILE_ENTRY(humidity, 1) \
X_FILE_ENTRY(temperature, 4)
А затем определить FILE_ENTRY(name, len)
как хотите:
// number of entries
#define X_EXPAND_AS_COUNT(name, len) 1 +
const int FILES_count = X_FILE_LIST(X_EXPAND_AS_COUNT) 0;
// struct definition
#define X_EXPAND_AS_FIELD(name, len) FILE_ name[len];
typedef struct {
X_FILE_LIST(X_EXPAND_AS_FIELD)
}
FILES;
// byte offsets of each field
#define X_EXPAND_AS_BYTEOFFSET(name, len) offsetof(FILES, name),
int FILES_byte_offsets[] = {
X_FILE_LIST(X_EXPAND_AS_BYTEOFFSET)
};
// FILE_ offsets of each field
#define X_EXPAND_AS_FILEOFFSET(name, len) offsetof(FILES, name)/sizeof(FILE_),
int FILES_offsets[] = {
X_FILE_LIST(X_EXPAND_AS_FILEOFFSET)
};
// sizes of each array
#define X_EXPAND_AS_LEN(name, len) len,
int FILES_sizes[] = {
X_FILE_LIST(X_EXPAND_AS_LEN)
};
// names of each field
#define X_EXPAND_AS_NAME(name, len) #name,
const char * FILES_names[] = {
X_FILE_LIST(X_EXPAND_AS_NAME)
};
Это расширится до чего-то вроде:
const int FILES_count = 1 + 1 + 1 + 1 + 1 + 1 + 0;
typedef struct {
FILE_ radiation_insolation[7];
FILE_ radiation_radiation[5];
FILE_ winds[9];
FILE_ pressure[1];
FILE_ humidity[1];
FILE_ temperature[4];
}
FILES;
int FILES_byte_offsets[] = {
((size_t)&(((FILES*)0)->radiation_insolation)),
((size_t)&(((FILES*)0)->radiation_radiation)),
((size_t)&(((FILES*)0)->winds)),
((size_t)&(((FILES*)0)->pressure)),
((size_t)&(((FILES*)0)->humidity)),
((size_t)&(((FILES*)0)->temperature)),
};
int FILES_offsets[] = {
((size_t)&(((FILES*)0)->radiation_insolation))/sizeof(FILE_),
((size_t)&(((FILES*)0)->radiation_radiation))/sizeof(FILE_),
((size_t)&(((FILES*)0)->winds))/sizeof(FILE_),
((size_t)&(((FILES*)0)->pressure))/sizeof(FILE_),
((size_t)&(((FILES*)0)->humidity))/sizeof(FILE_),
((size_t)&(((FILES*)0)->temperature))/sizeof(FILE_),
};
int FILES_sizes[] = { 7, 5, 9, 1, 1, 4, };
const char * FILES_names[] = {
"radiation_insolation", "radiation_radiation",
"winds", "pressure", "humidity", "temperature",
};
Затем вы можете повторить это, используя что-то вроде:
for (int i = 0; i < FILES_count; i++)
{
FILE_ * first_entry = (FILE_ *)&files + FILES_offsets[i];
for (int j = 0; j < FILES_sizes[i]; j++)
{
FILE_ * file = first_entry + j;
printf("%s[%d].skip_lines = %d \n",
FILES_names[i],
j,
file->skip_lines);
}
}
Это будет перебирать всех членов FILES
и через все элементы массива каждого поля:
// output of the program above
radiation_insolation[0].skip_lines = 0
radiation_insolation[1].skip_lines = 0
radiation_insolation[2].skip_lines = 0
radiation_insolation[3].skip_lines = 0
radiation_insolation[4].skip_lines = 0
radiation_insolation[5].skip_lines = 0
radiation_insolation[6].skip_lines = 0
radiation_radiation[0].skip_lines = 0
radiation_radiation[1].skip_lines = 0
radiation_radiation[2].skip_lines = 0
radiation_radiation[3].skip_lines = 0
radiation_radiation[4].skip_lines = 0
winds[0].skip_lines = 0
winds[1].skip_lines = 0
winds[2].skip_lines = 0
winds[3].skip_lines = 0
winds[4].skip_lines = 0
winds[5].skip_lines = 0
winds[6].skip_lines = 0
winds[7].skip_lines = 0
winds[8].skip_lines = 0
pressure[0].skip_lines = 0
humidity[0].skip_lines = 0
temperature[0].skip_lines = 0
temperature[1].skip_lines = 0
temperature[2].skip_lines = 0
temperature[3].skip_lines = 0
И это приводит вас к реальному "отражению", которое позволяет вам найти члена по его имени:
FILE_ * get_entry_by_name_and_index(FILES * files, const char * name, int idx)
{
// NOTE: no bounds checking/safe string function, etc
for (int i = 0; i < FILES_count; i++)
{
if (strcmp(FILES_names[i], name) == 0)
{
int base_offset = FILES_offsets[i];
return (FILE_ *)files + base_offset + idx;
}
}
return NULL;
}
Например, это получит указатель на files.winds[4]
:
FILE_ * item = get_entry_by_name_and_index(&files, "winds", 4);
assert((void*)item == (void*)&files.winds[4]);
На самом деле в Си нет способа сделать это. Структуры - это не таблицы, а нечто гораздо более близкое к оборудованию, а именно куски памяти.
Вы можете создать макрос для доступа к структуре:
// bad idea
#define FILES_ITEM(var, field, index, member) var.field[index].member
Но такие макросы - просто бессмысленная и плохая практика, гораздо понятнее напечатать все:
int main (void)
{
FILES files;
for(size_t i=0; i<7; i++)
{
files.radiation_insolation[i].skip_lines = i;
printf("%d ", files.radiation_insolation[i].skip_lines);
}
}
Как правило, будет очень трудно оправдать что-либо еще, кроме вышеуказанного стиля.
С C11 вы могли бы немного улучшить ситуацию, используя объединение, содержащее анонимную структуру в сочетании с массивом:
#define FILE_ITEMS_N (7 + 5 + 9 + 1 + 1 + 4)
typedef union {
struct
{
FILE_ radiation_insolation[7];
FILE_ radiation_radiation[5];
FILE_ winds[9];
FILE_ pressure[1];
FILE_ humidity[1];
FILE_ temperature[4];
};
FILE_ items [FILE_ITEMS_N];
} FILES;
Затем вы можете получить доступ к членам индивидуально:
files.radiation_insolation[0].skip_lines = 123;
Или как массив:
files.items[item].skip_lines = 123;
Союз гарантированно работает согласно C11 §6.7.2.1:
14 Каждый элемент не битового поля структуры или объекта объединения выравнивается определенным реализацией способом, соответствующим его типу.
/ - /
17 Там может быть безымянный отступ в конце структуры или объединения.
Это означает, что все члены внутренней структуры гарантированно выровнены соответствующим образом, с конечным заполнением в конце структуры, если это необходимо.
Кроме того, массив также гарантирует псевдонимы отдельных членов без проблем, в соответствии с C11 6.5/7:
Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов:
/ - /
- агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегата или автономного объединения)
Ответ: ты не можешь. Ну, на реальном C-компиляторе вы можете использовать псевдоним второй структуры как массив FILE_
но я уверен, что это вызывает неопределенное поведение. Я не думаю, что в стандарте есть что-то, что говорит, что заполнение в структуре, где все члены имеют одинаковый тип, должно быть таким же, как заполнение в массиве, где все члены имеют одинаковый тип.
Если для вас важно иметь доступ ко всем элементам в одном операторе for, вероятно, лучше использовать реальный массив и определить некоторые константы:
enum {
radiation_isolation = 0,
radiation_radiation = 7,
winds = 12,
// etc
}
FILE_ files[total_files];
FILE_ *isolation_3 = &files[radiation_isolation + 3];
Вы, вероятно, написали бы некоторые функции, чтобы все выглядело лучше и обеспечили некоторую проверку границ.
Делать union
из FILES
и похожая структура с одним FILE_
массив из всех 27 элементов:
typedef union
{
FILES f;
FILE_ all[27];
}
FILES_U;
Тогда вы можете получить доступ files->f.radiation_radiation[0]
или так же, как files->all[7]
, А также tables
массив будет содержать базовые индексы в all
(0,7,12...) вместо строковых имен.