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