Keith said:
How would your proposal handle this on hardware with strict alignment
requirements?
#include <stdio.h>
void func(int *ptr) {
printf("*ptr = %d\n", *ptr);
}
int main(void) {
memstruct foo {
char c;
int i;
};
memstruct foo obj = {'x', 42};
func(&obj.i);
return 0;
}
The problem: the `i` member of `memstruct foo` may be misaligned, but
`func()` has no way of knowing that.
memstruct != struct
This is evolving as I go along here. Originally I thought it best
to be able to dereference a memstruct member as "obj.i" in a read
context to mean "copy (probably misaligned) bytes for i into a properly
aligned location which can then be read normally". Since this
destination might be, and in this case probably would be, a register,
the "&obj.i" would throw a compiler warning. However, I'm now thinking
it might be better simply to never allow a direct dereferencing of a
memstruct or one of its members at all, except to the extent it defines
a location in memory that is the location of another memstruct pointer
(or, of course, when it maps data 1:1 to the corresponding normal
struct). In which case in the example above it would still throw a
compiler warning, since it would be trying to pass a (pointer to
memstruct of an int), not a (pointer to an int), and the two are not
compatible. The second method is a bit peculiar in that "obj.i" of a
memstruct is something that has an address but does not have a value per
se. That is exactly what is going on now on some platforms, though,
where a misaligned uint32_t does have an address, but it is not one that
may be dereferenced. The advantage of putting this in the compiler is
that the situation would be obvious on all platforms, whereas on an x86
platform now this condition would not usually even
cause a hiccup.
That is
#include <stdio.h>
struct accumulator {
uint32_t N;
int32_t sumN;
}
struct foo {
uint8_t c;
struct accumulator acc;
uint32_t i;
};
memstruct foo memfoo; /* defines memfoo memstruct type */
memstruct accumulator memaccumulator;
void func(int *ptr) {
printf("*ptr = %d\n", *ptr);
}
void func2(memstruct memacumulator *acc){
struct accumulator anacc;
/* copy data to the corresponding struct. This is
one allowed form of dereferencing */
anacc = *acc;
printf("N:%d sumN:%d\n",anacc.N, anacc.sumN;
/* this is OK, other allowed form of "dereferencing",
actually just used to specify an address. No type
problem with memstruct * to void * */
func3("Location of acc->N = ",&acc->N);
/* this is NOT OK, memstruct * to int * */
func(&acc->N);
}
void func3(char *string, void *ptr) {
printf("%p\n", string, *ptr);
}
int main(void) {
struct foo obj;
memstruct memfoo memobj; /* this form _uses_ memfoo type */
/* fill memobj, equivalent to: memobj = {'x', {3,123}, 42};
obj = memobj; /* copy data memstruct -> struct */
func(&obj.i); /* no issue, this is a normal struct */
/* pass address of acc within this memstruct as
pointer to memstruct memaccumulator. Types match,
so no issue. */
func2(&memobj.acc);
/* Next is not allowed, no dereferencing a memstruct
field directly */
printf("memobj i field:%d\n",memobj.i);
return 0;
}
I see now what some of you were getting at about byte order and floats.
Originally I expected the programmer to have routines to further clean
up obj after it is loaded from memobj, since that is what the current
code which triggered this thread does. But it would make sense for the
compiler to handle all of that that sort of thing
at the
obj = memobj;
line. That is, not just copy position and size in memory, but some
types of value to value conversion as well. That would need a couple of
pragmas, one for the byte order of the data in memory
(Big/End/Other/Native), and one for (IEEE float/not IEEE/Native), with
the defaults being whatever is "Native" for each. In some cases end
user code would
still be needed, but only if "Native" was an odd machine.
Regards,
David Mathog