Go Back   Rhinocerus > Newsgroup > Newsgroup comp.lang.c

Reply
 
Thread Tools Display Modes
  #46 (permalink)  
Old 01-25-2011, 06:34 PM
James Kanze
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 25, 11:17 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> On Jan 25, 2:22 am, James Kanze <james.ka...@gmail.com> wrote:


> > If you're actually trying to write an allocator, you also have
> > to take into account what actual compilers do, and not just the
> > standard. I seem to recall something along the following lines:


> > float f(float const* in, bool* out)
> > {
> > float result = *in;
> > *out = true;
> > return result;
> > }


> > failing with g++ when called with:


> > union U { float f; bool b; };
> > U u;
> > u.f = 3.14159;
> > float g = f(&u.f, &u.b);


> > According to both C and C++, the union guaranteed that this
> > should work, But g++ rearranged the read and the write in f.


> > (I also seem to recall---albeit vaguely---the C committee saying
> > that it wasn't the intent to make this work; that they only
> > meant for it to be guaranteed if e.g. f was passed a pointer to
> > the union. But it's all very vague---I didn't have time to
> > follow up at the time.)


> > At any rate, most of this discussion seems to turn around the
> > same issues, without the union.


> Indeed. It's all very related to the union DR. So, the C standard
> committee never intended for the following program to have defined
> behavior? Interesting.


> void foo(int* x, float* y)
> { *x = 1;
> *y = 1;
> }
> int main()
> { union { int x; float y; } u;
> foo(&u.x, &u.y);
> return u.y;
> }


That's what I vaguely remember. But I don't remember who was
saying this (someone authorized to speak for the committe, or
not?), nor the exact context (other than it was in relationship
with the gcc bug). The best might be to take the discussion to
comp.std.c (although I don't read that).

And once we find out what was intended for C, we then have to
address the issue in C++; I'm still supposing that C++ wants
full C compatibility in this regard. (Or is that wishful
thinking on my part.)

--
James Kanze
Reply With Quote
Alt Today
Advertising
 
and become member of Rhinocerus
Standard Sponsored Links

  #47 (permalink)  
Old 01-25-2011, 06:59 PM
Keith Thompson
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

James Kanze <james.kanze@gmail.com> writes:
> On Jan 24, 11:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:

[...]
>> We need to solve a couple of basic problems. The most important and
>> basic is: when does the lifetime of a POD class even begin?


Do you mean the lifetime of a POD *object*?

> That's a good question: do PODs have lifetime? I'd argue yes,
> but it's not the lifetime defined in §3.8. Accessing an
> uninitialized POD is undefined behavior, and if you can't access
> an object, how can you say it exists?


At least in C, "access" includes both reading and modifying.
You can certainly modify an uninitialized POD object, something
you couldn't do if it didn't exist.

If you mean the lifetime of the object, why wouldn't it be the lifetime
defined in 3.8? If you mean the lifetime of the class, I'm not sure
what that would mean.

[...]

--
Keith Thompson (The_Other_Keith) kst-u@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Reply With Quote
  #48 (permalink)  
Old 01-25-2011, 08:05 PM
Joshua Maurice
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 25, 11:59*am, Keith Thompson <ks...@mib.org> wrote:
> James Kanze <james.ka...@gmail.com> writes:
> > On Jan 24, 11:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:

> [...]
> >> We need to solve a couple of basic problems. The most important and
> >> basic is: when does the lifetime of a POD class even begin?

>
> Do you mean the lifetime of a POD *object*?


Yes. Of course. Sorry.

> > That's a good question: do PODs have lifetime? *I'd argue yes,
> > but it's not the lifetime defined in §3.8. *Accessing an
> > uninitialized POD is undefined behavior, and if you can't access
> > an object, how can you say it exists?

>
> At least in C, "access" includes both reading and modifying.
> You can certainly modify an uninitialized POD object, something
> you couldn't do if it didn't exist.
>
> If you mean the lifetime of the object, why wouldn't it be the lifetime
> defined in 3.8? *If you mean the lifetime of the class, I'm not sure
> what that would mean.


I think James was referring to how the lifetime rules for POD objects
are broken in C++. It says the lifetime of a POD object begins as soon
as memory of sufficient size and alignment is allocated, which is
absurd, because that would imply quite a large number of complete
objects of completely different types coexisting in the same piece of
memory for every piece of memory.

I'm not sure of the rules for C. Again, can you answer my questions
else-thread in the example which attempts to distinguish between the
following?
typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;

I think I'll take James's advice and take this to comp.std.c.
Hopefully they're more talkative than comp.std.c++.
Reply With Quote
  #49 (permalink)  
Old 01-25-2011, 08:35 PM
Johannes Schaub (litb)
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

Joshua Maurice wrote:

> On Jan 25, 11:59 am, Keith Thompson <ks...@mib.org> wrote:
>> James Kanze <james.ka...@gmail.com> writes:
>> > On Jan 24, 11:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:

>> [...]
>> >> We need to solve a couple of basic problems. The most important and
>> >> basic is: when does the lifetime of a POD class even begin?

>>
>> Do you mean the lifetime of a POD *object*?

>
> Yes. Of course. Sorry.
>
>> > That's a good question: do PODs have lifetime? I'd argue yes,
>> > but it's not the lifetime defined in §3.8. Accessing an
>> > uninitialized POD is undefined behavior, and if you can't access
>> > an object, how can you say it exists?

>>
>> At least in C, "access" includes both reading and modifying.
>> You can certainly modify an uninitialized POD object, something
>> you couldn't do if it didn't exist.
>>
>> If you mean the lifetime of the object, why wouldn't it be the lifetime
>> defined in 3.8? If you mean the lifetime of the class, I'm not sure
>> what that would mean.

>
> I think James was referring to how the lifetime rules for POD objects
> are broken in C++. It says the lifetime of a POD object begins as soon
> as memory of sufficient size and alignment is allocated, which is
> absurd, because that would imply quite a large number of complete
> objects of completely different types coexisting in the same piece of
> memory for every piece of memory.
>
> I'm not sure of the rules for C. Again, can you answer my questions
> else-thread in the example which attempts to distinguish between the
> following?
> typedef struct T1 { int x; int y; } T1;
> typedef struct T2 { int x; int y; } T2;
>
> I think I'll take James's advice and take this to comp.std.c.
> Hopefully they're more talkative than comp.std.c++.


T1 and T2 are different types in C. I cannot find a rule that says that
these are compatible. So since they are different types and there is no rule
saying otherwise, the are not compatible. Thus they cannot alias each other.

All of this thread was crossposted to comp.std.c. I installed a followup-to
header that posted all this to comp.lang.c++, comp.lang.c and comp.std.c.
Reply With Quote
  #50 (permalink)  
Old 01-25-2011, 08:45 PM
Joshua Maurice
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 25, 1:35*pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
> Joshua Maurice wrote:
> > I'm not sure of the rules for C. Again, can you answer my questions
> > else-thread in the example which attempts to distinguish between the
> > following?
> > * typedef struct T1 { int x; int y; } T1;
> > * typedef struct T2 { int x; int y; } T2;

>
> > I think I'll take James's advice and take this to comp.std.c.
> > Hopefully they're more talkative than comp.std.c++.

>
> T1 and T2 are different types in C. I cannot find a rule that says that
> these are compatible. So since they are different types and there is no rule
> saying otherwise, the are not compatible. Thus they cannot alias each other.


I can also repeat that verbatim. Can you answer the more specific
questions which I have? I can copy and paste them into a new reply, if
you want.
Reply With Quote
  #51 (permalink)  
Old 01-26-2011, 12:28 AM
James Kuyper
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On 01/25/2011 04:05 PM, Joshua Maurice wrote:
> On Jan 25, 11:59�am, Keith Thompson<ks...@mib.org> wrote:

....
>> If you mean the lifetime of the object, why wouldn't it be the lifetime
>> defined in 3.8? �If you mean the lifetime of the class, I'm not sure
>> what that would mean.

>
> I think James was referring to how the lifetime rules for POD objects
> are broken in C++. It says the lifetime of a POD object begins as soon
> as memory of sufficient size and alignment is allocated, which is
> absurd, because that would imply quite a large number of complete
> objects of completely different types coexisting in the same piece of
> memory for every piece of memory.


I think it's clear from the context that this rule does not refer to
arbitrary pieces of memory, but only the particular piece of memory
allocated for that particular object, which has only that object's
specific type.
--
James Kuyper
Reply With Quote
  #52 (permalink)  
Old 01-26-2011, 12:58 AM
Joshua Maurice
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 25, 5:28*pm, James Kuyper <jameskuy...@verizon.net> wrote:
> On 01/25/2011 04:05 PM, Joshua Maurice wrote:
>
> > On Jan 25, 11:59 am, Keith Thompson<ks...@mib.org> *wrote:

> ...
> >> If you mean the lifetime of the object, why wouldn't it be the lifetime
> >> defined in 3.8? If you mean the lifetime of the class, I'm not sure
> >> what that would mean.

>
> > I think James was referring to how the lifetime rules for POD objects
> > are broken in C++. It says the lifetime of a POD object begins as soon
> > as memory of sufficient size and alignment is allocated, which is
> > absurd, because that would imply quite a large number of complete
> > objects of completely different types coexisting in the same piece of
> > memory for every piece of memory.

>
> I think it's clear from the context that this rule does not refer to
> arbitrary pieces of memory, but only the particular piece of memory
> allocated for that particular object, which has only that object's
> specific type.


Let me ask the relevant questions again then. For the following
program, when does the lifetime of the T1 object begin? Why do we have
a T1 object instead of a T2 object? (See further questions inline in
the code.)

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;

int main()
{
void* p = 0;
T1* t1 = 0;
T2* t2 = 0;

if (sizeof(T1) != sizeof(T2))
return 1;
if (offsetof(T1, x) != offsetof(T2, x))
return 1;
if (offsetof(T1, y) != offsetof(T2, y))
return 1;

p = malloc(sizeof(T1));
/* Has the lifetime of a T1 object started yet? What about T2? */

*(int*)(((char*)p) + offsetof(T1, x)) = 1;
/* Has the lifetime of a T1 object started yet? What about T2? */

*(int*)(((char*)p) + offsetof(T1, y)) = 2;
/* Has the lifetime of a T1 object started yet? What about T2? */

/* Can one even talk about the start of the lifetime of a T1
object at this point? Is there any reason to prefer talking about a T1
object over a T2 object? How is the above code importantly different
than casting the result of malloc to T1* (which is implicit in C),
using a memberof expression to get an lvalue for both of its members,
and writing to those members? Do we need to change the standards to
explicitly mention that the memberof expression is special with
regards to the object lifetime rules - specifically something like "A
memberof expression "X.Y" starts the lifetime of a new complete object
if there does not exist an object (complete or subobject) of the same
type as X at the memory referred to by X. The new complete object's
type is the same type as the left hand side of the memberof
expression."? I've already mused about such a possibility on comp.std.c
++, but such an idea just strikes me as exceptionally silly. However,
I think that's our only real way out at the moment. */

/* Where is the UB in the following code, for exactly what reason?
*/
t1 = (T1*)p;
printf("%d\n", t1->y);
t2 = (T2*)p;
printf("%d\n", t2->y);
}
Reply With Quote
  #53 (permalink)  
Old 01-26-2011, 01:15 AM
Ben Bacarisse
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

Joshua Maurice <joshuamaurice@gmail.com> writes:

> On Jan 25, 1:35Â*pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
>> Joshua Maurice wrote:
>> > I'm not sure of the rules for C. Again, can you answer my questions
>> > else-thread in the example which attempts to distinguish between the
>> > following?
>> > Â* typedef struct T1 { int x; int y; } T1;
>> > Â* typedef struct T2 { int x; int y; } T2;

>>
>> > I think I'll take James's advice and take this to comp.std.c.
>> > Hopefully they're more talkative than comp.std.c++.

>>
>> T1 and T2 are different types in C. I cannot find a rule that says that
>> these are compatible. So since they are different types and there is no rule
>> saying otherwise, the are not compatible. Thus they cannot alias each other.

>
> I can also repeat that verbatim. Can you answer the more specific
> questions which I have? I can copy and paste them into a new reply, if
> you want.


I am not Johannes Schaub, but I don't mind trying to answer your
specific questions. I think they raise interesting questions and all I
can do is give you my best guess.

Joshua Maurice <joshuamaurice@gmail.com> writes:
| We need to solve a couple of basic problems. The most important and
| basic is: when does the lifetime of a POD class even begin? Consider:

I don't think that's what you want to ask. You seem to be asking when
an allocated object acquires an effective type. In the code below the
lifetime of the allocated object extends from the execution of the
malloc to the program's termination but that's not the important issue.

|
| #include <cstdlib>
| using namespace std;
|
| struct T1 { int x; int y; };
| struct T2 { int x; int y; };
|
| int main()
| {
| void* p = 0;
| T1 * t1 = 0;
| T2 * t2 = 0;
| int * x = 0;
|
| if (sizeof(T1) != sizeof(T2))
| return 1;
| if ( (char*)(& t1->y) - (char*) (& t1) != (char*)(& t2->y) -
| (char*) (& t2) )

You corrected this in a subsequent posting but I think it is better
expressed with offsetof:

if (offsetof(struct T1, y) != offsetof(struct T2, y))

| return 1;
|
| p = malloc(sizeof(T1));
| /* Do we have a T1 object here? Presumably no.

No, there is only an object with no declared type and no effective type.

| Otherwise we also
| have a T2 object here, and we definitely don't want to start talking
| about two distinct complete objects occupying the same storage at the
| same time. */

That does not follow -- having a T1 does not mean we'd have a T2.

memcpy(p, &(struct T1){0, 0}, sizeof (struct T1));

gives the allocated object the effective type "struct T1". In effect
there is now a struct T1 there and nothing else, though that is not a
particularly good way of putting it.

| t1 = (T1*) p;
| /* T1 object yet? Presumably the answer hasn't changed since the
| above comment. */

Agreed.

| x = & t1->x;
| /* T1 object yet? */

In C, your question is a shorthand for "is the effective type of the
allocated object structÂ*T1 yet?". My answer is no I see no reason for
the effective type to have changed.

| *x = 1;
| /* Do we have a T1 object here? Maybe. I just see a write through
| an int lvalue. I see no writes nor reads through a T1 lvalue.

No, I'd say definitely not. We have an int there and that is all. As
you say there is no write through an lvalue expression of type struct
T1.

| I see
| nothing that favors T1 over T2, besides some sort of data dependency
| analysis through the member-of operator. However, there isn't even a
| hint of data dependency analysis in the standard with regards to
| object lifetime rules. */

Talk of object lifetime rules is a C++ issue. In the C version of the
code there is only on object whose lifetime matter here -- the allocated
object and it persists to the end of the program's execution.

| x = & t1->y;
| *x = 2;
| /* Do we have a T1 object here?

I'd say no. I can't see any reason to think otherwise. You reasoning
above (there having been write through an lvalue expression of type
struct T1) still applies.

| The answer must be yes, or we'll
| never have a T1 object.

I don't think so. There are unequivocal way to get a struct T1
object (translation: for the effective type of the allocated object to
be struct T1). One is the memcpy I showed. The other would be a direct
assignment like:

*t1 = (struct T1){0, 0};

But the question you raise is an interesting one. We've set two
sub-objects inside one allocated object (thereby setting the effective
type of those two sub-objects) but we've not set the effective type of
the whole object (at least that's my reading of the standard).

| However, again, I see nothing to favor having
| a T1 object over a T2 object besides data dependency analysis through
| the member-of operator. */

Yes, we have two ints and neither a struct T1 nor a struct T2.

| t2 = (T2*) p;
| return t2->y; /* UB? Why? Why is reading "t1->y" not UB, but
| reading "t2->y" is UB? In other words, why do we have a T1 object, but
| not a T2 object? */

I'd say we have neither.

I have to conclude that neither t1->y not t2->y is undefined. Both
access an object with effective type int though and lvalue expression of
type int.

I say "have to" because I am not sure that this is what was intended.
Having gone round this a few times now I can no longer see the purpose
of the wording relating to aggregates and unions in 6.5 p7. Maybe some
kind c.l.c soul will explain it to me! Or maybe it will be clear to me
in the morning.

| }
|
| Also, what if we used offsetof hackery to initialize both int members
| of the T1 object without using a member-of operator on a T1 lvalue?


--
Ben.
Reply With Quote
  #54 (permalink)  
Old 01-26-2011, 06:51 AM
Joshua Maurice
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 25, 6:15*pm, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
> Joshua Maurice <joshuamaur...@gmail.com> writes:
> | *
> | * * p = malloc(sizeof(T1));
> | * * /* Do we have a T1 object here? Presumably no.
>
> No, there is only an object with no declared type and no effective type.
>
> | * * * * * * * * * * * * * * * * * ** * * * * * * * Otherwise we also
> | have a T2 object here, and we definitely don't want to start talking
> | about two distinct complete objects occupying the same storage at the
> | same time. */
>
> That does not follow -- having a T1 does not mean we'd have a T2.
>
> * memcpy(p, &(struct T1){0, 0}, sizeof (struct T1));
>
> gives the allocated object the effective type "struct T1". *In effect
> there is now a struct T1 there and nothing else, though that is not a
> particularly good way of putting it.


Of course. I merely meant to argue that the effective type of the
allocated object cannot be T1 just after the malloc. There is
absolutely nothing in the code thus far to favor T1 over T2.

> [...]


> | * * x = & t1->y;
> | * * *x = 2;
> | * * /* Do we have a T1 object here?
>
> I'd say no. *I can't see any reason to think otherwise. *You reasoning
> above (there having been write through an lvalue expression of type
> struct T1) still applies.
>
> | * * * * * * * * * * * * * * * * * **The answer must be yes, or we'll
> | never have a T1 object.
>
> I don't think so. *There are unequivocal way to get a struct T1
> object (translation: for the effective type of the allocated object to
> be struct T1). *One is the memcpy I showed. *The other would be a direct
> assignment like:
>
> * *t1 = (struct T1){0, 0};
>
> But the question you raise is an interesting one. *We've set two
> sub-objects inside one allocated object (thereby setting the effective
> type of those two sub-objects) but we've not set the effective type of
> the whole object (at least that's my reading of the standard).


So, let me ask this then:

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;

int main()
{
void* p = malloc(sizeof(T1));
T1* t = (T1*)p;
t->x = 1;
t->y = 2;
return t->y;
}

I am correct in assuming that this is a very C idiomatic way of
initializing malloc-ed memory, right?

Is this significantly different than?

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;

int main()
{
void* p = malloc(sizeof(T1));
* (int*) (((char*)p) + offsetof(T1, x)) = 1;
* (int*) (((char*)p) + offsetof(T1, y)) = 2;
return ((T1*)p)->y;
}

Is one good and the other not? If so, what's the important difference,
and most importantly what part of the standard, if any, can be read to
describe that difference?
Reply With Quote
  #55 (permalink)  
Old 01-26-2011, 08:37 AM
James Kanze
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 25, 7:59 pm, Keith Thompson <ks...@mib.org> wrote:
> James Kanze <james.ka...@gmail.com> writes:
> > On Jan 24, 11:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:

> [...]
> >> We need to solve a couple of basic problems. The most important and
> >> basic is: when does the lifetime of a POD class even begin?


> Do you mean the lifetime of a POD *object*?


Yes.

> > That's a good question: do PODs have lifetime? I'd argue yes,
> > but it's not the lifetime defined in §3.8. Accessing an
> > uninitialized POD is undefined behavior, and if you can't access
> > an object, how can you say it exists?


> At least in C, "access" includes both reading and modifying.
> You can certainly modify an uninitialized POD object, something
> you couldn't do if it didn't exist.


Accessing an object certainly includes both reading and writing.
Maybe I'm reading too much into it, but the C++ statement talks
about "accessing an object's value"---I'm interpreting this to
mean an lvalue to rvalue conversion.

> If you mean the lifetime of the object, why wouldn't it be the lifetime
> defined in 3.8? If you mean the lifetime of the class, I'm not sure
> what that would mean.


Yes. I'm referring to 3.8 in the C++ standard.

--
James Kanze
Reply With Quote
  #56 (permalink)  
Old 01-26-2011, 08:49 AM
James Kanze
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 26, 2:15 am, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
> Joshua Maurice <joshuamaur...@gmail.com> writes:
> > On Jan 25, 1:35 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> > wrote:
> >> Joshua Maurice wrote:
> >> > I'm not sure of the rules for C. Again, can you answer my questions
> >> > else-thread in the example which attempts to distinguish between the
> >> > following?
> >> > typedef struct T1 { int x; int y; } T1;
> >> > typedef struct T2 { int x; int y; } T2;


> >> > I think I'll take James's advice and take this to comp.std.c.
> >> > Hopefully they're more talkative than comp.std.c++.


> >> T1 and T2 are different types in C. I cannot find a rule
> >> that says that these are compatible. So since they are
> >> different types and there is no rule saying otherwise, the
> >> are not compatible. Thus they cannot alias each other.


The interesting question in C is are they still not compatible
types if you drop the tags after the struct? (In C++, they are,
because in C++, if a class or struct doesn't have a tag, and
is in a typedef, the first name given for the type in the
typedef acts effectively as if it were a tag.)

> | We need to solve a couple of basic problems. The most important and
> | basic is: when does the lifetime of a POD class even begin? Consider:


> I don't think that's what you want to ask. You seem to be asking when
> an allocated object acquires an effective type. In the code below the
> lifetime of the allocated object extends from the execution of the
> malloc to the program's termination but that's not the important issue.


He's coming from a C++ background, where an object's lifetime is
related to its type: an object without a type cannot be alive.
But I like the way you describe it.

[...]
> You corrected this in a subsequent posting but I think it is better
> expressed with offsetof:
>
> if (offsetof(struct T1, y) != offsetof(struct T2, y))
>
> | return 1;
> |
> | p = malloc(sizeof(T1));
> | /* Do we have a T1 object here? Presumably no.


> No, there is only an object with no declared type and no effective type.


Is that directly from the C standard? (I like the way it is
expressed. I think it's the clearest exposition of what I think
should be the specification.)

[...]
> | nothing that favors T1 over T2, besides some sort of data dependency
> | analysis through the member-of operator. However, there isn't even a
> | hint of data dependency analysis in the standard with regards to
> | object lifetime rules. */


> Talk of object lifetime rules is a C++ issue. In the C version of the
> code there is only on object whose lifetime matter here -- the allocated
> object and it persists to the end of the program's execution.


Yes. When you have constructors and destructors, object
lifetime has a real signification. The problem in the C++
standard is that it tries to extend the term to objects without
constructors and destructors, in a way that probably doesn't
apply.

[...]
> Yes, we have two ints and neither a struct T1 nor a struct T2.


> | t2 = (T2*) p;
> | return t2->y; /* UB? Why? Why is reading "t1->y" not UB, but
> | reading "t2->y" is UB? In other words, why do we have a T1 object, but
> | not a T2 object? */


> I'd say we have neither.


> I have to conclude that neither t1->y not t2->y is undefined. Both
> access an object with effective type int though and lvalue expression of
> type int.


The expression t1->y is, by definition, the same as (*t1).y.
And what is (*t1), if not an access to an object of T1 (that, we
agree, doesn't exist).

--
James Kanze
Reply With Quote
  #57 (permalink)  
Old 01-26-2011, 10:10 AM
Joshua Maurice
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

On Jan 26, 1:49*am, James Kanze <james.ka...@gmail.com> wrote:
> The expression t1->y is, by definition, the same as (*t1).y.
> And what is (*t1), if not an access to an object of T1 (that, we
> agree, doesn't exist).


Let me try a devil's advocate position.

With regards to the current POSIX pthreads rules, and future C++0x
threading rules, is (*t1) a read or write for the purposes of
synchronization and race conditions? I would argue no.

Consider the initial conditions:
typedef struct T1 { int x; int y; } T1;
T1* t = malloc(sizeof(T1));
t->x = 1;
t->y = 2;

If we start off the following two threads simultaneously, we do not
have a race condition under any sane interpretation.
/* thread 1 */
printf("%d\n", t->x);

/* thread 2 */
t->y = 3;

As you noted, those threads are by definition equivalent to:
/* thread 1 */
printf("%d\n", (*t).x);

/* thread 2 */
(*t).y = 3;

Thus, what exactly does that "*t" mean? One of those expressions
clearly involves a read of the *t object (and/or one of its sub-
objects), and the other clearly involves a write of the *t object (and/
or one of its sub-objects).

I think under the traditional view, it's not a race condition because
we're only accessing two distinct sub-objects and never accessing the
complete object.

However, you called *t an access of the T1 object, the object with
effective type T1, whatever. I suppose we could consider all accesses
of the form *t for struct types to be reads and never writes, but I
think that's somewhat silly. I think you would be hard pressed to
defend a terminology which says that "t->y = 3" involves a read of the
*t object.

Alternatively, we could define "access" so that you can access an
object without reading or writing it - for whatever that would mean,
which I think is even sillier.

I don't think that the expression "*t", where t has a not primitive
type, is necessarily an access in the conventional sense.
Unfortunately, I don't like that conclusion.

Consider:
T1 * x;
T1 * y;
/* .. */
*x = *y;
In the last line above, *x and *y are both "involved" in accesses.
There is a read of the *y object, and there is a write to the *x
object.

Consider:
T1 * x;
T1 * y;
/* .. */
(*x).x = (*y).x;
In the last line above, there is still a read and a write, but now
it's a read of a member sub-object and a write to a member sub-object
- not the whole object. The obviousness of this comes from how this
interacts with threading (described above), and with volatile
(described below).

Thus, it seems that "*x" is sometimes a read of the whole object, and
sometimes it's just part of an expression to get an lvalue to one of
its sub-objects (not meant to be an exhaustive list of possible
uses).

I think the rules concerning volatile also show this clearly. Consider
the following:
#include <stdlib.h>
typedef struct T3 { volatile int x; volatile int y; } T3;
int main()
{
T3 * t = malloc(sizeof(T3));

(*t).x = 1; /* Ok. There was a write to the volatile x sub-object.
There was not a read of the volatile x sub-object, and there was
definitely not a write nor read of the volatile y sub-object. I also
argue that there wasn't a read nor write of the T3 object. */

*t = *t; /* Ok, here we have a read of both volatile sub-objects,
and a write to both volatile sub-objects. I would even call this a
read of the T3 object, and a write to the T3 object. */

*t; /* What does this do? I don't know. Is it required to read
both volatile members? Is it required to read neither? */
}

What if the T3 object itself was volatile qualified?
typedef struct T3 { volatile int x; volatile int y; } T3;
int main()
{
volatile T3 a;
volatile T3 * b = & a;
(*b).y = 2;
}
In the above program, is there a read or write to the x sub-object? I
would assume no. Moreover, is there any sort of guaranteed observable
behavior besides the write to the y sub-object? That is, does the top
level volatile in "volatile T3 a;" mean anything in this example?
AFAIK, the top level volatile qualifier here doesn't really do much
because all of the sub-objects are already volatile qualified.

I'm not really sure where I'm going with this though... It's too late
for me. I think I'm trying to get at is AFAIK there really is no such
thing as an access, read or write, of an object with non-primitive
type. All of the accesses are really accesses through primitive types.
This seems abundantly clear from the volatile rules and the rules for
race conditions with threading. Which of course begs the question
about how to make this sensible with the strict aliasing rules which
do talk about accesses of objects of non-primitive type, and the
general consensus which definitely wants to prohibit "accessing" a T1
object through a T2 object lvalue.

Ex: Assuming a compiler which gives these two types the same layout
and size:

typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;
int main()
{
T1 * x;
T2 * y;

x = new malloc(sizeof(T1));
x->x = 1;
x->y = 2;
y = (T2*) x;
return y->y; /* UB */
}

In short: the general consensus says that the above UB is UB because
there's an "access" of an object with effective type T1 through a T2
lvalue. I don't see any rules which explain why the effective type of
the object is T1 (explained in other posts), and I don't see any
meaningful rules which describe what it means to "access" an object
through a T2 lvalue (explained at length in this post).
Reply With Quote
  #58 (permalink)  
Old 01-26-2011, 10:17 AM
Willem
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

James Kanze wrote:
) The expression t1->y is, by definition, the same as (*t1).y.
) And what is (*t1), if not an access to an object of T1 (that, we
) agree, doesn't exist).

(*t1) is only an access to an object if it is involved
in something else that implies access.

Consider the following: &(*t1)

( Or, when considering structs: &(t1->y) )


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
Reply With Quote
  #59 (permalink)  
Old 01-26-2011, 02:27 PM
Ben Bacarisse
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

James Kanze <james.kanze@gmail.com> writes:

> On Jan 26, 2:15 am, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
>> Joshua Maurice <joshuamaur...@gmail.com> writes:
>> > On Jan 25, 1:35 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
>> > wrote:
>> >> Joshua Maurice wrote:
>> >> > I'm not sure of the rules for C. Again, can you answer my questions
>> >> > else-thread in the example which attempts to distinguish between the
>> >> > following?
>> >> > typedef struct T1 { int x; int y; } T1;
>> >> > typedef struct T2 { int x; int y; } T2;

>
>> >> > I think I'll take James's advice and take this to comp.std.c.
>> >> > Hopefully they're more talkative than comp.std.c++.

>
>> >> T1 and T2 are different types in C. I cannot find a rule
>> >> that says that these are compatible. So since they are
>> >> different types and there is no rule saying otherwise, the
>> >> are not compatible. Thus they cannot alias each other.

>
> The interesting question in C is are they still not compatible
> types if you drop the tags after the struct?


No, I think not. Compatibility is defined by a set of rules and there
are only two might apply. One is "[t]wo types have compatible type if
their types are the same" but the description of a struct declaration
states that it introduces a new type. The second is a rule for
compatibility of structs declared in separate compilation units so it
does not apply here. The two types *would* be compatible if declared in
separate translation units (as you'd expect) but not otherwise.

> (In C++, they are,
> because in C++, if a class or struct doesn't have a tag, and
> is in a typedef, the first name given for the type in the
> typedef acts effectively as if it were a tag.)
>
>> | We need to solve a couple of basic problems. The most important and
>> | basic is: when does the lifetime of a POD class even begin? Consider:

>
>> I don't think that's what you want to ask. You seem to be asking when
>> an allocated object acquires an effective type. In the code below the
>> lifetime of the allocated object extends from the execution of the
>> malloc to the program's termination but that's not the important issue.

>
> He's coming from a C++ background, where an object's lifetime is
> related to its type: an object without a type cannot be alive.
> But I like the way you describe it.
>
> [...]
>> You corrected this in a subsequent posting but I think it is better
>> expressed with offsetof:
>>
>> if (offsetof(struct T1, y) != offsetof(struct T2, y))
>>
>> | return 1;
>> |
>> | p = malloc(sizeof(T1));
>> | /* Do we have a T1 object here? Presumably no.

>
>> No, there is only an object with no declared type and no effective type.

>
> Is that directly from the C standard? (I like the way it is
> expressed. I think it's the clearest exposition of what I think
> should be the specification.)


Yes, pretty much. 6.5 p6 refers to a footnote that explains how some
objects have no declared type ("Allocated objects have no declared
type") and none of the rules that give an object an effective type (that
is a C standard term) apply yet so the object does not yet have one.

> [...]
>> | nothing that favors T1 over T2, besides some sort of data dependency
>> | analysis through the member-of operator. However, there isn't even a
>> | hint of data dependency analysis in the standard with regards to
>> | object lifetime rules. */

>
>> Talk of object lifetime rules is a C++ issue. In the C version of the
>> code there is only on object whose lifetime matter here -- the allocated
>> object and it persists to the end of the program's execution.

>
> Yes. When you have constructors and destructors, object
> lifetime has a real signification. The problem in the C++
> standard is that it tries to extend the term to objects without
> constructors and destructors, in a way that probably doesn't
> apply.
>
> [...]
>> Yes, we have two ints and neither a struct T1 nor a struct T2.

>
>> | t2 = (T2*) p;
>> | return t2->y; /* UB? Why? Why is reading "t1->y" not UB, but
>> | reading "t2->y" is UB? In other words, why do we have a T1 object, but
>> | not a T2 object? */

>
>> I'd say we have neither.

>
>> I have to conclude that neither t1->y not t2->y is undefined. Both
>> access an object with effective type int though and lvalue expression of
>> type int.

>
> The expression t1->y is, by definition, the same as (*t1).y.


Yes, but that's a C/C++ difference. It's not defined like that in C.

> And what is (*t1), if not an access to an object of T1 (that, we
> agree, doesn't exist).


In C, the text describing -> avoids all mention of an access to an object
of the struct type.

However, even if we write (*t1)->y the effect is *still* defined. If
the "whole object" still has no effective type, then the final sentence
of 6.5 p6 applies:

"For all other accesses to an object having no declared type, the
effective type of the object is simply the type of the lvalue used for
the access."

Thus *t1 (and *t2 for that matter) is being "accessed by an lvalue
expression" with "a type compatible with the effective type of the
object". These two quotes are from the wording from the next paragraph
(6.5 p7) that defines what accesses are permitted.

This effective type does not "persist". In the case of a store, the
text of 6.5 p6 reads

"the lvalue becomes the effective type of the object for that access
and for subsequent accesses that do not modify the stored value"

but no such wording applies to a read access. Thus we can even write

return (*t1).y + (*t2).y;

and still be in the land of defined behaviour.

--
Ben.
Reply With Quote
  #60 (permalink)  
Old 01-26-2011, 03:38 PM
Keith Thompson
Guest
 
Posts: n/a
Default Re: Is the aliasing rule symmetric?

Joshua Maurice <joshuamaurice@gmail.com> writes:
[...]
> So, let me ask this then:
>
> #include <assert.h>
> #include <stddef.h>
> #include <stdlib.h>
>
> typedef struct T1 { int x; int y; } T1;
>
> int main()
> {
> void* p = malloc(sizeof(T1));
> T1* t = (T1*)p;
> t->x = 1;
> t->y = 2;
> return t->y;
> }
>
> I am correct in assuming that this is a very C idiomatic way of
> initializing malloc-ed memory, right?


There's usually no reason to store the result of malloc in a void*.
This is more idiomatic:

#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;

int main(void)
{
T1 *p = malloc(sizeof *p);
p->x = 1;
p->y = 2;
return p->y;
}

Though personally I'd drop the typedef and refer to "struct T1"
directly.

(Note that the "void" keyword makes the declaration of main a
prototype; it's debatable whether it's necessary, but IMHO it's
at least better style.)

This "sizeof" trick for malloc avoids errors when the type of p changes;
you don't have to mention the type twice. This:

T1 *p = malloc(sizeof(T1));

may be more common out in the real world (outside this newsgroup), but I
prefer the "sizeof *p" trick myself.

> Is this significantly different than?
>
> #include <assert.h>
> #include <stddef.h>
> #include <stdlib.h>
>
> typedef struct T1 { int x; int y; } T1;
>
> int main()
> {
> void* p = malloc(sizeof(T1));
> * (int*) (((char*)p) + offsetof(T1, x)) = 1;
> * (int*) (((char*)p) + offsetof(T1, y)) = 2;
> return ((T1*)p)->y;
> }
>
> Is one good and the other not? If so, what's the important difference,
> and most importantly what part of the standard, if any, can be read to
> describe that difference?


As a matter of style, it's a much more verbose way of saying essentially
the same thing.

--
Keith Thompson (The_Other_Keith) kst-u@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Reply With Quote
 
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are Off
Pingbacks are Off
Refbacks are Off




All times are GMT. The time now is 10:50 AM.


Copyright ©2009

LinkBacks Enabled by vBSEO 3.3.0 RC2 © 2009, Crawlability, Inc.