Reputation: 13140
I'm working in an embedded microcontroller and I have to read/write multiple types data to Non-Volatile RAM (NVRAM). I'd like to avoid having to write separate read/write functions for each value to store in NVRAM like this:
void Write_Value1(int value);
int Read_Value1(void);
void Write_Value2(unsigned long value);
unsigned long Read_Value2(void);
...
That is definitely cumbersome to maintain as the list of items to read/write grows. There's a couple of ways to tackle this in C++, templates for example. Using a template I could write a .Read()
and .Write(T value)
function that each item to read/write would have access to. Is there anyway to get this behavior from C? I suspect that there is some way, using some combination of void pointers and vtables. I'd like to handle this in a more polymorphic fashion and have the function for reading/writing each item to NVRAM called in a similar fashion.
Upvotes: 1
Views: 898
Reputation: 320661
You can [pretty closely] simulate C++ templates in C by using macros.
There are at least two way to do that.
First method: "Rubber stamp" macros
Define declare/define macros for your functions
#define DECLARE_READ_WRITE(suffix, type)\
type Read_Value_##suffix(void);
void Write_Value_##suffix(type val);
#define DEFINE_READ_WRITE(suffix, type)\
type Read_Value_##suffix(void) {\
/* your code */ \
}\
void Write_Value_##suffix(type val) {\
/* your code */ \
}
and then in some header file do
DECLARE_READ_WRITE(long, long)
DECLARE_READ_WRITE(int, int)
and in some implementation file
DEFINE_READ_WRITE(long, long)
DEFINE_READ_WRITE(int, int)
which will "generate" declarations and definitions for Read_Value_int
, Write_Value_int
, Read_value_long
and Write_value_long
.
Second method: Parameterized include files
Create two header files. One for declarations (read_write.h.template
)
TYPE__ CONCAT(Read_Value_, SUFFIX__)(void);
void CONCAT(Write_Value_, SUFFIX__)(TYPE__ val);
and one for definitions (read_write.c.template
)
TYPE__ CONCAT(Read_Value_, SUFFIX__)(void)
{
/* your code */
}
void CONCAT(Write_Value_, SUFFIX__)(TYPE__ val)
{
/* your code */
}
Here CONCAT
is a usual implementation of macro token concatenation (can/should be used in the first method as well).
And then include your "templated" code into an appropriate header file and implementation file as
#define TYPE__ int
#define SUFFIX__ int
#include "read_write.h.template"
#undef TYPE__
#undef SUFFIX__
#define TYPE__ long
#define SUFFIX__ long
#include "read_write.h.template"
#undef TYPE__
#undef SUFFIX__
The same thing in some implementation file with read_write.c.template
header.
The latter method has an added benefit of producing debuggable code. I.e. you can step through it in the debugger, just like it typically works with C++ templates.
Upvotes: 9
Reputation: 23680
You could do it with void pointer and sizes:
void write_value (void *data, size_t size);
void* read_value (size_t size);
Then:
write_value ( (void *) &int_val, sizeof (int));
write_value ( (void *) &ulong_val, sizeof (unsigned long));
intptr = read_value (sizeof (int));
charptr = read_value (sizeof (char));
The read_value
then needs to malloc
something and return it. This might just be overhead sometimes, when you'd rather read into an automatic. Then do:
/* Declaration. */
void read_value (void *data, size_t size);
/* Use. */
read_value (&automatic_var, sizeof (automatic_var));
And finally, you could 'simulate' templates in all sorts of ways.
Upvotes: 4
Reputation: 3980
There is no such thing in C. However, you could use a struct approach.
#define T_VOID 0
#define T_INT 1
#define T_ULINT 2
typedef struct
{
int type;
union
{
int a;
unsigned long b;
} my_union;
} my_struct;
void write_value(my_struct *s)
{
switch (s->type)
{
case T_VOID:
// do something
break;
case T_INT:
// do something with s->my_union.a
break;
case T_ULINT:
// do something with s->my_union.b
break;
}
}
In the same fashion, read_value
would return a my_struct
pointer.
Upvotes: 3
Reputation: 87486
Look at the Posix read
and write
functions and just do something similar:
void read(int address, void * data, int size);
void write(int address, const void * data, int size);
Then later:
read(address, &x, sizeof(x));
write(address, &x, sizeof(x));
Upvotes: 2
Reputation: 3638
Returning a different type based on the target type is (nearly?) impossible with C. You can mimic it with macros, though. For example:
#define WRITE(x) write_value(x, sizeof(x))
#define READ(x) read_value(&x, sizeof(x))
Now you can write write_value() and read_value() to copy data out based on length, taking into account endianness in your internal routine.
Upvotes: 3