Is This Code Well-Defined

Is this code well-defined?

This depends on how Sun is defined. The following is well-defined

struct A {
A &Fun(int);
A &Gun(int);
A &Sun(int&);
A &Tun();
};

void g() {
A someInstance;
int k = 0;
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
}

If you change the parameter type of Sun to int, it becomes undefined. Let's draw a tree of the version taking an int.

                     <eval body of Fun>
|
% // pre-call sequence point
|
{ S(increment, k) } <- E(++x)
|
E(Fun(++k).Gun(10))
|
.------+-----. .-- V(k)--%--<eval body of Sun>
/ \ /
E(Fun(++k).Gun(10).Sun(k))
|
.---------+---------.
/ \
E(Fun(++k).Gun(10).Sun(k).Tun())
|
% // full-expression sequence point

As can be seen, we have a read of k (designated by V(k)) and a side-effect on k (at the very top) that are not separated by a sequence point: In this expression, relative to each other sub-expression, there is no sequence point at all. The very bottom % signifies the full-expression sequence point.

Is this code well defined?

The evaluation order is not specified. The relevant section of the draft C++0x spec is 1.9, paragraphs 14 and 15:

14 Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated.

15 Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.

Here the relevant full-expression is:

TFoo(TFoo::bar1()).foobar1(TFoo::bar2()).foobar2(TFoo::bar3());

And so the evaluation of its subexpressions are unsequenced (unless there is an exception noted somewhere that I missed).

I am pretty sure earlier standards include language having the same effect but in terms of "sequence points".

[edit]

Paragraph 15 also says:

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [Note: Value computations and side effects associated with different argument expressions are unsequenced.— end note]

A "postfix expression designating the called function" is something like the foo().bar in foo().bar().

The "note" here merely clarifies that argument evaluation order is not an exception to the "unspecified order" default. By inference, neither is the evaluation order associated with the "postfix expression designating the called function"; or if you prefer, the evaluation order of the expression for the this argument. (If there were an exception, this would be the natural place to specify it. Or possibly section 5.2.2 that talks about function calls. Neither section says anything about the evaluation order for this example, so it is unspecified.)

Is this well defined code?

*d++ = ~(*d);

In this expression no object is having a new value stored to it more that once. The value of d + 1 is stored to d as a side effect of the incremement operator (d++) and the value of the object pointed to by d before this increment is written to by the assignment operator.

The issue is that d is read, not merely to determine the value to be written back to it (i.e. d + 1) but is also read to determine the address to read from in the right hand side sub-expression ~(*d).

This violates the third sentence of ISO/IEC 14882:2003 5 [expr] / 4 (first sentence omitted for brevity):

[...] Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

What is the difference (if any) between defined and well-defined?

Well-defined is what we usually call code containing neither undefined, unspecified or implementation-defined behavior. The definition of a strictly conforming program from chapter 4 might be helpful:

A strictly conforming program shall use only those features of the language and library specified in this International Standard. It shall not produce output dependent on any unspecified, undefined, or implementation-defined behavior, and shall not exceed any minimum implementation limit.

Any snippet of C code fulfilling the above could informally said to be well-defined, as in defined by the ISO C language standard and portable. As opposed to for example implementation-defined meaning documented compiler-specific behavior.

Is this code well defined by using sizeof operator?

Yes it is valid and will give the size of an int pointer (that's what the type of the arr parameter is, there are no array parameters in C) in bytes. 1

To get the size of an array in bytes with sizeof, the argument has to be an actual array, not a pointer.

Consequently, it's not possible to get the size of an array passed as a parameter inside a function without passing an extra parameter specifying the size.

So,

is there a way to make test() output different values by using sizeof on function argument handed in like on array?

no. (Without editing the function body.)

1 If you're wondering how it's possible to pass an array as a pointer parameter, it's because of the implicit array-to-pointer conversion, known as array decaying.

Is it well defined to reference a variable before it's constructed

The behaviour seems to be undefined in C++20. The change was made by P1358, resolving CWG 2256. As defect resolutions are generally retroactive, this code should be considered UB in all versions of C++.

According to [basic.life]/1:

... The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • its initialization (if any) is complete (including vacuous initialization) ...

At the time when f(a) is called, the object a has not yet begun its lifetime since its initialization has not completed. According to [basic.life]/7:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated ... any glvalue that refers to the original object may be used but only in limited ways. ... The program has undefined behavior if:

  • the glvalue is used to access the object ...

Thus, writing to a before its initialization has completed is UB, even though the storage has already been allocated.



Related Topics



Leave a reply



Submit