Il 08/04/2011 03:49, Eric Sosman ha scritto:
>> I need to declare const structures to save precious RAM space on
>> my embedded platform, but I can't initialize whatever member of
>> unions/structs (a limit of the compiler). So my real scheme doesn't
>> use unions but a struct/substruct approach:
>>
>> typedef struct {
>> signed char *ptr;
>> signed char min;
>> signed char max;
>> } scnum_t;
>>
>> typedef struct {
>> unsigned char *ptr;
>> unsigned char min;
>> unsigned char max;
>> } ucnum_t;
>>
>> /*...*/
>>
>> typedef struct {
>> enum {
>> NUM_TYPE_SCHAR,
>> NUM_TYPE_UCHAR,
>> NUM_TYPE_SSHRT,
>> NUM_TYPE_USHRT,
>> NUM_TYPE_SINT,
>> NUM_TYPE_UINT,
>> NUM_TYPE_SLONG,
>> NUM_TYPE_ULONG,
>> } type;
>> void *subnum; /* Pointer to scnum_t, ucnum_t, ... */
>> } num_t;
>>
>> In this case I can write:
>>
>> signed char temp; /* The variable */
>> const scnum_t snTemperature = {&temp, -10, +10 };
>> const num_t nTemperature = { NUM_TYPE_SCHAR,&snTemperature };
>
> And you do all this to "save precious RAM?" Ye gods! You plan
> to "save" RAM by replacing every variable-and-two-limits with a
> variable *and* a struct containing the limits *and* a pointer to
> the variable *and* a type code?
Oh, I'm sorry, I created some confusions on this point.
I need to declare limits and other stuff as constants to save RAM space
(when I declare const variables they are stored in Flash memory) and
because their values don't change at runtime.
I posted some functions based on structs with unions member, but I can't
use unions in constant struct with my compiler, because it doesn't
accept new initialization syntax with the name of the member.
In other words I can't do:
const struct {
int type;
union {
unsigned char x;
unsigned int y;
} u;
} mystruct = { 0, { .y=3 } };
even if it is perfectly legal for a C99 compiler. So I invented the
struct/substruct mechanism to declare constant structs of different
types with my limited compiler, not to save space.
You wrote you don't waste space declaring limits as long even for char,
short and int variables because my original approach used unions. And
you were right.
I just wanted to note that with my real approach (struct and substruct)
I effectively save some space (in Flash memory) respect your choice to
use always long.
But I have also said that Flash memory is more than sufficient, so this
(waste a small amount of Flash memory with long limits) is a minor drawback.
>> With an additional pointer (subnum in num_t), I can declare min/max
>> values as the correct type, so without losing space declaring long or
>> unsigned long min/max.
>
> R-i-i-i-g-h-t.
>
>> Most of the variables have small values, so they are already declared
>> as signed/unsigned char/short. In that case, on a 16-bit platform
>> (like the one I'm using), I need one num_t struct (4 bytes) and one
>> "subnum" struct (4 bytes).
>
> Plus the variable itself, for 9 bytes in all (for chars). If
> you jettisoned the damn pointers and the type code and just put the
> value and limits into one struct, you'd use 3 bytes per variable --
> maybe 4, if the compiler pads structs rather freely -- instead of
> the 9 you use in your current scheme (*if* it's truly your current
> scheme, and not yet another "small variation").
>
> If RAM is so "precious" that you eagerly seek ways to triple
> your usage thereof, I'd like to sell you a truly precious bridge.
I'm sorry also for this confusion. I was comparing my struct/substruct
approach with the correct types (8 bytes for chars):
typedef struct {
signed char *ptr;
signed char min;
signed char max;
} scnum_t;
typedef struct {
unsigned char *ptr;
unsigned char min;
unsigned char max;
} ucnum_t;
...
typedef struct {
enum {
NUM_TYPE_SCHAR,
...
} type;
void *subnum; /* Pointer to scnum_t, ucnum_t, ... */
} num_t;
with your first suggestion (to not use unions but only long, 12 bytes
for chars):
typedef struct {
enum {
NUM_TYPE_SCHAR,
NUM_TYPE_SSHRT,
NUM_TYPE_SINT,
NUM_TYPE_SLONG,
} type;
void *ptr; /* pointer to the variable */
signed long min;
signed long max;
} numSigned;
... the same for numUnsigned
I wasn't considering your last suggestion to separate values and limits
with a different API (passing the values as longs to the functions).
>> Anyway this isn't a very big problem, because I have a lot of "const"
>> space in non-volatile memory.
>
> Please reconcile "isn't a very big problem" with "precious."
Isn't a big problem to waste Flash memory, is a big problem to waste
RAM. So I'd like to declare consts where it is possible, for example for
limits that don't change.
> Also, please explain why you need upper/lower range limits to bracket
> the values of const variables.
Limits are constants, not values of the variables to must be keeped
inside those limits.
>>> You could go ahead with a three-quarters-reduced version of
>>> your current scheme, but now that the possibilities are down to two
>>> (signed vs. unsigned) I'd be strongly tempted to say "Two is a
>>> manageable number; I'll handle the two types separately." This is
>>> optional, of course, but it has the advantage of eliminating all
>>> those `if's you find so objectionable. You'd get something like
>>>
>>> typedef struct {
>>> signed long val;
>>> signed long min;
>>> signed long max;
>>> } Signed;
>>>
>>> typedef struct {
>>> unsigned long val;
>>> unsigned long min;
>>> unsigned long max;
>>> } Unsigned;
>>
>> I can't do in this way. The variables are also declared and used in
>> several parts of the code and in different modules I can't touch.
>> So the numeric value can't reside inside Signed/Unsigned struct.
>> I have to use a pointer to the variable.
>
> You don't know how to point at an element of a struct?
I have many modules (I can't touch) that uses internally many variables
and export them in a .h file (with extern keyword).
I need to change these extern variables with my functions, but I can't
move the declarations of those variables.
>> typedef struct {
>> enum {
>> NUM_TYPE_SCHAR,
>> NUM_TYPE_SSHRT,
>> NUM_TYPE_SINT,
>> NUM_TYPE_SLONG
>> } type;
>> void *ptr; /* Pointer to signed char/short/int as in type
>> */
>> signed long min;
>> signed long max;
>> } Signed;
>>
>> typedef struct {
>> enum {
>> NUM_TYPE_UCHAR,
>> NUM_TYPE_USHRT,
>> NUM_TYPE_UINT,
>> NUM_TYPE_ULONG
>> } type;
>> void *ptr; /* Pointer to unsigned char/short/int as in
>> type */
>> unsigned long min;
>> unsigned long max;
>> } Unsigned;
>>
>> But, in this case, the compiler can't give me warning if I wrongly
>> assign to ptr member of Signed/Unsigned struct a variable with a
>> different type as indicated in the type member.
>
> Under your current (?) scheme(s?), the compiler can't warn you
> about *any* mismatches. Failure to warn about *some* mismatches
> is therefore an improvement.
With the struct/substruct approach (see above), I can be advised if I
wrongly assign an incompatible pointer to substruct member ptr:
int temp;
const scnum_t sTemperature = { &temp, -10, +10 };
const num_t Temperature = { NUM_TYPE_SCHAR, &sTemperature };
Here temp is int, but scnum_t struct want a signed char pointer, so the
compiler can warn me.
With the second approach (with void *ptr in Signed and Unsigned structs,
see above), the compiler can't warn me of incompatible types.
But I must say this second approach is a my personal idea (borned after
your suggestions), not yours.
>> Also this can be a minor problem (in my struct/substruct approach
>> above, I have the same problem with subnum that is void*).
>
> Non capisco.
With struct/substruct approach (see above) the compiler can warn me
about incompatible pointer types for ptr member of substructs, but can't
warn me about incompatible pointer of subnum pointer of num_t struct,
like in the following example:
int temp;
const scnum_t sTemperature = { &temp, -10, +10 };
const num_t Temperature = { NUM_TYPE_SINT, &sTemperature };