Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undefined behavior inside void expressions

Is a C implementation required to ignore undefined behaviors occurring during the evaluation of void expressions as if the evaluation itself never took place?

Considering C11, 6.3.2.2 §1:

If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)

This is related to the common idiom used to prevent compilers from warning about unused variables:

void f() {
  int a;
  (void)a;
}

But what if we have undefined behavior, such as:

void f() {
  int a;
  (void)(1/0);
}

Can I safely claim that this program contains no undefined behavior? The standard says that "its value or designator is discarded", but the "expression (...) is evaluated (...)", so the evaluation does seem to take place.

GCC/Clang do report the undefined behavior, since it is obvious in this case, but in a more subtle example they don't:

int main() {
  int a = 1;
  int b = 0;
  (void)(a/b);
  return 0;
}

Even with -O0, neither GCC nor Clang evaluate 1/0. But that also happens even without the cast to void, so it's not representative.

Pushing the argument to its extreme, wouldn't the simple evaluation of (void)a in my first example (where a is uninitialized) systematically trigger undefined behavior?

ISO C11 6.3.2.1 §2 does mention that:

If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

However, in the Annex J.2 Undefined behavior, the phrasing is slightly different:

The behavior is undefined in the following circumstances:

(...)

An lvalue designating an object of automatic storage duration that could have been declared with the register storage class is used in a context that requires the value of the designated object, but the object is uninitialized. (6.3.2.1).

This annex does lead to the interpretation that a void expression containing undefined behavior during its evaluation is not actually evaluated, but since it's just an annex, I'm not sure of its argumentative weight.

like image 681
anol Avatar asked Jul 24 '19 12:07

anol


People also ask

What is undefined behavior in programming?

So, in C/C++ programming, undefined behavior means when the program fails to compile, or it may execute incorrectly, either crashes or generates incorrect results, or when it may fortuitously do exactly what the programmer intended.

What causes undefined behavior C++?

In C/C++ bitwise shifting a value by a number of bits which is either a negative number or is greater than or equal to the total number of bits in this value results in undefined behavior.

What is C++ Behaviour?

In C++, it is defined as "behavior, for a well-formed program construct and correct data, that depends on the implementation." The C++ Standard also notes that the range of possible behaviors is usually provided.


1 Answers

This is related to the common idiom used to prevent compilers from warning about unused variables:

void f() {
  int a;
  (void)a;
}

Yes and no. I'd argue that that idiom turns an unused variable into a used one -- it appears in an expression -- with the cast to void serving to prevent compilers from complaining about the result of that expression going unused. But in the technical, language-lawyer sense, that particular expression of the idiom produces UB because the sub-expression a is subject to lvalue conversion when a's value is indeterminate. You've already quoted the relevant text of the standard.

But what if we have undefined behavior, such as:

void f() {
  int a;
  (void)(1/0);
}

Can I safely claim that this program contains no undefined behavior?

No.

The standard says that "its value or designator is discarded", but the "expression (...) is evaluated (...)", so the evaluation does seem to take place.

Yes, just as the expression a in your earlier example is also evaluated, also producing UB. UB arises from evaluation of the inner sub-expression. The conversion to type void is a separable consideration, exactly as a conversion to any other type would be.

GCC/Clang do report the undefined behavior, since it is obvious in this case, but in a more subtle example they don't:

Compiler behavior cannot be taken as indicative here. C does not require compilers to diagnose most undefined behaviors, not even those that could, in principle, be detected at compile time. Indeed, it is important to recognize that UB arising from incorrect code happens first and foremost at compile time, though of course it follows that if an executable is produced then it exhibits UB, too.

Pushing the argument to its extreme, wouldn't the simple evaluation of (void)a in my first example (where a is uninitialized) systematically trigger undefined behavior?

Yes, as I already remarked. But that does not mean that programs containing such constructions are obligated to misbehave. As a quality-of-implementation matter, I think it reasonable to hope that the expression statement (void)a; will be accepted by the compiler and will have no corresponding runtime behavior at all. But I cannot rely on the language standard to back me up on that.

This annex does lead to the interpretation that a void expression containing undefined behavior during its evaluation is not actually evaluated, but since it's just an annex, I'm not sure of its argumentative weight.

The plain wording of the normative text of the standard is quite sufficient here. The annex is not normative, but if there is any question about how the normative text is meant to be interpreted, then informative sections of the standard, such as Annex J, are one of the sources taken into account in sorting that out (but they are still only informative).

like image 105
John Bollinger Avatar answered Oct 03 '22 07:10

John Bollinger