简体   繁体   English

如何在C中动态访问struct的成员?

[英]How to access member of struct dynamically in C?

I have two structs and array of const chars: 我有两个结构和const字符数组:

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" };

I also have FILES files; 我也有FILES files; in main function and initiate function which loads data from file. 在main函数中,并启动从文件加载数据的函数。 So every member of the files contains data. 因此,文件的每个成员都包含数据。

Then I need to access the data like this: 然后,我需要像这样访问数据:

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

My plan is to create loop to process every member dynamically. 我的计划是创建循环以动态处理每个成员。

for(i = 0; i<6; i++) {
        // do some job
    }

My question is how to do it when I need to access eg files->radiation_insolation using the tables[i] in the loop? 我的问题是当我需要使用循环中的表[i]访问例如files-> radiation_insolation时该怎么做? How to create the name of the member so that the compiler knows what member to access? 如何创建成员的名称,以便编译器知道要访问的成员?

In PHP language one can use something like $files->$tables[i]. 在PHP语言中,可以使用$ files-> $ tables [i]之类的东西。 But how to do it in C? 但是,如何在C中做到这一点?

The answer is you can't. 答案是你做不到。 Well, on a real C compiler, you might be able to alias the second struct as an array of FILE_ but it I'm pretty sure it invokes undefined behaviour. 好吧,在真正的C编译器上,您也许可以将第二个结构别名为FILE_数组,但是我敢肯定它会调用未定义的行为。 I don't think there's anything in the standard that says the padding in a struct where all members are the same type has to be the same as the padding in an array where all members are the same type. 我认为标准中没有什么可以说所有成员都是相同类型的结构中的填充必须与所有成员都是相同类型的数组中的填充相同。

If it is important to you to be able to access all the members in a single for statement, it's probably better to use an actual array and define some constants: 如果能够在一个for语句中访问所有成员对您很重要,则最好使用实际的数组并定义一些常量:

enum {
   radiation_isolation = 0,
   radiation_radiation = 7,
   winds = 12,
   // etc
}

FILE_ files[total_files];

FILE_ *isolation_3 = &files[radiation_isolation + 3];

You'd probably write some functions to make it all look nicer and provide some bounds checking. 您可能会编写一些函数来使其看起来更好并提供一些边界检查。

There's not really a way to do this in C. Structs are not tables, but something much closer to the hardware, namely chunks of memory. 在C语言中,实际上没有办法做到这一点。结构不是表,而是与硬件更接近的东西,即内存块。

You could create an icky macro to access the struct: 您可以创建一个icky宏来访问该结构:

// bad idea
#define FILES_ITEM(var, field, index, member) var.field[index].member

But such macros are just pointless and bad practice, it is much clearer to type out everything: 但是这样的宏只是毫无意义且不好的做法,更清晰地键入所有内容:

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);
  }
}

It will normally be very hard to justify anything else than the above style. 除了上述样式,通常很难证明其他任何理由。


With C11 you could improve the situation a bit by using a union, containing an anonymous structure in combination with an array: 使用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;

You can then either access members individually: 然后,您可以分别访问成员:

files.radiation_insolation[0].skip_lines = 123;

Or as an array: 或作为数组:

files.items[item].skip_lines = 123;

The union is guaranteed to work by C11 §6.7.2.1: C11§6.7.2.1保证工会可以工作:

14 Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type. 14结构或联合对象的每个非位字段成员都以适合于其类型的实现定义的方式对齐。

/--/ /-/

17 There may be unnamed padding at the end of a structure or union. 17在结构或联合的末尾可能存在未命名的填充。

This means that all members of the inner struct are guaranteed to be aligned appropriately, with trailing padding at the end of struct if needed. 这意味着内部结构的所有成员都保证正确对齐,如果需要,在结构的末尾添加尾随填充。

Furthermore, the array is also guaranteed to alias the individual members with no problems, as per C11 6.5/7: 此外,根据C11 6.5 / 7,还保证数组可以毫无问题地别名单个成员:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types: 一个对象只能通过具有以下类型之一的左值表达式访问其存储值:

/--/ /-/

— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union) —集合或联合类型,其成员中包括上述类型之一(递归地包括子集合或包含的联合的成员)

Make union of FILES and similar structure with one FILE_ array of all 27 elements: 使unionFILES和相似的结构与一个FILE_所有27个元素的数组:

typedef union
{
    FILES f;
    FILE_ all[27];
}
FILES_U;

Then you can access files->f.radiation_radiation[0] or the same as files->all[7] . 然后,您可以访问files->f.radiation_radiation[0]或与files->f.radiation_radiation[0] files->all[7] And tables array will contain base indexes in all (0,7,12...) instead of string names. 并且tables数组将在all (0,7,12 ...)中包含基本索引,而不是字符串名称。

One approach would be through the (ab)use of x-macros. 一种方法是通过(滥用)使用x-宏。 They allow you to reduce repetition, at the expense of potential rage of your fellow coworkers. 它们使您减少重复,却以同事的潜在愤怒为代价。 The benefit is that you will only need to update the list of items at one place, and the struct and all the necessary metadata will be autogenerated by the preprocessor. 好处是您只需要在一个地方更新项目列表,并且预处理器将自动生成结构和所有必要的元数据。

Ie you define simply a list of entries like this, where FILE_ENTRY is yet undefined: 即,您仅定义一个这样的条目列表,其中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)

And then define FILE_ENTRY(name, len) as you wish: 然后根据需要定义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)
};

This will expand to something like: 这将扩展为:

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",
};

You can then iterate it using something like: 然后,您可以使用类似的方法进行迭代:

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);
    }
}

This will iterate through all the members of FILES , and through all array members of each field: 这将遍历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

And this brings you to the actual "reflection" which allows you to find the member by its name: 这将带您进入实际的“反思”,使您可以按其名称查找成员:

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;
}

For example, this will get the pointer to files.winds[4] : 例如,这将获得指向files.winds[4]的指针:

FILE_ * item = get_entry_by_name_and_index(&files, "winds", 4);
assert((void*)item == (void*)&files.winds[4]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM