4

Is the following undefined behaviour?

 union {
   int foo;
   float bar;
 } baz;

 baz.foo = 3.14 * baz.bar;

I remember that writing and reading from the same underlying memory between two sequence points is UB, but I am not certain.

melpomene
  • 81,915
  • 7
  • 76
  • 137
Vroomfondel
  • 2,368
  • 1
  • 14
  • 28

2 Answers2

4

Disclaimer: This answer addresses C++.

You're accessing an object whose lifetime hasn't begun yet - baz.bar - which induces UB by [basic.life]/(6.1).

Assuming bar has been brought to life (e.g. by initializing it), your code is fine; before the assignment, foo need not be alive as no operation is performed that depends on its value, and during it, the active member is changed by reusing the memory and effectively initializing it. The current rules aren't clear about the latter; see CWG #1116. However, the status quo is that such assignments are indeed setting the target member as active (=alive).

Note that the assignment is sequenced (i.e. guaranteed to happen) after the value computation of the operands - see [expr.ass]/1.

Columbo
  • 58,324
  • 8
  • 149
  • 196
  • @Barry It means the latter. See CWG 556. – Columbo Oct 22 '15 at 21:57
  • Currently I do not have C++ standard copy to check for more detail but I have some doubt on your your explanation. – haccks Oct 22 '15 at 21:58
  • 1
    @Columbo But then that's weird right? `u.a = u.b` is undefined, but `u.a = B(u.b)` is fine? – Barry Oct 22 '15 at 21:59
  • @Barry It is weird, but it's the intention of the wording AFAICS ("instead of being a general statement about aliasing, it's describing the situation in which the source of the value being assigned is storage that overlaps the storage of the target object"). The target object is a temporary of type `float`, but that temporary's storage does certainly not overlap `baz.foo`s. Then again, perhaps the committee was not precise enough in wording their note, and they actually did mean that `u.a=f(u.b)` is not defined. Eitherway, lifetime rules are a mess. – Columbo Oct 22 '15 at 22:06
  • 1
    The behavior is definitely defined in C, supposing `baz.bar` has been initialized and `baz.foo` has not subsequently been written to. Given the C / C++ reconciliation efforts in the 2011 versions of the standards, I would be very surprised to find that the same code has undefined behavior in C++. – John Bollinger Oct 22 '15 at 22:09
  • @JohnBollinger It doesn't. What are you referring to? – Columbo Oct 22 '15 at 22:11
  • @Columbo, I'm referring to the earlier version of your answer, which said the opposite of what your answer now says. I hadn't yet received the update. Sorry for the noise. – John Bollinger Oct 22 '15 at 22:15
  • @JohnBollinger Sorry for confusing everyone with sloppy language-lawyering. :-) – Columbo Oct 22 '15 at 22:19
  • "_object whose lifetime hasn't begun yet - baz.bar_" what? – curiousguy Oct 28 '15 at 12:55
  • @curiousguy `bar` is not alive in the snippet of the asker. – Columbo Oct 28 '15 at 13:44
  • @Columbo Do you mean not initialized? – curiousguy Oct 28 '15 at 13:46
  • @curiousguy …effectively. I'd familiarize myself with standard terminology, though. – Columbo Oct 28 '15 at 15:06
3

I remember that writing and reading from the same underlying memory between two sequence points is UB, but I am not certain.

Reading and writing to the same memory location in the same expression does not invoke undefined behavior until and unless that location is modified more than once between two sequence points or the side effect is unsequenced relative to the value computation using the value at the same location.

C11: 6.5 Expressions:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. [...]

The expression

 baz.foo = 3.14 * baz.bar;  

has well defined behaviour if bar is initialized before. The reason is that the side effect to baz.foo is sequenced relative to the value computations of the objects baz.foo and baz.bar.

6.5.16 (p3):

[...] The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.

Community
  • 1
  • 1
haccks
  • 100,941
  • 24
  • 163
  • 252
  • That's not quite true: `printf("%d", i + i++);` has undefined behavior. – melpomene Oct 22 '15 at 21:36
  • 1
    An unsequenced read and write of the same memory location is UB just like two unsequenced writes. – Brian Bi Oct 22 '15 at 21:37
  • Your example contains an unsequenced read/write (or what do you mean by "unsequenced"?). – melpomene Oct 22 '15 at 21:39
  • Read the reference added to the answer. – haccks Oct 22 '15 at 21:41
  • @haccks That still doesn't explain what "unsequenced" means. – melpomene Oct 22 '15 at 21:43
  • @melpomene; What makes it difficult to understand the meaning of "unsequenced" in this context? – haccks Oct 22 '15 at 21:46
  • @haccks The lack of a definition. – melpomene Oct 22 '15 at 21:46
  • @melpomene; Where you find difficulties? I will try to explain. – haccks Oct 22 '15 at 21:48
  • @haccks I have literally no idea what you mean by "unsequenced". You might as well have said "*If a side effect on a scalar object is schnitzelkraut relative to either a different side effect ...*" – melpomene Oct 22 '15 at 21:49
  • 3
    @melpomene, "unsequenced" is a defined term in the C2011 standard. A complete definition would probably be inappropriately large for this venue, but I encourage you to read what the standard itself says about it, and about the "sequenced before" relation. – John Bollinger Oct 22 '15 at 21:50
  • @melpomene; What do you mean by *schnitzelkraut*? – haccks Oct 22 '15 at 21:51
  • @haccks I have no idea! That's why I keep asking you for a definition! – melpomene Oct 22 '15 at 21:52
  • @JohnBollinger Will you buy me a copy of the standard? – melpomene Oct 22 '15 at 21:53
  • 3
    @melpomene; Read [this](http://stackoverflow.com/a/31083924/2455888) to know everything about *sequence before* and *unsequenced*. Download n1570 pdf from [here](http://www.compsci.hunter.cuny.edu/~sweiss/resources/c11standard.pdf). – haccks Oct 22 '15 at 21:56
  • @haccks That answer defines *unsequenced*, but not *sequenced*. – melpomene Oct 22 '15 at 22:03
  • 2
    @haccks, you've omitted mention of the key provision of the standard relevant to this question: "The side effect of updating the stored value of the left operand [of an assignment operator] is sequenced after the value computations of the left and right operands" (from C11 6.5.16/3). Absent that, or some other provision having the same effect, the provision you quoted would hold that the behavior *is* undefined. – John Bollinger Oct 22 '15 at 22:03
  • @melpomene; That defines *sequenced*. – haccks Oct 22 '15 at 22:05
  • @JohnBollinger; Thanks for the reference. I should have mentioned that but I thought that it is obvious. – haccks Oct 22 '15 at 22:11
  • I think historically there was a requirement that an object may only be read and written in the same expression if both were accessed the same way. Certainly there are many machines where such a rule would enable useful optimizations (e.g. on an 8x51 clone with two data pointers, given "uint32_t *foo,*bar;" copying four bytes from "foo" to "bar" would be most efficiently implemented by copying the first byte of foo to the first byte of bar, then the second byte, third, and fourth, but that could malfunction if they overlap.) – supercat Oct 30 '15 at 20:48