declaration meaning

T

Tomás

Why do you care that there's padding? sizeof tells you all you need to
know doesn't it?

Well consider this:

(I'm still not adjusted to C, so forgive me if I use C++ idioms/styles which
I shouldn't use.)

#include <stdlib.h>

typedef struct Monkey {

int i;
char a[1];

} Monkey;

void SomeFunc( Monkey* const p_monkey )
{
/* Let's get a pointer to the second array element: */

char *pc1 = &p_monkey->a[1];


/* Now let's do that another way: */

char *pc2 = (char*) (p_monkey + 1);


/* ******************************** */


/* Now let's compare the addresses */

if ( pc1 != pc2 )
{
printf("We have padding at the end!");
}
}

int main(void)
{
//Let's say we want that array to
//consist of seven elements:

void* const p = malloc( sizeof(Monkey) + 6 );

Monkey* const p_monkey = (Monkey*)p;

p_monkey->i = 5;

p_monkey->a[0] = 'a';
p_monkey->a[1] = 'b';
p_monkey->a[2] = 'c';
p_monkey->a[3] = 'd';
p_monkey->a[4] = 'e';
p_monkey->a[5] = 'f';
p_monkey->a[6] = 'g';

SomeFunc( p_monkey );


free(p);
}


If there is indeed three bytes of padding at the end of this structure, then
the following l-value:

p_monkey->a[1]

refers to the first byte of padding.

However, the following l-value:

p_monkey + 1

refers to the first byte AFTER the three bytes of padding.

Therefore, it seems that there's not much benefit in the whole "array at the
end of a structure" strategy. It would be just as handy to do the following:


typedef struct Monkey { int i; } Monkey;

int main(void)
{
void* const p = malloc( sizeof(Monkey) + 7 );


Monkey* const p_monkey = (Monkey*)p;


/* Now if we want to access the array: */

char* p_array = (char*) (p_monkey + 1);

p_array[0] = 'a';

p_array[1] = 'b';
}


Then "SomeFunc" could play with the structure as follows:


void SomeFunc( Monkey* const p_monkey )
{
/* Let's get a pointer to the first array element: */

char *p = (char*) (p_monkey + 1);

/* Now access the array: */

p[0] = 'a';
}



It appears to me, that there would only be merit in the method of putting an
array at then end of a struct if the padding was put BEFORE the first array
element, as follows:

a) 4 bytes for "i"
b) 3 bytes of padding.
c) 1st element of the array.


-Tomás
 
T

Tomás

And this difference depends upon how the Monkey structure is defined
(specifically, the length we specify for "array").


Opps I just realized that your code was correct, and that it took into
account the length we specify for "arrayname". I'll try again:

enum { length = 5; };

typedef struct Monkey { int i; char array[length]; } Monkey;

enum bool { false, true };

bool IsTherePaddingAtTheEndOfMonkey(void)
{
Monkey object;

void* const p1 = &object + 1;

void* const p2 = &object.array + 1;

if ( p1 != p2 ) printf( "We have padding!";

/* The amount of padding is determined by: */

(char*)p1 - (char*)p2;
}


-Tomás
 
B

Ben C

Why do you care that there's padding? sizeof tells you all you need to
know doesn't it?
[...]

If there is indeed three bytes of padding at the end of this structure, then
the following l-value:

p_monkey->a[1]

refers to the first byte of padding.

However, the following l-value:

p_monkey + 1

refers to the first byte AFTER the three bytes of padding.

Yes (a few operators and casts understood), but that doesn't really
matter so long as you don't try to use p_monkey + 1 to find the second
element of the array, but always use p_monkey->a[1].

It means you malloc a bit more than you need-- sizeof (Monkey) + 7
extends the array actually by 7 + padding. But that doesn't really
matter.
Therefore, it seems that there's not much benefit in the whole "array at the
end of a structure" strategy. It would be just as handy to do the following:


typedef struct Monkey { int i; } Monkey;

int main(void)
{
void* const p = malloc( sizeof(Monkey) + 7 );


Monkey* const p_monkey = (Monkey*)p;


/* Now if we want to access the array: */

char* p_array = (char*) (p_monkey + 1);

p_array[0] = 'a';

p_array[1] = 'b';
}

Yes, this also works. Then you wouldn't bother with the char a[1] at the
end of struct Monkey presumably. It would be positively misleading to
leave it in there.

It's less convenient this way though, because you have to use casts to
get at that array, and the user of the array (SomeFunc say) has to know
what type to cast to. If we have that char a[1], we know at least that
it's an array of char (or whatever else), and we just use it
transparently as if it were a larger array.
Then "SomeFunc" could play with the structure as follows:


void SomeFunc( Monkey* const p_monkey )
{
/* Let's get a pointer to the first array element: */

char *p = (char*) (p_monkey + 1);

/* Now access the array: */

p[0] = 'a';
}
It appears to me, that there would only be merit in the method of
putting an array at then end of a struct if the padding was put BEFORE
the first array element, as follows:

a) 4 bytes for "i"
b) 3 bytes of padding.
c) 1st element of the array.

This sounds like it could work, and I think the compiler could
legitimately do it that way if it felt like it; then (p_monkey + 1 ==
&p_monkey->array[1]). But it doesn't matter in practice that these are
not equal, so long as you don't use p_monkey + 1 to get to the second
element of the extended array.
 
M

michi

Darko said:
If this works, then it's a work of art. :) Of course - if one was to
declare it as a variable, it would get a compiler error. However,
declaring it in a structure doesn't do the allocating itself, we just
get a name we later hold for when referring to (m)allocated memory.
Genialno. :) So, it actually works only for structs?
work of art? I'd say that's a landmine.

- Anyone unsuspiciously using a memcpy(pX,pFoo,sizeof(*pFoo)) will get
surprising results.

- Using malloc, one must take into account that the storage for the
first element is covered by sizeof(stuct message) while the rest is not.

- for copying you should at least provide a size_t len member to
determine the "extra" space.

- it makes your code wide open for heap-overflow attacks and code
injection exploits.

- It's impossible to have these objects on the stack, even if the
compiler permits it.

- Consider using #pragma pack(1) struct ... #pragma pack()
this will provide you at least with defined memory-layouts you are using
reinterpret-casts to the "char" data, even if packing was modified from
somewhere else. (cc options, foreign headers)

I'd suggest not to use this construct even if it's convenient.

<flame mode="plenty">
variable sized array are pure evil
</flame>
 
K

Keith Thompson

michi said:
work of art? I'd say that's a landmine.

The technique described in the previous article is the "struct hack",
a very well-known technique. C99 adds "flexible array members" (q.v.)
that do the same thing.
- Anyone unsuspiciously using a memcpy(pX,pFoo,sizeof(*pFoo)) will get
surprising results.

Yes, you have to be careful.
- Using malloc, one must take into account that the storage for the
first element is covered by sizeof(stuct message) while the rest is
not.
Yes.

- for copying you should at least provide a size_t len member to
determine the "extra" space.
Yes.

- it makes your code wide open for heap-overflow attacks and code
injection exploits.

How so? If you manage memory carefully enough, this shouldn't be an
issue.
- It's impossible to have these objects on the stack, even if the
compiler permits it.
Yes.

- Consider using #pragma pack(1) struct ... #pragma pack()
this will provide you at least with defined memory-layouts you are
using reinterpret-casts to the "char" data, even if packing was
modified from somewhere else. (cc options, foreign headers)

"#pragma pack" is non-standard and non-portable. Use it if you must,
but I don't see how it helps in using the "struct hack". (And C
doesn't have "reinterpret-casts"; that's a C++ term.)
 

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,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top