g++ and aliasing bools
Daniel Berlin
dan@dberlin.org
Fri Jan 25 02:25:00 GMT 2002
On Thu, 24 Jan 2002, Mark Mitchell wrote:
>
> > An incremental improvement would be to allow at least C-type structs.
> >
> > {
> > if (AGGREGATE_TYPE_P (t) &&
> > IS_A_CLASS_DERIVED_FROM_ANOTHER_CLASS_P (t)) /* [1] */
> > return 0;
> >
> > return c_common_get_alias_set (t);
> > }
> >
> > should be safe because:
> > a) the predicate [1] is true for any aggregate that is not a C-type struct
> > b) c_common_get_alias_set deals with C-type structs correctly
> > c) C-type structs cannot alias derived classes because the later are
> > put in alias set 0 because of [1]
> >
>
> See this is where things get subtle. You have to (at least) worry
> about whether or not t is zero-sized or has zero-sized bases or members.
Not any more than we worry about it in C.
And we are already giving up on the case where we are derived from another
class, we are just trying to handle simple classes/structs here, so we
need not worry about bases. A c
struct can have zero sized members, too.
> If it does, there may be other zero-sized things at the same address.
Same in C.
> If that's so, then if you put the zero-sized things in different alias
> sets, you're saying they never alias. Now, obviously, we never read or
> write zero-sized things -- but different alias sets also implies that
> &x != &y which is false.
The only way we could end upw ith a problem in C++ for the restricted
case above is if aliasing is broken for zero sized members in C, too.
You can end up with empty structs, and empty members just the same
in C.
In the following example, foo.empty and foo.empty2 get placed in the same
alias set, properly (i had to use the memset with 1 rather than 0 to
actually get it to not no-op it at -O2)
#include <stddef.h>
struct dan
{
struct empty
{
} empty;
struct empty
{
} empty2;
int a;
};
int main(void)
{
struct dan foo;
printf("%d\n", offsetof (struct dan, empty));
printf("%d\n", offsetof (struct dan, empty2));
printf("%d\n", offsetof (struct dan, a));
printf("%d\n", sizeof (struct dan));
printf("%d\n", (unsigned int) &foo.empty - (unsigned int) &foo);
printf("%d\n", (unsigned int) &foo.empty2 - (unsigned int) &foo);
printf("%d\n", (unsigned int) &foo.a - (unsigned int) &foo);
foo.a = 5;
memset(&foo.empty, 5, 1);
memset(&foo.empty2, 5, 1);
}
Note that foo.empty2 and foo.empty are at the same address, too.
Also Note it lists them both as foo+0, which is correct.
...
(insn 62 59 63 (set (reg:QI 127)
(const_int 5 [0x5])) -1 (nil)
(expr_list:REG_EQUAL (const_int 5 [0x5])
(nil)))
(insn 63 62 66 (set (mem/s:QI (reg/f:SI 111 virtual-stack-vars) [3 foo+0
S1 A128])
(reg:QI 127)) -1 (nil)
(nil))
(insn 66 63 67 (set (reg:QI 128)
(const_int 5 [0x5])) -1 (nil)
(expr_list:REG_EQUAL (const_int 5 [0x5])
(nil)))
(insn 67 66 68 (set (mem/s:QI (reg/f:SI 111 virtual-stack-vars) [3 foo+0
S1 A128])
(reg:QI 128)) -1 (nil)
(nil))
...
If you throw an int b in between the two structs, like so:
#include <stddef.h>
struct dan
{
struct empty
{
} empty;
int b;
struct empty
{
} empty2;
int a;
};
int main(void)
{
struct dan foo;
printf("%d\n", offsetof (struct dan, empty));
printf("%d\n", offsetof (struct dan, empty2));
printf("%d\n", offsetof (struct dan, a));
printf("%d\n", offsetof (struct dan, b));
printf("%d\n", sizeof (struct dan));
printf("%d\n", (unsigned int) &foo.empty - (unsigned int) &foo);
printf("%d\n", (unsigned int) &foo.empty2 - (unsigned int) &foo);
printf("%d\n", (unsigned int) &foo.a - (unsigned int) &foo);
printf("%d\n", (unsigned int) &foo.b - (unsigned int) &foo);
foo.a = 5;
foo.b = 5;
memset(&foo.empty, 5, 1);
memset(&foo.empty2, 5, 1);
}
We get:
(insn 78 75 79 (set (reg:SI 132)
(const_int 5 [0x5])) -1 (nil)
(expr_list:REG_EQUAL (const_int 5 [0x5])
(nil)))
(insn 79 78 82 (set (mem/s:SI (plus:SI (reg/f:SI 111 virtual-stack-vars)
(const_int 12 [0xc])) [5 foo.a+0 S4 A32])
(reg:SI 132)) -1 (nil)
(nil))
(insn 82 79 83 (set (reg:SI 133)
(const_int 5 [0x5])) -1 (nil)
(expr_list:REG_EQUAL (const_int 5 [0x5])
(nil)))
(insn 83 82 86 (set (mem/s:SI (plus:SI (reg/f:SI 111 virtual-stack-vars)
(const_int 4 [0x4])) [5 foo.b+0 S4 A32])
(reg:SI 133)) -1 (nil)
(nil))
(insn 86 83 87 (set (reg:QI 134)
(const_int 5 [0x5])) -1 (nil)
(expr_list:REG_EQUAL (const_int 5 [0x5])
(nil)))
(insn 87 86 90 (set (mem/s:QI (reg/f:SI 111 virtual-stack-vars) [3 foo+0
S1 A128])
(reg:QI 134)) -1 (nil)
(nil))
(insn 90 87 91 (set (reg:QI 135)
(const_int 5 [0x5])) -1 (nil)
(expr_list:REG_EQUAL (const_int 5 [0x5])
(nil)))
(insn 91 90 93 (set (mem:QI (plus:SI (reg/f:SI 111 virtual-stack-vars)
(const_int 8 [0x8])) [0 S1 A8])
(reg:QI 135)) -1 (nil)
(nil))
Which I *think* is right (though why we lost track of foo.empty2 before we
even wrote the first rtl dump is curious)
Now, we haven't gotten into tricky C++ stuff, but that's okay, some
improvement is better than nothing.
Unless you can show that we have a problem with empty members in C, i
don't see how, for the restricted case of classes that aren't inheriting
from other classes, we could get screwed.
Remove the inheritance, and we have a c struct with virtual
functions, which translates directly into a c struct with function
pointers.
>
> --
> Mark Mitchell mark@codesourcery.com
> CodeSourcery, LLC http://www.codesourcery.com
>
More information about the Gcc
mailing list