Home > Software design >  How to access a struct property through a struct.variableWithNameOfTheProperty?
How to access a struct property through a struct.variableWithNameOfTheProperty?

Time:01-12

I am new on C and I am trying to insert data into a struct using "for" like this:


char *personAttributes[6] = {"cpf", "name", "sex", "dtBorn", "city", "UF"};
    int i;

    Person pw;

    typedef struct 
    {
        char cpf;
        char name;
        char sex;
        int dtBorn;
        char city;
        char UF;
    } Person;

    for (i = 0; i < 6; i  )
    {
        printf("Input person's %s: \n", personAttributes[i]);
        scanf("%[^\n]", &pw.personAttributes[i]);
    }

Instead of reading each struct property in multiple lines (one for each element of the struct, like: ..., &pw.cpf, &pw.name...), I'd like to do it dynamically, by passing parameter in a for and accessing it through its index in an array.

I tried to do something like this:

scanf("%[^\n]", &pw.personAttributes[i]);

but this way of accessing apparently does not work. Is there any way I can do something similar in C?

CodePudding user response:

(edit: other posters have reminded me of the offsetof macro that makes this much more humane)

C doesn't automatically know how to do this. You want your program to know, at run-time, what the attributes of Person are called ("cpf", "name", etc.). However, when you're programming in C, your program doesn't know that. The compiler knows what things are called, but it doesn't store that information in your compiled program. In dynamic languages like Python, there's less separation between the compiler and your program, and you can do things like this. But in C, what you write is what you get.

However, this is still achievable with a little extra work. You just need to explicitly tell your program what it needs to know. Your program needs to know what the name of each attribute is, how to read it, and where it's stored in the struct. Since the compiler doesn't store this information in your program automatically, you need to store it explicitly in your code, like this:

#include "stdio.h"
#include "stddef.h"
#include "stdint.h"

typedef struct 
{
    char cpf[80];
    char name[80];
    char Sex[80];
    int32_t dtBorn;
    char city[80];
    char UF[80];
} Person;

typedef struct
{
    char *label;  // What the attribute is called
    char *scanf_pattern;  // How to read it with scanf
    ptrdiff_t offset;  // Where in the struct the attribute is located
} AttributeInfo;

static const AttributeInfo attributes[] = {
    {"cpf", " y[^\n]s", offsetof(Person, cpf)},
    {"name", " y[^\n]s", offsetof(Person, name)},
    {"Sex", " y[^\n]s", offsetof(Person, Sex)},
    {"dtBorn", "%d", offsetof(Person, dtBorn)},
    {"city", " y[^\n]s", offsetof(Person, city)},
    {"UF", " y[^\n]s", offsetof(Person, UF)}
};

Now the attributes constant has all the extra information you need. For each attribute, it remembers the name of the attribute, the scanf format string for the attribute, and the offset in bytes from the start of the Person struct to where that attribute is (that's what the offsetof macro computes for us; https://en.wikipedia.org/wiki/Offsetof has more info on that).

Now, we've saved the extra information we need in attributes. Even at run-time, our program has the stored information needed to do what you want to do. We just need to explicitly use it like so:

int main(int argc, char **argv) {
    Person pw;

    for (size_t i = 0; i < sizeof(attributes) / sizeof(AttributeInfo); i  )
    {
        AttributeInfo attribute = attributes[i];
        printf("Input person's %s: \n", attribute.label);
        int success = scanf(attribute.scanf_pattern, (void*) &pw   attribute.offset);
        if (success != 1) {
            printf("Not valid; try again.\n");
            scanf("%*[^\n]s");
            --i;
        }
    }
    printf("cpf: %s\n", pw.cpf);
    printf("name: %s\n", pw.name);
    printf("Sex: %s\n", pw.Sex);
    printf("dtBorn: %d\n", pw.dtBorn);
    printf("city: %s\n", pw.city);
    printf("UF: %s\n", pw.UF);
}

When we compile and run the program, we will get:

$ gcc foo.c -o foo
...

$ ./foo
Input person's cpf: 
clam protection factor
Input person's name: 
Super Foonly
Input person's Sex: 
undecided
Input person's dtBorn: 
yesterday
Not valid; try again.
Input person's dtBorn: 
19700101
Input person's city: 
Anytown
Input person's UF: 
ulterior freshness
cpf: clam protection factor
name: Super Foonly
Sex: undecided
dtBorn: 19700101
city: Anytown
UF: ulterior freshness

Your question was clear about what you were asking. However, here are some issues you should be aware of that your original version had:

  • cpf, name, etc. were only single characters, not strings (arrays of characters). I don't know what "cpf" and "UF" are, but I assume you want name, city and Sex to be strings.
  • Your scanf format didn't have a width specifier (the 79 in " y[^\n]s" above). This lets someone crash or exploit your program by typing in a longer string than you expected. Have a look at http://www.crasseux.com/books/ctutorial/String-overflows-with-scanf.html for more discussion of this issue.

And I understand that these may have been for example purposes and intentional on your part, but just in case they weren't:

  • You didn't have a main() method. You can't just put a for-loop in your .c file at top level; it needs to be inside a function.
  • It's best not to hardcode loop lengths (the 6 in your for-loop). See https://en.wikipedia.org/wiki/Magic_number_(programming) for more info on this rule of programming.

Looks like you're making the mistakes that everyone makes when they're learning C for the first time. But what you wanted to do is doable and reasonable, and I hope this helps communicate how it could be done. You might want to come back to this after you've spent a little more time thinking about pointer math, depending on your comfort level. But no worries! You'll have it in no time.

CodePudding user response:

There is no way to iterate over members of a struct using an array of strings (as the names of your members are generally only available at compile-time). You can obtain a pointer to each member using their (identifier) name but it's not going to save you any effort. If you need to iterate use an array, or you can could write functions to access the correct location of an array of values.

Also, some of struct's members that are of the type char (single letter) should probably be char * (string).

CodePudding user response:

There is no simple way to make that work and no direct support in C for anything similar. You can get vaguely close if you build a table with the names of the elements, the offsetof() and sizeof each member names. (The offsetof macro is defined in <stddef.h>.) But there are problems with how to encode the type of each member, and how to access the data. You can write code that's adequate for the types your structures use, but it is hard to make it fully general — if only because structures can be arbitrarily diverse.

Look at these two questions for some ideas— but there is no simple way of dealing with it:

C23 will introduce two new keywords — typeof and typeof_unqual (see N3054 §6.7.2.5 Typeof specifiers on p114). It is not immediately clear whether they could be used in support of this. They are type specifiers in the language and come into effect long after the preprocessor is done (so I don't think that you'd be able to convert the result into a string; stringization is a preprocessor operation but typeof can probably only be evaluated by the main compiler, not the preprocessor).

  • Related