I found this exercise in an old book on C data structures and it gave the following problem prompt:
- Write a program that, given a structure definition, will generate a C function that allocates and initializes the structure (such a function is called a "constructor"). For example, given the input:
struct complex { double real; double imaginarry; }
function
create_complex()
will be created having two arguments of typedouble
.
I tried thinking of how to create such a program and my only solution is to use a file to get the struct input and create a new text file in which I write the function.
Is there any other way to approach this problem? I appreciate it.
CodePudding user response:
If you want to literally take the struct definition as input and output a function definition that (1) creates a struct of that type and (2) has an argument corresponding to each element of the structure, then you're correct: you'll need to read the source code as a text file and parse the definition manually. If going with this approach, I recommend using lex and yacc to generate the code that generates the code. Parsing code for an LR grammar like C has a lot of pitfalls
But if our goal is instead to create both the structure and function from a single definition, we can do that directly from the source code, using X-Macros. The trick to these is that we define our data structure in terms of function-like macros, e.g. #define X STRUCT(complex, ARG(double, real), ARG(double, imaginary))
but we don't define what those macros do yet. We then include a special header file.
The header defines the macros, then includes X
(that is, it has a literal X
in the file; it includes, rather than #includes), which is expanded according to the macro definitions. It then redefines the macros to do something different, and includes X
again, which in turn is expanded according to the new macro definitions. Thus, we only need to specify the definition once (and don't need to worry about information getting out of sync), but we generate all the different pieces of code that depend on it.
The following example shows how it works; we create both the complex
struct from your question, and also struct other { int a; double b; }
to show how it works with multiple type definitions.
struct_defn.h
#include <stdlib.h>
// Here, we expand X into a struct definition
#define STRUCT(NAME, ARGS) struct NAME { ARGS };
#define ARG(TYPE, NAME) TYPE NAME;
X
#undef ARG
#undef STRUCT
// Expand X into a function decl
#define STRUCT(NAME, ARGS) struct NAME *make_##NAME(struct NAME *out ARGS)
#define ARG(TYPE, NAME) , TYPE NAME
X
#undef ARG
#undef STRUCT
// Expand X into a function definition
#define STRUCT(NAME, ARGS) { \
if (!out) { \
out = malloc(sizeof(struct NAME)); \
if (!out) return NULL; \
} \
ARGS \
return out; \
}
#define ARG(TYPE, NAME) out->NAME = NAME;
X
#undef ARG
#undef STRUCT
#undef X
main.c
#include <stdio.h>
// We define each of the structures in terms of the X-Macros, then "call" the macro by including the file.
#define X STRUCT(complex, \
ARG(double, real) \
ARG(double, imaginary))
#include "struct_defn.X"
/* The above will expand to the following (but without the formatting)
*
* struct complex {
* double real;
* double imaginary;
* };
* struct complex *make_complex(struct complex *out, double real, double imaginary) {
* if (!out) {
* out = malloc(sizeof(struct complex));
* if (!out) return ((void *) 0);
* }
* out->real = real;
* out->imaginary = imaginary;
* return out;
* }
*
*/
#define X STRUCT(other, \
ARG(int, a) \
ARG(double, b))
#include "struct_defn.X"
int main() {
struct complex foo;
struct complex *bar = make_complex(NULL, 2.71828, 42.0);
make_complex(&foo, 3.14159265, 0.5);
struct other *baz = make_other(NULL, 4, 2);
printf("foo: %f, %f\nbar: %f, %f\nbaz: %d, %f\n",
foo.real, foo.imaginary,
bar->real, bar->imaginary,
baz->a, baz->b);
}