1

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?

John Boe
  • 3,016
  • 10
  • 32
  • 56
  • 6
    You can't. It's called reflection and C doesn't support it. Variable names are lost upon compilation. – Hatted Rooster Aug 08 '17 at 08:06
  • 1
    You can't. C doesn't have [introspection](https://en.wikipedia.org/wiki/Type_introspection) or [reflection](https://en.wikipedia.org/wiki/Reflection_(computer_programming)), which is needed for this to work. – Some programmer dude Aug 08 '17 at 08:07
  • 2
    On an unrelated note, creating the typenames `FILE_` and `FILES` is going to lead to problem if you use files in your program, because then you have the standard C `FILE`, and your `FILE_` and `FILES`. It will be very hard to read and understand, as well as easy to make mistakes. – Some programmer dude Aug 08 '17 at 08:11
  • Thx for ur comments. I rename the structs FILES and FILE_ to TABLES and TABLE_. I have new idea. I have create array of integers: ` int * table_types[TABLE_TYPES_NUM]; table_types[0] = tables.radiation_insolation; table_types[1] = tables.radiation_radiation; table_types[2] = tables.winds; table_types[3] = tables.pressure; table_types[4] = tables.humidity; table_types[5] = tables.temperature; `. I would like to access the members from the pointer. But IDK how to write the part to access the element 0 of the table_types[i]. – John Boe Aug 08 '17 at 08:55

4 Answers4

0

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.

JeremyP
  • 80,230
  • 15
  • 117
  • 158
  • It should be safe to alias it against an array, since each struct will be aligned with padding in itself. You can add extra checks to be sure that this is the case, but I'm not sure if it will be necessary in practice. – Lundin Aug 08 '17 at 09:13
  • At least I can't come up with any scenario neither in theory nor practice that would cause it to fail. Rather, it is implicitly guaranteed to work by the C standard. – Lundin Aug 08 '17 at 09:55
  • @Lundin can you point to where in the C standard it is implicitly guaranteed? (Just to save me some research) – JeremyP Aug 08 '17 at 09:57
  • Added quotes at the bottom of my posted answer. – Lundin Aug 08 '17 at 10:07
0

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)

Lundin
  • 155,020
  • 33
  • 213
  • 341
  • `int i` refers to number of the member, not array element: `files.radiation_insolation[i].skip_lines` is wrong. If I changed your code to: pseudo code: `files.table_type[i][n].skip_lines` ... so the `n` dimension can change. The i dimension is a constant. – John Boe Aug 08 '17 at 09:22
  • Shouldn't there be some explicit packing pragmas here? Or is this defined behavior in C11? – Groo Aug 08 '17 at 09:23
  • @Groo It should be well-defined under every version of C, simply because each struct member in itself is guaranteed to be aligned and padded. For any type `struct { struct a mem1; struct a mem2; } X;` there is never a need for X to apply padding in itself, because this is already ensured by the individual members. So therefore such a struct will always be equivalent to an array `struct a array[2]`. And there is no strict aliasing concerns here either, since it is fine to use "an aggregate or union type that includes one of the aforementioned types among its members". – Lundin Aug 08 '17 at 09:28
-1

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.

i486
  • 6,083
  • 3
  • 21
  • 37
  • I haven't looked it up, but I would think that invokes UB. – JeremyP Aug 08 '17 at 09:02
  • 1
    @JeremyP I'm pretty sure that this is perfectly fine, as long as there are no other members but `FILE_` ones. Since all items are aligned/padded structs by themselves, alignment shouldn't be an issue. And there is no strict aliasing violation either. – Lundin Aug 08 '17 at 09:19
  • 1
    Does the C standard specify that the padding between struct members of the same type must be the same as the padding between array elements of that type? If not, this solution is technically wrong, although I agree that it would almost certainly work in any real implementation. – JeremyP Aug 08 '17 at 09:55
  • @JeremyP Padding is 1, 2, 4, 8,... bytes. With corresponding compiler option or statement like `__attribute__ ((packed))` this can be fixed. And there is no need to give examples with PDP11 or other ancient 36-bit architecture. – i486 Aug 08 '17 at 11:27
  • There is never any padding between array elements. However, each struct member here is also a struct, which applies padding in itself. So a struct containing several structs, all of the same type, will never add additional padding. There is no need for packing - on the contrary this might break alignment and portability. – Lundin Aug 08 '17 at 13:45
  • 1
    In my answer (which is essentially just another flavour of this one), I have cited the reason that guarantee that this method will work. – Lundin Aug 08 '17 at 13:46
  • Of course there can be padding between array elements. The start of each array element must be aligned. Also, since your quote says the padding between struct members is "implementation defined" and not explicitly the same as array members, I do not think your solution is portable. – JeremyP Aug 08 '17 at 14:35
  • @JeremyP No, the C standard explicitly states that an array must be allocated as an adjacent chunk of memory. If the array items are smaller or not a multiple of the alignment size, then item access will be misaligned. A _struct_ must however be aligned, allocated at an even address. So if you have an array of structs, each of them will be appropriately aligned, and that's because of the properties of a struct and not of an array. – Lundin Aug 09 '17 at 06:06
  • @Lundin Where in the C standard does it say a struct must be allocated at an even address? Where does it say that an implementation must not put padding in a struct except for resolving alignment issues? – JeremyP Aug 09 '17 at 08:28
  • "If the array items are smaller or not a multiple of the alignment size, then item access will be misaligned" - right, so if an implementation chooses not to pad the last element of a struct, then the organisation in memory of a struct of structs of the same type can be different from an array of structs of the same type which means the union is not safe. – JeremyP Aug 09 '17 at 08:31
  • @JeremyP Try to find compiler and configuration where the `union` method will not work. I think it will not be easy. – i486 Aug 09 '17 at 09:00
  • @JeremyP It is specified in the part I already cited. You'll find that all implementations do add padding bytes at the end, just so that you can declares arrays of the struct. Because it is also specified that there may not be any padding bytes at the beginning of the struct. – Lundin Aug 09 '17 at 09:35
  • No. You cited a bit of text that says the padding between members is implementation defined i.e. it's up to the implementation what the padding is. I could also point out a bit that says the union contains *at most one of its members*. – JeremyP Aug 09 '17 at 09:45
-1

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]);
Groo
  • 45,930
  • 15
  • 109
  • 179
  • Please be aware that this solution is only justified if you have rigorous requirements regarding code repetition during maintenance. If so, then this is a fine solution. If not, then this is obfuscation. – Lundin Aug 08 '17 at 09:16
  • Yes, that's the deal with x-macros. But they are unfortunately the closest you can get to reflection in C. I would say the obfuscation still reduces the number of *runtime* errors due to developer mistakes when updating the code. It's true that unit testing is there to catch these mistakes, but still nothing beats machine generated code in type safety and correctness. – Groo Aug 08 '17 at 09:22
  • Unnecessarily too complex. – i486 Aug 08 '17 at 09:24
  • @i486: does this really mean this answer is "unhelpful" and needs to be downvoted? [X-macros](https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros) are not my invention (see [this thread](https://stackoverflow.com/q/6635851/69809) for many examples). They may be complex, but this is the only possible automatic solution to OP's problem which will compile on any standard compiler, as you can see from all other "helpful" answers in this thread. – Groo Aug 08 '17 at 09:30
  • @i486 It depends on your requirements really. X macros should always be the very last resort, but there's a place for them. If there is a requirement that the code must only be modified in one single place during maintenance, then this is an acceptable solution, unreadable as it might be. – Lundin Aug 08 '17 at 09:32