Memory alignment

T

Thomas Stegen

Is the following code valid? As I read the standard it is valid.
The only thing that can be difficult in general is finding the
minimum size of LARGE_ENOUGH. Or am I missing something?

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define LARGE_ENOUGH 4096

int main(void)
{
void *ptr;
long *l;
short *s;

unsigned i = 0;

ptr = malloc(LARGE_ENOUGH);

if(ptr == NULL)
{
puts("malloc failed.");
return EXIT_FAILURE;
}

l = ptr;
*l = LONG_MAX;
s = ptr;

for(i = 0; (void *)(l + 1) < (void *)s; s++)
{/*empty*/}

*s = SHRT_MAX;
printf("l = %ld, s = %hd.\n", *l, *s);

return 0;
}
 
M

Malcolm

Thomas Stegen said:
for(i = 0; (void *)(l + 1) < (void *)s; s++)
{/*empty*/}
Without the casts to (void *) the comparison would be illegal (because a
short * may have padding bits if the machine architecture addresses only 32
bit bytes, and mixed padding / raw arithmetic is meaningless). I think it is
strictly illegal anyway because arithmetic isn't allowed on void *s, but
it's something that compilers would typically allow.
 
M

Martin Dickopp

Thomas Stegen said:
Is the following code valid? As I read the standard it is valid.
The only thing that can be difficult in general is finding the
minimum size of LARGE_ENOUGH. Or am I missing something?

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define LARGE_ENOUGH 4096

int main(void)
{
void *ptr;
long *l;
short *s;

unsigned i = 0;

ptr = malloc(LARGE_ENOUGH);

if(ptr == NULL)
{
puts("malloc failed.");
return EXIT_FAILURE;
}

l = ptr;
*l = LONG_MAX;
s = ptr;

for(i = 0; (void *)(l + 1) < (void *)s; s++)
{/*empty*/}

*s = SHRT_MAX;
printf("l = %ld, s = %hd.\n", *l, *s);

return 0;
}

Assuming that LARGE_ENOUGH is indeed large enough (I think
`sizeof(long)+2*sizeof(short)-1' would a valid bound that works even in
pathological cases like `sizeof(long)>4096'), I see nothing obviously
wrong with this code. Which part specifically do you think could be
problematic?

BTW, what's the purpose of `i'? Unless I'm missing something, you
initialize it to zero, and later assign zero to it again, but you don't
use it otherwise.

Martin
 
E

Eric Sosman

Thomas said:
Is the following code valid? As I read the standard it is valid.
The only thing that can be difficult in general is finding the
minimum size of LARGE_ENOUGH. Or am I missing something?

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define LARGE_ENOUGH 4096

int main(void)
{
void *ptr;
long *l;
short *s;

unsigned i = 0;

ptr = malloc(LARGE_ENOUGH);

if(ptr == NULL)
{
puts("malloc failed.");
return EXIT_FAILURE;
}

l = ptr;
*l = LONG_MAX;
s = ptr;

for(i = 0; (void *)(l + 1) < (void *)s; s++)

I think you meant `>=' rather than `<'. (I was also
a little surprised that the compiler accepts this: it's
not possible to subtract two `void*', so I expected the
relational operators not to work, either. Well, you learn
something every day ...)
{/*empty*/}

*s = SHRT_MAX;
printf("l = %ld, s = %hd.\n", *l, *s);

Undefined behavior because `s' and `l' both point to
the start of the allocated area, so writing to `*s' may
have corrupted the value of `*l' and made it indeterminate.

But if the loop condition above were changed, I think
all would be well.
return 0;
}

A portable way to determine LARGE_ENOUGH would be

struct fake { long l; short s; };
#define LARGE_ENOUGH sizeof(struct fake)
 
T

Thomas Stegen

Eric said:
I think you meant `>=' rather than `<'.

Yeah, next time I'll choose numbers that don't have a common
set of bits that happen to overlap... Better for testing that
way :)

(I was also
a little surprised that the compiler accepts this: it's
not possible to subtract two `void*', so I expected the
relational operators not to work, either. Well, you learn
something every day ...)

Well, I checked the standard after writing this and the
two pointers compared must point to compatible incomplete types.
void * meets that criteria.
Undefined behavior because `s' and `l' both point to
the start of the allocated area, so writing to `*s' may
have corrupted the value of `*l' and made it indeterminate.

Yep.


A portable way to determine LARGE_ENOUGH would be

struct fake { long l; short s; };
#define LARGE_ENOUGH sizeof(struct fake)

I thought about this, but I wasn't sure if the alignment requirements
would be the same. Thinking about it again it seems natural that
they should be though. (Don't know if pathological implementation are
allowed...)
 
T

Thomas Stegen

Martin said:
Thomas Stegen <[email protected]> writes:
Assuming that LARGE_ENOUGH is indeed large enough (I think
`sizeof(long)+2*sizeof(short)-1' would a valid bound that works even in
pathological cases like `sizeof(long)>4096'), I see nothing obviously
wrong with this code. Which part specifically do you think could be
problematic?

I think it is valid, but it is a bit strange so I thought I'd ask :)
BTW, what's the purpose of `i'? Unless I'm missing something, you
initialize it to zero, and later assign zero to it again, but you don't
use it otherwise.

I had it in for a reason, but I changed the code so it was not needed
anymore and I forgot to take it out.
 
T

Thomas Stegen

Malcolm said:
Without the casts to (void *) the comparison would be illegal (because a
short * may have padding bits if the machine architecture addresses only 32
bit bytes, and mixed padding / raw arithmetic is meaningless). I think it is
strictly illegal anyway because arithmetic isn't allowed on void *s, but
it's something that compilers would typically allow.

There is no arithmetic on pointers to void here... (relational operators
can be implemented in terms of arithmetic, but that arithmetic is not
part of the abstraction.)
 
M

Martin Dickopp

Malcolm said:
Without the casts to (void *) the comparison would be illegal (because a
short * may have padding bits if the machine architecture addresses only 32
bit bytes, and mixed padding / raw arithmetic is meaningless). I think it is
strictly illegal anyway because arithmetic isn't allowed on void *s, but
it's something that compilers would typically allow.

I see no arithmetic on `void *' here, and I can find nothing in the
definition of the relational operators which would make this invalid.
But maybe I'm missing something; could you elaborate why you think it's
invalid?

Martin
 
S

Stephen L.

Thomas said:
Is the following code valid? As I read the standard it is valid.
The only thing that can be difficult in general is finding the
minimum size of LARGE_ENOUGH. Or am I missing something?

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define LARGE_ENOUGH 4096

int main(void)
{
void *ptr;
long *l;
short *s;

unsigned i = 0;

ptr = malloc(LARGE_ENOUGH);

if(ptr == NULL)
{
puts("malloc failed.");
return EXIT_FAILURE;
}

l = ptr;
*l = LONG_MAX;
s = ptr;

for(i = 0; (void *)(l + 1) < (void *)s; s++)
{/*empty*/}

*s = SHRT_MAX;
printf("l = %ld, s = %hd.\n", *l, *s);

return 0;
}


You know, after

`for(i = 0; (void *)(l + 1) < (void *)s; s++)'

`s' contains the same value as `l'. So, the

`*s = SHRT_MAX;'

overwrites all or part of the original value for `*l'.
So I'm not sure what you're trying to ask in the
sample code...

-
Stephen
 
D

Dan Pop

In said:
I think you meant `>=' rather than `<'.

Why do you want to remain in the loop when (void *)(l + 1) == (void *)s ?
AFAICS, the right operator is '>'.

Or am I missing something?
(I was also
a little surprised that the compiler accepts this: it's
not possible to subtract two `void*', so I expected the
relational operators not to work, either. Well, you learn
something every day ...)

If they're pointers to compatible types and they do point inside the same
object, why have any doubts about the correctness of the expression?

Dan
 
E

Eric Sosman

Thomas said:
Eric said:
[...]
A portable way to determine LARGE_ENOUGH would be

struct fake { long l; short s; };
#define LARGE_ENOUGH sizeof(struct fake)


I thought about this, but I wasn't sure if the alignment requirements
would be the same. Thinking about it again it seems natural that
they should be though. (Don't know if pathological implementation are
allowed...)

sizeof(struct fake) may well be more than is necessary,
but cannot be too small. A possibly more parsimonious
alternative might be

#define LARGE_ENOUGH(offsetof(struct fake, s) \
+ sizeof(short))

.... but even *that* might be larger than strictly necessary
(although, again, it cannot be too small).
 
E

Eric Sosman

Dan said:
Why do you want to remain in the loop when (void *)(l + 1) == (void *)s ?
AFAICS, the right operator is '>'.

Or am I missing something?

"Upon further review," as they say in Murkin football,
you're right: `>' is correct.
If they're pointers to compatible types and they do point inside the same
object, why have any doubts about the correctness of the expression?

I didn't say I *should* have had doubts, just that I
*did* have doubts and they've now been dispelled. That's
the "learn something every day" part.
 
M

Malcolm

Martin Dickopp said:
I see no arithmetic on `void *' here, and I can find nothing in the
definition of the relational operators which would make this
invalid.
But maybe I'm missing something; could you elaborate why you
think it's invalid?
Because you can only compare objects from the same object, and attempts to
store objects of different types in the same area of memory are straining
the limits of C.
(The important exception is when the objects are part of the same
structure).

The other reason is that if the objects have different bit representations,
the operator cannot be implemented with a subtract, so it would need special
code to make it valid. Since it is something that isn't very useful in real
programs anyway, would such a compier writer necessarily patch just to
support this?
 
D

Dan Pop

Because you can only compare objects from the same object, and attempts to
store objects of different types in the same area of memory are straining
the limits of C.
(The important exception is when the objects are part of the same
structure).

Nope, the OP has shown a *valid* method for storing all kind of objects
inside a dynamically allocated memory block without using any aggregate
block and relying exclusively on a property guaranteed by the C standard:
the memory block is properly aligned for *all* C types. I would have
cast to char * myself, but there is nothing technically wrong with casting
to void *.

Dan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top