Is gcc's
In C, the compiler will lay out members of a struct in the order in which they're declared, with possible padding bytes inserted between members, or after the last member, to ensure that each member is aligned properly.
gcc provides a language extension, __attribute__((packed))
, which tells the compiler not to insert padding, allowing struct members to be misaligned. For example, if the system normally requires all int
objects to have 4-byte alignment, __attribute__((packed))
can cause int
struct members to be allocated at odd offsets.
Quoting the gcc documentation:
The `packed' attribute specifies that a variable or structure field should have the smallest possible alignment--one byte for a variable, and one bit for a field, unless you specify a larger value with the `aligned' attribute.
Obviously the use of this extension can result in smaller data requirements but slower code, as the compiler must (on some platforms) generate code to access a misaligned member a byte at a time.
But are there any cases where this is unsafe? Does the compiler always generate correct (though slower) code to access misaligned members of packed structs? Is it even possible for it to do so in all cases?
Yes, __attribute__((packed))
is potentially unsafe on some systems. The symptom probably won't show up on an x86, which just makes the problem more insidious; testing on x86 systems won't reveal the problem. (On the x86, misaligned accesses are handled in hardware; if you dereference an int*
pointer that points to an odd address, it will be a little slower than if it were properly aligned, but you'll get the correct result.)
On some other systems, such as SPARC, attempting to access a misaligned int
object causes a bus error, crashing the program.
There have also been systems where a misaligned access quietly ignores the low-order bits of the address, causing it to access the wrong chunk of memory.
Consider the following program:
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %dn", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %dn", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %dn", (int)offsetof(struct foo, x));
printf("arr[0].x = %dn", arr[0].x);
printf("arr[1].x = %dn", arr[1].x);
printf("p0 = %pn", (void*)p0);
printf("p1 = %pn", (void*)p1);
printf("*p0 = %dn", *p0);
printf("*p1 = %dn", *p1);
return 0;
}
On x86 Ubuntu with gcc 4.5.2, it produces the following output:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
On SPARC Solaris 9 with gcc 4.5.1, it produces the following:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
In both cases, the program is compiled with no extra options, just gcc packed.c -o packed
.
(A program that uses a single struct rather than array doesn't reliably exhibit the problem, since the compiler can allocate the struct on an odd address so the x
member is properly aligned. With an array of two struct foo
objects, at least one or the other will have a misaligned x
member.)
(In this case, p0
points to a misaligned address, because it points to a packed int
member following a char
member. p1
happens to be correctly aligned, since it points to the same member in the second element of the array, so there are two char
objects preceding it -- and on SPARC Solaris the array arr
appears to be allocated at an address that is even, but not a multiple of 4.)
When referring to the member x
of a struct foo
by name, the compiler knows that x
is potentially misaligned, and will generate additional code to access it correctly.
Once the address of arr[0].x
or arr[1].x
has been stored in a pointer object, neither the compiler nor the running program knows that it points to a misaligned int
object. It just assumes that it's properly aligned, resulting (on some systems) in a bus error or similar other failure.
Fixing this in gcc would, I believe, be impractical. A general solution would require, for each attempt to dereference a pointer to any type with non-trivial alignment requirements either (a) proving at compile time that the pointer doesn't point to a misaligned member of a packed struct, or (b) generating bulkier and slower code that can handle either aligned or misaligned objects.
I've submitted a gcc bug report. As I said, I don't believe it's practical to fix it, but the documentation should mention it (it currently doesn't).
It's perfectly safe as long as you always access the values through the struct via the .
(dot) or ->
notation.
What's not safe is taking the pointer of unaligned data and then accessing it without taking that into account.
Also, even though each item in the struct is known to be unaligned, it's known to be unaligned in a particular way, so the struct as a whole must be aligned as the compiler expects or there'll be trouble (on some platforms, or in future if a new way is invented to optimise unaligned accesses).
As ams said above, don't take a pointer to a member of a struct that's packed. This is simply playing with fire. When you say __attribute__((__packed__))
or #pragma pack(1)
, what you're really saying is "Hey gcc, I really know what I'm doing." When it turns out that you do not, you can't rightly blame the compiler.
Perhaps we can blame the compiler for it's complacency though. While gcc does have a -Wcast-align
option, it isn't enabled by default nor with -Wall
or -Wextra
. This is apparently due to gcc developers considering this type of code to be a brain-dead "abomination" unworthy of addressing -- understandable disdain, but it doesn't help when an inexperienced programmer bumbles into it.
Consider the following:
struct __attribute__((__packed__)) my_struct {
char c;
int i;
};
struct my_struct a = {'a', 123};
struct my_struct *b = &a;
int c = a.i;
int d = b->i;
int *e __attribute__((aligned(1))) = &a.i;
int *f = &a.i;
Here, the type of a
is a packed struct (as defined above). Similarly, b
is a pointer to a packed struct. The type of of the expression ai
is (basically) an int l-value with 1 byte alignment. c
and d
are both normal int
s. When reading ai
, the compiler generates code for unaligned access. When you read b->i
, b
's type still knows it's packed, so no problem their either. e
is a pointer to a one-byte-aligned int, so the compiler knows how to dereference that correctly as well. But when you make the assignment f = &a.i
, you are storing the value of an unaligned int pointer in an aligned int pointer variable -- that's where you went wrong. And I agree, gcc should have this warning enabled by default (not even in -Wall
or -Wextra
).
上一篇: 试图使用qsort与矢量
下一篇: 是gcc的