Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c > #22865 > unrolled thread
| Started by | nroberts <roberts.noah@gmail.com> |
|---|---|
| First post | 2012-06-27 08:50 -0700 |
| Last post | 2012-07-08 11:06 -0700 |
| Articles | 20 on this page of 22 — 12 participants |
Back to article view | Back to comp.lang.c
Function casting - UB? nroberts <roberts.noah@gmail.com> - 2012-06-27 08:50 -0700
Re: Function casting - UB? Eric Sosman <esosman@ieee-dot-org.invalid> - 2012-06-27 12:49 -0400
Re: Function casting - UB? Heikki Kallasjoki <fis+usenet@zem.fi> - 2012-06-27 17:54 +0000
Re: Function casting - UB? "io_x" <a@b.c.invalid> - 2012-06-28 18:58 +0200
Re: Function casting - UB? Tim Rentsch <txr@alumni.caltech.edu> - 2012-06-27 10:37 -0700
Re: Function casting - UB? Johannes Bauer <dfnsonfsduifb@gmx.de> - 2012-06-28 09:23 +0200
Re: Function casting - UB? Johannes Bauer <dfnsonfsduifb@gmx.de> - 2012-06-28 09:27 +0200
Re: Function casting - UB? Jens Gustedt <jens.gustedt@loria.fr> - 2012-06-28 12:03 +0200
Re: Function casting - UB? Tim Rentsch <txr@alumni.caltech.edu> - 2012-06-29 11:10 -0700
Re: Function casting - UB? Tim Rentsch <txr@alumni.caltech.edu> - 2012-06-29 11:03 -0700
Re: Function casting - UB? Les Cargill <lcargill99@comcast.com> - 2012-06-27 12:58 -0500
Re: Function casting - UB? Les Cargill <lcargill99@comcast.com> - 2012-06-27 12:59 -0500
Re: Function casting - UB? James Kuyper <jameskuyper@verizon.net> - 2012-06-27 14:45 -0400
Re: Function casting - UB? "christian.bau" <christian.bau@cbau.wanadoo.co.uk> - 2012-06-27 14:55 -0700
Re: Function casting - UB? Tim Rentsch <txr@alumni.caltech.edu> - 2012-06-27 17:44 -0700
Re: Function casting - UB? Joshua Maurice <joshuamaurice@gmail.com> - 2012-07-07 16:46 -0700
Re: Function casting - UB? Barry Schwarz <schwarzb@dqel.com> - 2012-07-08 09:19 -0700
Re: Function casting - UB? Tim Rentsch <txr@alumni.caltech.edu> - 2012-07-08 11:13 -0700
Re: Function casting - UB? Barry Schwarz <schwarzb@dqel.com> - 2012-07-08 15:24 -0700
Re: Function casting - UB? Joshua Maurice <joshuamaurice@gmail.com> - 2012-07-08 18:12 -0700
Re: Function casting - UB? Tim Rentsch <txr@alumni.caltech.edu> - 2012-07-10 10:40 -0700
Re: Function casting - UB? Tim Rentsch <txr@alumni.caltech.edu> - 2012-07-08 11:06 -0700
Page 1 of 2 [1] 2 Next page →
| From | nroberts <roberts.noah@gmail.com> |
|---|---|
| Date | 2012-06-27 08:50 -0700 |
| Subject | Function casting - UB? |
| Message-ID | <0c176b1e-39f3-4e65-8a7b-eca16b8955c5@d24g2000yqh.googlegroups.com> |
In C++ this would be totally undefined. How about in C? It works on
my machine with my compiler...
If it's UB, is it one of those rules that pretty much has to work
anyway? What I mean is things like the fact that it's UB to assign to
one part of a union and read from another but it generally works and
is a pretty important construct. Is this like that?
#include <stdio.h>
typedef void (*fun)(int, int);
void f(int i) {
int q = 33;
printf("%d %d\n", i, q);
}
int main(void) {
fun funptr = (fun)f;
funptr(3,2);
return 0;
}
output => 3 33
[toc] | [next] | [standalone]
| From | Eric Sosman <esosman@ieee-dot-org.invalid> |
|---|---|
| Date | 2012-06-27 12:49 -0400 |
| Message-ID | <jsfdik$vtr$1@dont-email.me> |
| In reply to | #22865 |
On 6/27/2012 11:50 AM, nroberts wrote:
> In C++ this would be totally undefined. How about in C? It works on
> my machine with my compiler...
>
> If it's UB, is it one of those rules that pretty much has to work
> anyway? What I mean is things like the fact that it's UB to assign to
> one part of a union and read from another but it generally works and
> is a pretty important construct. Is this like that?
>
> #include <stdio.h>
>
> typedef void (*fun)(int, int);
>
> void f(int i) {
> int q = 33;
> printf("%d %d\n", i, q);
> }
>
> int main(void) {
> fun funptr = (fun)f;
> funptr(3,2);
>
> return 0;
> }
>
> output => 3 33
Undefined behavior, because the type of the pointer expression
`funptr' used in the call does not match the type of the called
function `f'. (The fact that the `(fun)' cast was necessary
should alert you to the mismatch.)
It's likely to "work" on a good many systems. For example,
on a system where the first four or so "sufficiently small"
arguments are passed in a few designated registers, `main' will
fill two registers and `f' will use only one of them, and there's
a decent chance that the one `f' uses will be the one `main' put
the 3 into. But it would be a bad idea to generalize from the
apparent success of this simple case to the notion "It works!"
Throw in a floating-point argument, or a union argument, or even
more than MagicNumber plain int arguments, and things may well go
blooey. Make the function struct- or union-valued, and things are
more likely than not to go blooey.
And then, there's wide variety in function linkage mechanisms.
Sometimes the caller takes care of both setting up and disposing
of the argument list, but sometimes the caller sets it up and the
callee disposes -- and if the callee does cleanup for a one-argument
list while the caller expected it to clean up two, it's blooey again.
Does it "pretty much" have to work? That sort of depends on
your definition of "pretty much," but my take would be "No."
Besides: Can you come up with a good reason to want to do such
a perverted thing in the first place? In what way does this merit
being called "a pretty important construct?"
--
Eric Sosman
esosman@ieee-dot-org.invalid
[toc] | [prev] | [next] | [standalone]
| From | Heikki Kallasjoki <fis+usenet@zem.fi> |
|---|---|
| Date | 2012-06-27 17:54 +0000 |
| Message-ID | <slrnjumi6h.dba.fis@iris.zem.fi> |
| In reply to | #22870 |
On 2012-06-27, Eric Sosman <esosman@ieee-dot-org.invalid> wrote:
> On 6/27/2012 11:50 AM, nroberts wrote:
>> In C++ this would be totally undefined. How about in C? It works on
>> my machine with my compiler...
...
>> typedef void (*fun)(int, int);
>>
>> void f(int i) {
>> int q = 33;
>> printf("%d %d\n", i, q);
>> }
>>
>> int main(void) {
>> fun funptr = (fun)f;
>> funptr(3,2);
>>
>> return 0;
>> }
...
> the 3 into. But it would be a bad idea to generalize from the
> apparent success of this simple case to the notion "It works!"
I fully agree that empirical experimentation is not terribly useful
(and/or perhaps interesting), but FWIW, a counter-example:
$ cat tmp.c
#include <windows.h>
int WINAPI f(int a) { return a; }
int g(void) {
int WINAPI (*p)(int, int) = (int WINAPI (*)(int, int))f;
return p(0, 1);
}
int main(void) {
return g();
}
$ i586-mingw32msvc-gcc -o tmp.exe tmp.c -O2 -fomit-frame-pointer
$ wine ./tmp.exe
wine: Unhandled page fault on read access to 0x00000001 at address 0x1
(thread 0022), starting debugger...
(For the "not really comp.lang.c material" details, see below.)
> And then, there's wide variety in function linkage mechanisms.
> Sometimes the caller takes care of both setting up and disposing
> of the argument list, but sometimes the caller sets it up and the
> callee disposes -- and if the callee does cleanup for a one-argument
> list while the caller expected it to clean up two, it's blooey again.
The above case (involving the "WINAPI"/"stdcall" calling convention) is
an example of the latter, and with the frame pointer omitted, 'g' ends
up using the second provided parameter as its return address.
--
Heikki Kallasjoki
[toc] | [prev] | [next] | [standalone]
| From | "io_x" <a@b.c.invalid> |
|---|---|
| Date | 2012-06-28 18:58 +0200 |
| Message-ID | <4fec8bc3$0$1385$4fafbaef@reader2.news.tin.it> |
| In reply to | #22877 |
"Heikki Kallasjoki" <fis+usenet@zem.fi> ha scritto nel messaggio
news:slrnjumi6h.dba.fis@iris.zem.fi...
> On 2012-06-27, Eric Sosman <esosman@ieee-dot-org.invalid> wrote:
>> On 6/27/2012 11:50 AM, nroberts wrote:
>>> In C++ this would be totally undefined. How about in C? It works on
>>> my machine with my compiler...
> ...
>>> typedef void (*fun)(int, int);
>>>
>>> void f(int i) {
>>> int q = 33;
>>> printf("%d %d\n", i, q);
>>> }
>>>
>>> int main(void) {
>>> fun funptr = (fun)f;
>>> funptr(3,2);
>>>
>>> return 0;
>>> }
> ...
>> the 3 into. But it would be a bad idea to generalize from the
>> apparent success of this simple case to the notion "It works!"
>
> I fully agree that empirical experimentation is not terribly useful
> (and/or perhaps interesting), but FWIW, a counter-example:
>
> $ cat tmp.c
> #include <windows.h>
>
> int WINAPI f(int a) { return a; }
>
> int g(void) {
> int WINAPI (*p)(int, int) = (int WINAPI (*)(int, int))f;
so here you cast as a function of 2 args one function of 1 arg ...
> return p(0, 1);
> }
>
> int main(void) {
> return g();
> }
>
> $ i586-mingw32msvc-gcc -o tmp.exe tmp.c -O2 -fomit-frame-pointer
> $ wine ./tmp.exe
> wine: Unhandled page fault on read access to 0x00000001 at address 0x1
> (thread 0022), starting debugger...
>
> (For the "not really comp.lang.c material" details, see below.)
>
>> And then, there's wide variety in function linkage mechanisms.
>> Sometimes the caller takes care of both setting up and disposing
>> of the argument list, but sometimes the caller sets it up and the
>> callee disposes -- and if the callee does cleanup for a one-argument
>> list while the caller expected it to clean up two, it's blooey again.
>
> The above case (involving the "WINAPI"/"stdcall" calling convention) is
> an example of the latter, and with the frame pointer omitted, 'g' ends
> up using the second provided parameter as its return address.
>
> --
> Heikki Kallasjoki
i not understand what you whant to say; but i like function pointers
and using pointers [and cast to functions] and call as function...
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <txr@alumni.caltech.edu> |
|---|---|
| Date | 2012-06-27 10:37 -0700 |
| Message-ID | <kfnzk7o4pxf.fsf@x-alumni2.alumni.caltech.edu> |
| In reply to | #22865 |
nroberts <roberts.noah@gmail.com> writes:
> In C++ this would be totally undefined. How about in C? It works on
> my machine with my compiler...
The example you show is clearly and explicitly undefined behavior.
> If it's UB, is it one of those rules that pretty much has to work
> anyway? What I mean is things like the fact that it's UB to assign to
> one part of a union and read from another but it generally works and
> is a pretty important construct. Is this like that?
No, it's completely different, because it is both allowed to fail
and is likely to fail on some platforms. The other example you give,
assigning to one member of a union and reading from another, is
actually defined behavior, _not_ undefined behavior.
> #include <stdio.h>
>
> typedef void (*fun)(int, int);
>
> void f(int i) {
> int q = 33;
> printf("%d %d\n", i, q);
> }
>
> int main(void) {
> fun funptr = (fun)f;
> funptr(3,2);
>
> return 0;
> }
>
> output => 3 33
The call to f() through 'funptr' is undefined behavior because
the type of the function pointer used to call is not compatible
with the type of the function actually being called. And that
isn't just a theoretical problem.
[toc] | [prev] | [next] | [standalone]
| From | Johannes Bauer <dfnsonfsduifb@gmx.de> |
|---|---|
| Date | 2012-06-28 09:23 +0200 |
| Message-ID | <jsh0qa$49g$1@news.albasani.net> |
| In reply to | #22876 |
On 27.06.2012 19:37, Tim Rentsch wrote: > The other example you give, > assigning to one member of a union and reading from another, is > actually defined behavior, _not_ undefined behavior. I also was under the impression that writing to member x and reading member y of a union is UB. Wikipedia says "This is not, however, a safe use of unions in general.", which is pretty vague (i.e. it's not clear which cases are safe and which are not). Could you elaborate on why you think this is well-defined? Best regards, Johannes -- >> Wo hattest Du das Beben nochmal GENAU vorhergesagt? > Zumindest nicht öffentlich! Ah, der neueste und bis heute genialste Streich unsere großen Kosmologen: Die Geheim-Vorhersage. - Karl Kaos über Rüdiger Thomas in dsa <hidbv3$om2$1@speranza.aioe.org>
[toc] | [prev] | [next] | [standalone]
| From | Johannes Bauer <dfnsonfsduifb@gmx.de> |
|---|---|
| Date | 2012-06-28 09:27 +0200 |
| Message-ID | <jsh10i$49g$2@news.albasani.net> |
| In reply to | #22916 |
On 28.06.2012 09:23, Johannes Bauer wrote: > On 27.06.2012 19:37, Tim Rentsch wrote: >> The other example you give, >> assigning to one member of a union and reading from another, is >> actually defined behavior, _not_ undefined behavior. > > I also was under the impression that writing to member x and reading > member y of a union is UB. Wikipedia says "This is not, however, a safe > use of unions in general.", which is pretty vague (i.e. it's not clear > which cases are safe and which are not). > > Could you elaborate on why you think this is well-defined? Ah, I just read James' response further down. Interesting. Really thought this was undefined. Is this a recent change? Best regards, Johannes -- >> Wo hattest Du das Beben nochmal GENAU vorhergesagt? > Zumindest nicht öffentlich! Ah, der neueste und bis heute genialste Streich unsere großen Kosmologen: Die Geheim-Vorhersage. - Karl Kaos über Rüdiger Thomas in dsa <hidbv3$om2$1@speranza.aioe.org>
[toc] | [prev] | [next] | [standalone]
| From | Jens Gustedt <jens.gustedt@loria.fr> |
|---|---|
| Date | 2012-06-28 12:03 +0200 |
| Message-ID | <4FEC2BDA.3090801@loria.fr> |
| In reply to | #22917 |
Am 28.06.2012 09:27, schrieb Johannes Bauer: > On 28.06.2012 09:23, Johannes Bauer wrote: >> On 27.06.2012 19:37, Tim Rentsch wrote: >>> The other example you give, >>> assigning to one member of a union and reading from another, is >>> actually defined behavior, _not_ undefined behavior. >> >> I also was under the impression that writing to member x and reading >> member y of a union is UB. Wikipedia says "This is not, however, a safe >> use of unions in general.", which is pretty vague (i.e. it's not clear >> which cases are safe and which are not). >> >> Could you elaborate on why you think this is well-defined? > > Ah, I just read James' response further down. Interesting. Really > thought this was undefined. Is this a recent change? I think this was not considered as a change in contents but given as more precision on the intent. n1256.pdf has modification marks in this region so I suppose that these came with TC3. They state > When a value is stored in a member of an object of union type, the > bytes of the object representation that do not correspond to that > member but do correspond to other members take unspecified values. which in terms of the standard means that it is only UB if these unspecific values are "forbidden" values for that type, in particular trap representations. This means that for most of modern architectures manipulating integer values (except of _Bool) through unions is completely ok. Floating point values, _Bool, and pointer types must be treated with more care. Jens
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <txr@alumni.caltech.edu> |
|---|---|
| Date | 2012-06-29 11:10 -0700 |
| Message-ID | <kfn8vf62dmo.fsf@x-alumni2.alumni.caltech.edu> |
| In reply to | #22920 |
Jens Gustedt <jens.gustedt@loria.fr> writes: > Am 28.06.2012 09:27, schrieb Johannes Bauer: >> On 28.06.2012 09:23, Johannes Bauer wrote: >>> On 27.06.2012 19:37, Tim Rentsch wrote: >>>> The other example you give, >>>> assigning to one member of a union and reading from another, is >>>> actually defined behavior, _not_ undefined behavior. >>> >>> I also was under the impression that writing to member x and reading >>> member y of a union is UB. Wikipedia says "This is not, however, a safe >>> use of unions in general.", which is pretty vague (i.e. it's not clear >>> which cases are safe and which are not). >>> >>> Could you elaborate on why you think this is well-defined? >> >> Ah, I just read James' response further down. Interesting. Really >> thought this was undefined. Is this a recent change? > > I think this was not considered as a change in contents but given as > more precision on the intent. n1256.pdf has modification marks in this > region so I suppose that these came with TC3. [snip] Yes, if you read the Defect Report that prompted the change I think you'll find that the intention was that the behavior required was supposed to be the same all along (ie, since C90 and presumably also before that), but changes in wording in other places raised a concern that this (unchanged) requirement was not evident enough without the footnote.
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <txr@alumni.caltech.edu> |
|---|---|
| Date | 2012-06-29 11:03 -0700 |
| Message-ID | <kfnd34i2dya.fsf@x-alumni2.alumni.caltech.edu> |
| In reply to | #22916 |
Johannes Bauer <dfnsonfsduifb@gmx.de> writes: > On 27.06.2012 19:37, Tim Rentsch wrote: >> The other example you give, >> assigning to one member of a union and reading from another, is >> actually defined behavior, _not_ undefined behavior. > > I also was under the impression that writing to member x and reading > member y of a union is UB. Wikipedia says "This is not, however, a safe > use of unions in general.", which is pretty vague (i.e. it's not clear > which cases are safe and which are not). > > Could you elaborate on why you think this is well-defined? Besides the particular footnote (which you already mentioned in your own followup), there is just the normative text pertaining to types and storage access. If you read through the two main sections on types (6.2.5 and 6.2.6), and also the description of what happens on lvalue-to-value conversion, I think it's pretty easy to see that the definition is there (although I freely admit it isn't expressed as directly as one might like). Basically, the same passages that explain how ordinary (ie, non-union-member) access works also explain how access to union members work; the only thing that's missing is knowing that the respective memories overlap, which is stated in 6.2.5. There is another detail having to do with effective type rules, but that doesn't contribute to defining the semantics; it just needs to be checked to make sure the effective type rules don't _un_define the semantics (and they don't, but if you're interested look at 6.5 p6&7).
[toc] | [prev] | [next] | [standalone]
| From | Les Cargill <lcargill99@comcast.com> |
|---|---|
| Date | 2012-06-27 12:58 -0500 |
| Message-ID | <jsfhe5$m4n$1@dont-email.me> |
| In reply to | #22865 |
nroberts wrote:
> In C++ this would be totally undefined. How about in C? It works on
> my machine with my compiler...
>
> If it's UB, is it one of those rules that pretty much has to work
> anyway? What I mean is things like the fact that it's UB to assign to
> one part of a union and read from another but it generally works and
> is a pretty important construct. Is this like that?
>
No. The example given is just wrong, period.
> #include <stdio.h>
>
> typedef void (*fun)(int, int);
>
> void f(int i) {
> int q = 33;
> printf("%d %d\n", i, q);
> }
>
> int main(void) {
> fun funptr = (fun)f;
> funptr(3,2);
>
> return 0;
> }
>
> output => 3 33
>
--
Les Cargill
[toc] | [prev] | [next] | [standalone]
| From | Les Cargill <lcargill99@comcast.com> |
|---|---|
| Date | 2012-06-27 12:59 -0500 |
| Message-ID | <jsfhfs$m4n$2@dont-email.me> |
| In reply to | #22865 |
nroberts wrote:
> In C++ this would be totally undefined. How about in C? It works on
> my machine with my compiler...
>
> If it's UB, is it one of those rules that pretty much has to work
> anyway? What I mean is things like the fact that it's UB to assign to
> one part of a union and read from another
I am not sure that that is undefined behavior. That's sort of what
unions are *for*.
> but it generally works and
> is a pretty important construct. Is this like that?
>
> #include <stdio.h>
>
> typedef void (*fun)(int, int);
>
> void f(int i) {
> int q = 33;
> printf("%d %d\n", i, q);
> }
>
> int main(void) {
> fun funptr = (fun)f;
> funptr(3,2);
>
> return 0;
> }
>
> output => 3 33
>
--
Les Cargill
[toc] | [prev] | [next] | [standalone]
| From | James Kuyper <jameskuyper@verizon.net> |
|---|---|
| Date | 2012-06-27 14:45 -0400 |
| Message-ID | <4FEB54D7.2030106@verizon.net> |
| In reply to | #22879 |
On 06/27/2012 01:59 PM, Les Cargill wrote: > nroberts wrote: ... >> anyway? What I mean is things like the fact that it's UB to assign to >> one part of a union and read from another > > I am not sure that that is undefined behavior. That's sort of what > unions are *for*. A footnote in the current version of the standard says that the result of reading from a different member of a union than the one last written is that the bit pattern stored in that memory is reinterpreted according to the type of the member being read; it would therefore have defined behavior, so long as that bit pattern is a valid one for that type. Some have claimed that this conclusion can be derived from the normative text of the standard, but I find the argument supporting that claim weak. There's certainly no normative text that says so directly. However, that is how unions were always intended to work, whether or not the normative text of the standard has ever actually said so.
[toc] | [prev] | [next] | [standalone]
| From | "christian.bau" <christian.bau@cbau.wanadoo.co.uk> |
|---|---|
| Date | 2012-06-27 14:55 -0700 |
| Message-ID | <dd47ef15-63a6-4d9b-a2f4-9673ac644260@fr28g2000vbb.googlegroups.com> |
| In reply to | #22882 |
On Jun 27, 7:45 pm, James Kuyper <jameskuy...@verizon.net> wrote: > A footnote in the current version of the standard says that the result > of reading from a different member of a union than the one last written > is that the bit pattern stored in that memory is reinterpreted according > to the type of the member being read; it would therefore have defined > behavior, so long as that bit pattern is a valid one for that type. > Some have claimed that this conclusion can be derived from the normative > text of the standard, but I find the argument supporting that claim > weak. There's certainly no normative text that says so directly. > However, that is how unions were always intended to work, whether or not > the normative text of the standard has ever actually said so. I had to check that, and you are right (footnote 95 in the N1570 draft). I think there is a problem. Say long and float have the same size, I have a union containing a long and a float, I write to the long and read the float, then I am supposed to get a float with exactly those bits that I stored. That's perfectly fine. But what if the compiler doesn't know that both are elements of the same union? If I just have a long*, and a float*, which _might_ point to members of the same union, but the compiler doesn't know. Does the rule apply then as well? That would completely destroy what is said in other places. I'd prefer if this was said in the standard explicitely, but with the restriction that the value must be written, then read, using the . or - > operators.
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <txr@alumni.caltech.edu> |
|---|---|
| Date | 2012-06-27 17:44 -0700 |
| Message-ID | <kfn395g465i.fsf@x-alumni2.alumni.caltech.edu> |
| In reply to | #22902 |
"christian.bau" <christian.bau@cbau.wanadoo.co.uk> writes: > On Jun 27, 7:45 pm, James Kuyper <jameskuy...@verizon.net> wrote: > >> A footnote in the current version of the standard says that the result >> of reading from a different member of a union than the one last written >> is that the bit pattern stored in that memory is reinterpreted according >> to the type of the member being read; it would therefore have defined >> behavior, so long as that bit pattern is a valid one for that type. >> Some have claimed that this conclusion can be derived from the normative >> text of the standard, but I find the argument supporting that claim >> weak. There's certainly no normative text that says so directly. >> However, that is how unions were always intended to work, whether or not >> the normative text of the standard has ever actually said so. > > I had to check that, and you are right (footnote 95 in the N1570 > draft). I think there is a problem. Say long and float have the same > size, I have a union containing a long and a float, I write to the > long and read the float, then I am supposed to get a float with > exactly those bits that I stored. That's perfectly fine. > > But what if the compiler doesn't know that both are elements of the > same union? If I just have a long*, and a float*, which _might_ point > to members of the same union, but the compiler doesn't know. Does the > rule apply then as well? That would completely destroy what is said in > other places. This case is different, because it is addressed by different portions of the effective type rules. In particular, using the '.' or '->' form of access, the lvalue being accessed has a declared type, and so those accesses never violate effective type rules. When access is done using pointers, the rule for determining effective type is different, so the two accesses may very well run afoul of the effective type requirements. > I'd prefer if this was said in the standard explicitely, but with the > restriction that the value must be written, then read, using the . or - >> operators. Unfortunately the Standard often expresses itself rather obliquely, and this case certainly falls into that category. However, it should be easy to see that the two different cases you bring up are covered under different areas of the effective type rules. See 6.5 p6. Note especially the first sentence, which applies in the case of member access (ie, through '.' or '->', but which does not apply in the case of pointer access.
[toc] | [prev] | [next] | [standalone]
| From | Joshua Maurice <joshuamaurice@gmail.com> |
|---|---|
| Date | 2012-07-07 16:46 -0700 |
| Message-ID | <d90ed80f-f97e-4d01-8dee-09cf8fa94165@f8g2000pbf.googlegroups.com> |
| In reply to | #22911 |
On Jun 27, 5:44 pm, Tim Rentsch <t...@alumni.caltech.edu> wrote:
> "christian.bau" <christian....@cbau.wanadoo.co.uk> writes:
> > On Jun 27, 7:45 pm, James Kuyper <jameskuy...@verizon.net> wrote:
>
> >> A footnote in the current version of the standard says that the result
> >> of reading from a different member of a union than the one last written
> >> is that the bit pattern stored in that memory is reinterpreted according
> >> to the type of the member being read; it would therefore have defined
> >> behavior, so long as that bit pattern is a valid one for that type.
> >> Some have claimed that this conclusion can be derived from the normative
> >> text of the standard, but I find the argument supporting that claim
> >> weak. There's certainly no normative text that says so directly.
> >> However, that is how unions were always intended to work, whether or not
> >> the normative text of the standard has ever actually said so.
>
> > I had to check that, and you are right (footnote 95 in the N1570
> > draft). I think there is a problem. Say long and float have the same
> > size, I have a union containing a long and a float, I write to the
> > long and read the float, then I am supposed to get a float with
> > exactly those bits that I stored. That's perfectly fine.
>
> > But what if the compiler doesn't know that both are elements of the
> > same union? If I just have a long*, and a float*, which _might_ point
> > to members of the same union, but the compiler doesn't know. Does the
> > rule apply then as well? That would completely destroy what is said in
> > other places.
>
> This case is different, because it is addressed by different
> portions of the effective type rules. In particular, using
> the '.' or '->' form of access, the lvalue being accessed
> has a declared type, and so those accesses never violate effective
> type rules. When access is done using pointers, the rule for
> determining effective type is different, so the two accesses
> may very well run afoul of the effective type requirements.
>
> > I'd prefer if this was said in the standard explicitely, but with the
> > restriction that the value must be written, then read, using the . or -
> >> operators.
>
> Unfortunately the Standard often expresses itself rather obliquely,
> and this case certainly falls into that category. However, it should
> be easy to see that the two different cases you bring up are covered
> under different areas of the effective type rules. See 6.5 p6.
> Note especially the first sentence, which applies in the case of
> member access (ie, through '.' or '->', but which does not apply
> in the case of pointer access.
Sorry. Some silly questions if I may, please? Consider the following
programs:
int main(void)
{
union { int x; float y; } u;
u.y = 2;
u.x = 1;
return u.x;
}
/* ---- */
int main(void)
{
union { int x; float y; } u;
float * y = &u.y;
*y = 2;
int * x = &u.x;
*x = 1;
return u.x;
}
/* ---- */
int main(void)
{
union { int x; float y; } u;
float * y = &u.y;
int * x = &u.x;
*y = 2;
*x = 1;
return u.x;
}
/* ---- */
void foo(int * x, float * y)
{
*y = 2;
*x = 1;
}
int main(void)
{
union { int x; float y; } u;
float * y = &u.y;
int * x = &u.x;
foo(x, y);
return u.x;
}
/* ---- */
The last program above, except with foo in a different translation
unit.
Where exactly do you think we cross from defined behavior to undefined
behavior? I would argue that the first example is clearly not UB, and
the last example with foo() in a different translation unit is
probably UB. Specifically, the intent of the effective type rules is
to allow the compiler to do additional aliasing analysis and reorder
reads and writes that are sufficiently differently typed. With foo()
in a different translation unit, we want the compiler to be able to
reorder the writes to x and y in foo() from type aliasing analysis,
but if we do that then we'll change the semantics of the last program
and have it return garbage.
I don't have a strong opinion on this one. It seems that the intent of
the type access rules and the existence of unions is an inherent
contradiction - with several plausible ways out, of course.
[toc] | [prev] | [next] | [standalone]
| From | Barry Schwarz <schwarzb@dqel.com> |
|---|---|
| Date | 2012-07-08 09:19 -0700 |
| Message-ID | <lcbjv751nk5eg1pama9vbkpof1b45ars45@4ax.com> |
| In reply to | #23193 |
On Sat, 7 Jul 2012 16:46:42 -0700 (PDT), Joshua Maurice
<joshuamaurice@gmail.com> wrote:
>On Jun 27, 5:44 pm, Tim Rentsch <t...@alumni.caltech.edu> wrote:
>> "christian.bau" <christian....@cbau.wanadoo.co.uk> writes:
>> > On Jun 27, 7:45 pm, James Kuyper <jameskuy...@verizon.net> wrote:
>>
>> >> A footnote in the current version of the standard says that the result
>> >> of reading from a different member of a union than the one last written
>> >> is that the bit pattern stored in that memory is reinterpreted according
>> >> to the type of the member being read; it would therefore have defined
>> >> behavior, so long as that bit pattern is a valid one for that type.
<snip>
>Sorry. Some silly questions if I may, please? Consider the following
>programs:
>
> int main(void)
> {
> union { int x; float y; } u;
> u.y = 2;
> u.x = 1;
> return u.x;
> }
<snip three similar examples>
>Where exactly do you think we cross from defined behavior to undefined
>behavior? I would argue that the first example is clearly not UB, and
None of your examples perform the sequence of operations under
discussion. In every case, you store a value in one member of the
union, store a value in a different member of the union, and then
access the member which was last stored. Accessing the last stored
member never yields undefined behavior.
--
Remove del for email
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <txr@alumni.caltech.edu> |
|---|---|
| Date | 2012-07-08 11:13 -0700 |
| Message-ID | <kfnmx3a6rz2.fsf@x-alumni2.alumni.caltech.edu> |
| In reply to | #23202 |
Barry Schwarz <schwarzb@dqel.com> writes:
> On Sat, 7 Jul 2012 16:46:42 -0700 (PDT), Joshua Maurice
> <joshuamaurice@gmail.com> wrote:
>
>>On Jun 27, 5:44 pm, Tim Rentsch <t...@alumni.caltech.edu> wrote:
>>> "christian.bau" <christian....@cbau.wanadoo.co.uk> writes:
>>> > On Jun 27, 7:45 pm, James Kuyper <jameskuy...@verizon.net> wrote:
>>>
>>> >> A footnote in the current version of the standard says that the result
>>> >> of reading from a different member of a union than the one last written
>>> >> is that the bit pattern stored in that memory is reinterpreted according
>>> >> to the type of the member being read; it would therefore have defined
>>> >> behavior, so long as that bit pattern is a valid one for that type.
>
> <snip>
>
>>Sorry. Some silly questions if I may, please? Consider the following
>>programs:
>>
>> int main(void)
>> {
>> union { int x; float y; } u;
>> u.y = 2;
>> u.x = 1;
>> return u.x;
>> }
>
> <snip three similar examples>
>
>>Where exactly do you think we cross from defined behavior to undefined
>>behavior? I would argue that the first example is clearly not UB, and
>
> None of your examples perform the sequence of operations under
> discussion. In every case, you store a value in one member of the
> union, store a value in a different member of the union, and then
> access the member which was last stored. Accessing the last stored
> member never yields undefined behavior.
Only the first example (ie, the only one not snipped) stores into
members. The other examples store into objects that happen to
coincide with memory areas corresponding to members of u, but
that's not the same as storing into members. If nothing else,
which parts of the effective type rules govern the accesses
are different in the two cases.
[toc] | [prev] | [next] | [standalone]
| From | Barry Schwarz <schwarzb@dqel.com> |
|---|---|
| Date | 2012-07-08 15:24 -0700 |
| Message-ID | <732kv7ps7h8b8g33fo9vn8j54mf60nesda@4ax.com> |
| In reply to | #23205 |
On Sun, 08 Jul 2012 11:13:53 -0700, Tim Rentsch <txr@alumni.caltech.edu> wrote: >Barry Schwarz <schwarzb@dqel.com> writes: snip >> None of your examples perform the sequence of operations under >> discussion. In every case, you store a value in one member of the >> union, store a value in a different member of the union, and then >> access the member which was last stored. Accessing the last stored >> member never yields undefined behavior. > >Only the first example (ie, the only one not snipped) stores into >members. The other examples store into objects that happen to >coincide with memory areas corresponding to members of u, but >that's not the same as storing into members. If nothing else, >which parts of the effective type rules govern the accesses >are different in the two cases. Do I understand correctly that storing into a member and storing into the memory occupied by that member are somehow different? -- Remove del for email
[toc] | [prev] | [next] | [standalone]
| From | Joshua Maurice <joshuamaurice@gmail.com> |
|---|---|
| Date | 2012-07-08 18:12 -0700 |
| Message-ID | <4e13d30a-e5a5-49a3-a7ae-f4796acbc1a8@l6g2000pbi.googlegroups.com> |
| In reply to | #23209 |
On Jul 8, 3:24 pm, Barry Schwarz <schwa...@dqel.com> wrote:
> On Sun, 08 Jul 2012 11:13:53 -0700, Tim Rentsch
> >Only the first example (ie, the only one not snipped) stores into
> >members. The other examples store into objects that happen to
> >coincide with memory areas corresponding to members of u, but
> >that's not the same as storing into members. If nothing else,
> >which parts of the effective type rules govern the accesses
> >are different in the two cases.
>
> Do I understand correctly that storing into a member and storing into
> the memory occupied by that member are somehow different?
I would hope not! (But maybe.) I agree that the current rules are
unclear.
I think/hope that:
struct foo { int x; };
int main(void)
{
struct foo f;
f.x = 1;
}
is definitionally equivalent to:
struct foo { int x; };
int main(void)
{
struct foo f;
int * y;
y = &f.x;
*y = 1;
}
Any decision that makes "f.x = 1;" somehow different than "y = &f.x;
*y = 1;" is my least preferred alternative.
I'd much rather have rules that require the compiler to limit its type
aliasing optimizations when unions are in scope. Basically, a rule in
the standard somewhere which says something like the following. Please
note that I just whipped this up, and I have no clue if it's actually
"correct". It could very probably/definitely be fixed, improved, etc.
I'm just trying to get the ball rolling. There's closely related
alternative formulations that would also be appealing to me.
Quickie Definition: The "lifetime" of a pointer value is the
contiguous interval of time of the program execution, starting when
the pointer value is "created", and ending when the last "copy" or
"derivation" of the pointer value ceases to exist in an object.
Example:
#include <stdlib.h>
int main(void)
{
{
int a[2];
int * x;
int * y;
{
x = a; /* this statement "creates" a pointer value */
}
/*the pointer object "x" exists, and it contains the pointer
value, so it's still "alive" */
y = x + 1;
x = 0;
/* the pointer value is still "alive" because a "derivation" of
it exists in the pointer object "y" */
}
/* the pointer value is now "dead", and the pointer value lifetime
has ended */
}
New Rule: For two accesses to two sufficiently differently typed
members of a union, if:
- the accesses are a write and a read, or two writes, to the union
member objects or sub-objects thereof, and
- the pointer value lifetimes of the pointer values used to do the
accesses overlap, and
- both accesses are done in scopes where the union definition is not
visible, then
- the program has undefined behavior.
This approach formulated disallows all aliasing optimization with the
types in a union when the union definition is in scope. Perhaps there
are "nicer" ways to do this without such a substantial penalty.
[toc] | [prev] | [next] | [standalone]
Page 1 of 2 [1] 2 Next page →
Back to top | Article view | comp.lang.c
csiph-web