Each time I want to print or initialize a struct I have to go through each member making the code not very reusable. Is there a way to do this in a for, while or do while lo
Here is an example using offsetof()
. The code isn't complete, but what's present does compile, assuming you use a C99 or C11 compiler. The code isn't minimal in places — notably the val_integer()
and fmt_integer()
functions. You could avoid the base
and ptr
variables in each by writing a complex expression.
The scenario it is based on is 'read a configuration from a text file into a structure'. The lines in the file can be blank or contain a comment (#
to end of line), or a configuration parameter in the form:
NAME_OF_PARAMETER value-for-parameter
The name is followed by white space and the value. Different configuration elements are of different types. The code reads the line, splits non-comment lines into a key (parameter name) and value, and then calls code to convert the value to the correct type for the key. In the configuration file this scheme is based on, there were about three hundred configuration parameters, and different parameters could have different valid ranges, and defaults, and so on, so the set of types was considerably larger, therefore, but this is good enough for illustrative purposes. The 'real' scheme was considerably more complex than this as there were about 30 pairs of validation and formatting functions — but it replaced a single function with over 6,000 lines in stylized set of 300 units of 20 lines each with a vastly smaller source file of less than a thousand lines in total and no function larger than about 20 lines (all size figures approximations).
#include <stddef.h>
#include <stdio.h>
/* #include "xxconfig.h" // would define struct XX_Config */
typedef struct XX_Config
{
int xx_version_major;
int xx_version_minor;
char *xx_product_name;
int xx_max_size;
int xx_min_size;
double xx_ratio;
/* And so on for several hundred configuration elements */
} XX_Config;
typedef enum TypeCode
{
T_INT,
T_DOUBLE,
T_CHARPTR,
T_CHARARR,
/* ...other types as needed */
} TypeCode;
typedef struct Descriptor Descriptor;
typedef struct TypeInfo TypeInfo;
typedef int (*Validator)(char *buffer, const Descriptor *descr, void *data);
typedef int (*Formatter)(char *buffer, size_t buflen, const Descriptor *descr, void *data);
struct TypeInfo
{
TypeCode type;
Validator valid;
Formatter format;
};
struct Descriptor
{
TypeCode type;
size_t offset;
char *name;
};
extern int val_integer(char *buffer, const Descriptor *descr, void *data);
extern int val_double(char *buffer, const Descriptor *descr, void *data);
extern int val_charptr(char *buffer, const Descriptor *descr, void *data);
extern int fmt_integer(char *buffer, size_t buflen, const Descriptor *descr, void *data);
extern int fmt_double(char *buffer, size_t buflen, const Descriptor *descr, void *data);
extern int fmt_charptr(char *buffer, size_t buflen, const Descriptor *descr, void *data);
extern void err_report(const char *fmt, ...);
extern int read_config(const char *file, XX_Config *config);
extern int print_config(FILE *fp, const XX_Config *config);
/* Would be static - but they're not defined so they need to be extern */
extern int is_comment_line(char *line);
extern Descriptor *lookup(char *key);
extern int split(char *line, char **key, char **value);
static TypeInfo info[] =
{
[T_CHARPTR] = { T_CHARPTR, val_charptr, fmt_charptr },
[T_DOUBLE] = { T_DOUBLE, val_double, fmt_double },
[T_INT] = { T_INT, val_integer, fmt_integer },
// ...other types as needed
};
static Descriptor xx_config[] =
{
{ T_INT, offsetof(XX_Config, xx_version_major), "xx_version_major" },
{ T_INT, offsetof(XX_Config, xx_version_minor), "xx_version_minor" },
{ T_CHARPTR, offsetof(XX_Config, xx_product_name), "xx_product_name" },
{ T_INT, offsetof(XX_Config, xx_max_size), "xx_max_size" },
{ T_INT, offsetof(XX_Config, xx_min_size), "xx_min_size" },
{ T_DOUBLE, offsetof(XX_Config, xx_ratio), "xx_ratio" },
};
enum { NUM_CONFIG = sizeof(xx_config) / sizeof(xx_config[0]) };
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
extern const char jlss_id_offsetof_c[];
const char jlss_id_offsetof_c[] = "@(#)$Id$";
#endif /* lint */
int read_config(const char *file, XX_Config *config)
{
FILE *fp = fopen(file, "r");
if (fp == 0)
return -1;
char line[4096];
while (fgets(line, sizeof(line), fp) != 0)
{
if (is_comment_line(line))
continue;
char *key;
char *value;
if (split(line, &key, &value) == 0)
{
Descriptor *desc = lookup(key);
if (desc == 0)
{
err_report("Do not recognize key <<%s>>\n", key);
continue;
}
TypeCode t = desc->type;
if ((*info[t].valid)(value, desc, config) != 0)
{
err_report("Failed to convert <<%s>>\n", value);
}
}
}
fclose(fp);
return 0;
}
int print_config(FILE *fp, const XX_Config *config)
{
for (int i = 0; i < NUM_CONFIG; i++)
{
char value[256];
TypeCode t = xx_config[i].type;
if ((*info[t].format)(value, sizeof(value), &xx_config[i], (void *)config) == 0)
fprintf(fp, "%-20s %s\n", xx_config[i].name, value);
}
return 0;
}
int val_integer(char *buffer, const Descriptor *descr, void *data)
{
int value;
if (sscanf(buffer, "%d", &value) != 1)
{
err_report("Failed to convert <<%s>> to integer for %s\n", buffer, descr->name);
return -1;
}
char *base = data;
int *ptr = (int *)(base + descr->offset);
*ptr = value;
return 0;
}
int fmt_integer(char *buffer, size_t buflen, const Descriptor *descr, void *data)
{
char *base = data;
int *ptr = (int *)(base + descr->offset);
int nbytes;
if ((nbytes = snprintf(buffer, buflen, "%d", *ptr)) < 0 || (size_t)nbytes >= buflen)
{
err_report("Failed to format %d into buffer of size %zu\n", *ptr, buflen);
return -1;
}
return 0;
}
Like I said in a comment:
For practical purposes, no [there isn't an easy way to use a
for
loop to step through the elements of a structure to print or initialize them]. You can do it (look upoffsetof()
in<stddef.h>
), but setting it up to work properly is hard work — much harder work than accepting that you'll need print each member in turn, and usually for minimal benefit.
In the context of the original scenario, there was a benefit to the revised code — 5000 deleted lines of benefit!