How to access member of struct dynamically in C?

白昼怎懂夜的黑 提交于 2020-01-14 03:57:07

问题


I have two structs and array of const chars:

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; in main function and initiate function which loads data from file. 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 e.g. files->radiation_insolation using the tables[i] in the loop? 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]. But how to do it in C?


回答1:


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. 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:

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.




回答2:


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.

You could create an icky macro to access the struct:

// 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:

#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:

14 Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type.

/--/

17 There may be unnamed padding at the end of a structure or union.

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:

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)




回答3:


Make union of FILES and similar structure with one FILE_ array of all 27 elements:

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]. And tables array will contain base indexes in all (0,7,12...) instead of string names.




回答4:


One approach would be through the (ab)use of x-macros. 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.

I.e. you define simply a list of entries like this, where FILE_ENTRY is yet undefined:

#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:

// 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:

// 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]:

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


来源:https://stackoverflow.com/questions/45562681/how-to-access-member-of-struct-dynamically-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!