Reputation: 123
I have a struct
typedef struct
{
void *l_var;
void *r_var;
}EXPR;
EXPR expr;
I initialize it
expr.l_var = &motor_rt_params[0].position;
expr.r_var = &motor_rt_params[1].position;
Now I want to operate with the values
void SCRIPT_Process(void *l_var, void *r_var, uint32_t oper)
{
int32_t res;
switch (oper)
{
case OP_PLUS:
res = *((??? *) l_var) + *((??? *)r_var);
break;
case OP_MINUS:
res = *((??? *) l_var) - *((??? *)r_var);
break;
}
}
SCRIPT_Process(expr.l_var , expr.r_var , OP_PLUS);
The variables may be 32, 16, 8 bit. The question - how can I cast it to an appropriate type (instead of (??? *)) in run time?
Upvotes: 0
Views: 204
Reputation: 68013
safe and the same generic. No pointer punning. The type list in the union might be much longer
typedef union
{
int8_t u8;
int16_t u16;
int32_t u32;
}data_t;
int32_t get(data_t *o, int size)
{
switch (size)
{
case 8:
return o -> u8;
break;
case 16:
return o -> u16;
break;
default:
return o -> u32;
break;
}
}
void SCRIPT_Process(data_t *l_var, data_t *r_var, uint32_t oper, int sizel, int sizer)
{
int32_t res;
int32_t l = get(l_var, sizel);
int32_t r = get(r_var, sizer);
switch (oper)
{
case OP_PLUS:
res = l + r;
break;
case OP_MINUS:
res = l -r;
break;
/* ..... */
}
}
Upvotes: 1
Reputation: 141900
The variables may be 32, 16, 8 bit.
So you need to know:
And you need to pass that information along.
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
enum type_e {
TYPE_u8,
TYPE_u16,
TYPE_u32,
// TODO: add more, ex. TYPE_INT, TYPE_DOUBLE, etc.
};
enum oper_e {
OP_PLUS,
OP_MINUS,
// TODO: add mode, ex. OP_POW or OP_DIV etc.
};
void SCRIPT_Process(void *res, const void *l, const void *r, enum type_e type, enum oper_e oper)
{
switch (oper) {
case OP_PLUS:
switch (type) {
case TYPE_u8:
*(uint8_t*)res = *(uint8_t*)l + *(uint8_t*)r;
break;
case TYPE_u16:
*(uint16_t*)res = *(uint16_t*)l + *(uint16_t*)r;
break;
case TYPE_u32:
*(uint32_t*)res = *(uint32_t*)l + *(uint32_t*)r;
break;
default:
assert(0);
}
break;
case OP_MINUS:
// TODO:
assert(0);
}
}
int main() {
uint32_t l = 5, r = 2, res;
SCRIPT_Process(&res, &r, &l, TYPE_u32, OP_PLUS);
printf("%d + %d = %d\n", (int)l, (int)r, (int)res);
}
Would be nice to provide a macro to make the code more verbose and with less typing:
#define SCRIPT_PROCESS_MACRO(type, res, l, op, r) \
*(type*)res = *(type*)l op *(type*)r;
void SCRIPT_Process(void *res, void *l, void *r, enum type_e type, enum oper_e oper)
{
switch (oper) {
case OP_PLUS:
switch (type) {
case TYPE_u8:
SCRIPT_PROCESS_MACRO(uint8_t, res, l, +, r);
break;
case TYPE_u16:
SCRIPT_PROCESS_MACRO(uint16_t, res, l, +, r);
break;
case TYPE_u32:
SCRIPT_PROCESS_MACRO(uint32_t, res, l, +, r);
break;
default:
assert(0);
}
break;
case OP_MINUS:
// TODO:
assert(0);
}
}
Or even more simple with more macros, which makes adding new operations and types trivial:
#define SCRIPT_PROCESS_MACRO(type, res, l, op, r) \
*(type*)res = *(type*)l op *(type*)r;
#define SCRIPT_PROCESS_TYPE_CASES(type, res, l, op, r) \
switch (type) { \
case TYPE_u8: SCRIPT_PROCESS_MACRO(uint8_t, res, l, op, r); break; \
case TYPE_u16: SCRIPT_PROCESS_MACRO(uint16_t, res, l, op, r); break; \
case TYPE_u32: SCRIPT_PROCESS_MACRO(uint32_t, res, l, op, r); break; \
default: assert(0); break; \
}
void SCRIPT_Process(void *res, void *l, void *r, enum type_e type, enum oper_e oper)
{
switch (oper) {
case OP_PLUS:
SCRIPT_PROCESS_TYPE_CASES(type, res, l, +, r);
break;
case OP_MINUS:
SCRIPT_PROCESS_TYPE_CASES(type, res, l, -, r);
break;
}
}
Or you could even go with even more generic solution, having separate types for result, left and right operands:
void SCRIPT_Process(
void *res, enum type_e restype,
const void *l, enum type_e ltype,
const void *r, enum type_e rtype,
enum oper_e oper) {
if (restype == TYPE_u8 && ltype == TYPE_u32 && rtype == TYPE_u16 && oper == OP_ADD) {
*(uint8_t*)res = *(uint32_t*)ltype + *(uint16_t*)rtype;
} if ( // and so on so on so on so on ....
}
Upvotes: 0
Reputation: 32596
What about to also save the size when you initialize the pointers ?
typedef struct
{
void *l_var;
void *r_var;
size_t sz;
}EXPR;
expr.l_var = &motor_rt_params[0].position;
expr.r_var = &motor_rt_params[1].position;
expr.sz = sizeof(motor_rt_params[1].position);
allowing to to for instance
void SCRIPT_Process(void *l_var, void *r_var, size_t sz, uint32_t oper)
{
int32_t res;
switch (oper)
{
case OP_PLUS:
if (sz == sizeof(int32_t))
res = *((int32_t *) l_var) + *((int32_t *)r_var);
else /* suppose a int16_t */
res = *((int16_t *) l_var) + *((int16_t *)r_var);
break;
case OP_MINUS:
if (sz == sizeof(int))
res = *((int32_t *) l_var) - *((int32_t *)r_var);
else /* suppose a int16_t */
res = *((int16_t *) l_var) - *((int16_t *)r_var);
}
}
SCRIPT_Process(expr.l_var , expr.r_var , expr.sz, OP_PLUS);
supposing it is int or short only, I let you adding the case of a int8_t
The advantage also placing the size in the EXPR is to not lost that information / create inconsistencies by error because managed in different piece of code.
Or may be to give the EXPR rather than the fields separately in arguments to SCRIPT_Process
?
May be you also need to know if signed or unsigned, with an additional field, or using an int for the size valuing the positive size for unsigned (4, 2 or 1) and negative size for a signed (-4, -2 -1).
An other way is to save pointers to the right functions rather than the size, a kind of C++ virtual implementation.
Of course all of that supposes you cannot save values as int32_t
in the struct and you really need to save the pointers.
Upvotes: 2
Reputation: 249592
You can simply store the values in the void*
, rather than their address:
expr.l_var = (void*)motor_rt_params[0].position;
expr.r_var = (void*)motor_rt_params[1].position;
Then to use them, cast to intptr_t
(values, not pointers). These types are guaranteed to be the same width as a pointer, so you won't need to care anymore about the width of the source values.
Upvotes: 0