So you want to use the struct, with the function call, as you would use an associative array in a high level language (HLL)?
For example, in JavaScript you can use:
foo["bar"] = baz;which is equivalent to
foo.bar = baz;and this will set the property bar to the value of baz.
In C, we need to construct all the data structures to hold these.
This might be a good lesson on the internals of HLLs --
On the one hand, it's a PITA to do in C. On the other, it may give you a great appreciation for all the hard work HLLs have to do, and why you might give them more patience for being slow, when they're so powerful. (and really, with the aggressive optimizations used in the most prominent languages, they're not very slow at all; an extremely impressive feat!)
One way JS might do this, is to store an "Object" as a structure in memory. The structure contains a type descriptor, so the interpreter can tell what it's looking at; and a pointer to the data thus described. The null object might have type 0 (and the data is irrelevant because it's never read), int 1, etc. Arrays, other Objects, and hybrids (mixed sets of objects, arrays or primitives) can be enumerated as well.
When we ask about properties of an object, we might be referring to an Object of, say, PropertyArray type, which contains key-value pairs. Say we do:
foo.one = "Apple";
foo.two = "Banana";
foo.three = "Cherry";
The interpreter might execute this and construct a PropertyArray whose data might be written in C like:
PropertyArray foo_p = {
struct {char* key = "one", char* value = "Apple"},
struct {char* key = "two", char* value = "Banana"},
struct {char* key = "three", char* value = "Cherry"}
}
When foo is accessed later,
console.log(foo.one);
foo.one = "Avocado";
a function is called which reads this PropertyArray, checking for the specified key, and returns or assigns a new value:
printf("%s", getPropertyValueString(&foo, "one");
setPropertyValue(&foo, "one", "Avocado");
...I take it you're interested in the latter operation, then?
We might break down the setting operation as getting a pointer to the PropertyArray's value member, and modifying it:
char** tmp = getPropertyValue_p(&foo, "one"); // found key; returns pointer to foo_p[0].value
mark_for_garbage_collection(*tmp);
*tmp = malloc(strlen("Avocado"));
strcpy(*tmp, "Avocado");
(Note we might be keen to mark the now-slightly-orphaned object for garbage collection. That is, to check if it has any other pointers to it in current context, and to free it if it's now completely orphaned. Garbage collection can also be done on its own, without such tips; it's up to the design of the VM. In C, we need to do this, explicitly, and carefully. Never free()ing the object results in a leak, while free()ing it unconditionally may leave a dangling pointer. Both are dangerous. So you can see, there is a lot of value added by automatic garbage collection.)
To further implement this, we need some way to store properties. If we are using dynamic memory (as in the HLL case), the PropertyArray can be anywhere in memory, and as we add and remove items from it, its size can vary; this is a normal dynamic array operation, the implementation of which can be found anywhere. We keep track of it with foo.
If we are allocating static types and properties, we might use a fixed array, and perhaps also split it into two arrays (char* keys[NUM_KEYVALUEPAIRS] and char* values[NUM_KEYVALUEPAIRS]) which can be a minor optimization. We might merge both into foo's type, so that it's a pair of fixed-length arrays. Or we might use a struct with useful member names, the same names being enumerated in the keys array, and the value array then gets offsets of foo's members; and perhaps their type or size/length as well, depending on what types and how general you want to get.
If the member assignments should have side-effects, you can also put together an array of function pointers; this reduces your stack of if-statements to a simple loop on strcmp, then performing the one operation on the element thus located.
A note on security: if any of these should be somehow user-writable, be very careful about buffer overruns, in static, dynamic (heap) or stack memory; and be especially careful of executing function pointers from RAM. A buffer overflow exploit is one thing; or jumping to a null pointer, another; but literally jumping to a potentially-user-controlled pointer is just putting it on a silver platter! If these data structures should be static, putting them in read-only memory (when an MMU is present) can help. (For global variables, set them as const, and any other attributes as necessary for the platform -- for example, AVR uses PROGMEM to place variables in Flash memory, and requires accessor functions to utilize. For functions, set them as static const, which will place them in global data rather than on the stack.)
Tim