Dividing by zero in a constant expression

拜拜、爱过 提交于 2019-12-20 09:46:56

问题


My toy compiler crashes if I divide by zero in a constant expression:

int x = 1 / 0;

Is this behaviour allowed by the C and/or C++ standards?


回答1:


The mere presence of 1 / 0 does not permit the compiler to crash. At most, it is permitted to assume that the expression will never be evaluated, and thus, that execution will never reach the given line.

If the expression is guaranteed to be evaluated, the standard imposes no requirements on the program or compiler. Then the compiler can crash.

1 / 0 is only UB if evaluated.

The C11 standard gives an explicit example of 1 / 0 being defined behavior when unevaluated:

Thus, in the following initialization,

        static int i = 2 || 1 / 0;

the expression is a valid integer constant expression with value one.

Section 6.6, footnote 118.

1 / 0 is not a constant expression.

Section 6.6 of the C11 standard, under Constraints, says

  1. Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.
  2. Each constant expression shall evaluate to a constant that is in the range of representable values for its type.

Since 1/0 does not evaluate to a constant in the range of values representable by an int, 1/0 is not a constant expression. This is a rule about what counts as a constant expression, like the rule about not having assignments in it. You can see that at least for C++, Clang doesn't consider 1/0 a constant expression:

prog.cc:3:18: error: constexpr variable 'x' must be initialized by a constant expression
   constexpr int x = 1/ 0 ;
                 ^   ~~~~

It wouldn't make much sense for an unevaluated 1 / 0 to be UB.

(x == 0) ? x : 1 / x is perfectly well-defined, even if x is 0 and evaluating 1/x is UB. If it were the case that (0 == 0) ? 0 : 1 / 0 were UB, that would be nonsense.




回答2:


Yes, division by zero is undefined behavior and neither the C nor C++ standard impose any requirements in such cases. Although in this case I believe you should at least issue a diagnostic(see below).

Before I go quoting the standards, I should note that although this may be conformant behavior quality of implementation is a different issue, being merely conforming is not the same as being useful. As far as I know the gcc, clang, Visual Studio and Intel(as per tpg2114) team consider internal compiler errors(ICEs) to be bugs that should be reported. It should be noted that both current gcc and clang produce a warning for this case seemingly regardless of flags provided. In the case where both operands are literals/constants, the case we have here, it seems rather straight forward to detect and provide a diagnostic for this. clang produces the following diagnostic for this case (see it live):

warning: division by zero is undefined [-Wdivision-by-zero]
int x = 1 / 0 ;
          ^ ~

From the draft C11 standard section 6.5.5 Multiplicative operators (emphasis mine):

The result of the / operator is the quotient from the division of the first operand by the second; [...] if the value of the second operand is zero, the behavior is undefined.

and so it is undefined behavior.

The draft C++ standard section 5.6 [expr.mul] says:

The binary / operator yields the quotient [...] If the second operand of / or % is zero the behavior is undefined [...]

again undefined behavior.

Both the draft C++ standard and draft C standard have a similar definition for undefined behavior both saying:

[...]for which this International Standard imposes no requirements

The phrase imposes no requirements seems too allow any behavior, including nasal demons. Both have a similar note saying something along the lines of:

Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

So although notes are not normative, it seems like if you are going to terminate during translation, you should at least issue a diagnostic. The term terminating is not defined, so it is hard to argue what this allows. I don't think I have seen a case where clang and gcc have an ICE without a diagnostic.

Does the code have to be executed?

If we read Can code that will never be executed invoke undefined behavior? we can see at least in case of C there is room for debate where the 1 / 0 has to be executed in order to invoke undefined behavior. What is worse in the C++ case the definition of behavior is not present so part of the analysis used for the C case can not be used for the C++ case.

It seems that if the compiler can prove the code will never be executed then we can reason that it would be as-if the program did not have undefined behavior but I don't think this is provable, just reasonable behavior.

From the C perspective WG14 defect report 109 further clarifies this. The following code example is given:

int foo()
{
  int i;
  i = (p1 > p2); /* Must this be "successfully translated"? */
  1/0; /* Must this be "successfully translated"? */
  return 0;
} 

and the response included:

Furthermore, if every possible execution of a given program would result in undefined behavior, the given program is not strictly conforming.
A conforming implementation must not fail to translate a strictly conforming program simply because some possible execution of that program would result in undefined behavior. Because foo might never be called, the example given must be successfully translated by a conforming implementation.

So in the case of C, unless it can be guaranteed that the code invoking undefined behavior will be executed then the compiler must successfully translate the program.

C++ constexpr case

If x was a constexpr variable:

constexpr int x = 1 / 0 ;

it would be ill-formed and gcc produces a warning and clang makes it error (see it live):

error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = 1/ 0 ;
             ^   ~~~~
note: division by zero
constexpr int x = 1/ 0 ;
                  ^
warning: division by zero is undefined [-Wdivision-by-zero]
constexpr int x = 1/ 0 ;
                  ^ ~

Helpfully noting that division by zero is undefined.

The draft C++ standard section 5.19 Constant expressions [expr.const] says:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions

and includes the following bullet:

an operation that would have undefined behavior [Note: including, for example, signed integer overflow (Clause 5), certain pointer arithmetic (5.7), division by zero (5.6), or certain shift operations (5.8) —end note ];

Is 1 / 0 a constant expression in C11

1 / 0 is not a constant expression in C11, we can see this from section 6.6 Constant expressions which says:

Each constant expression shall evaluate to a constant that is in the range of representable values for its type.

although, it does allow:

An implementation may accept other forms of constant expressions.

So 1 / 0 is not a constant expression in either C or C++ but that does not change the answer since it is not being used in a context that requires a constant expression. I suspect the OP meant that 1 / 0 is available for constant folding since both operands are literals, this would also explain the crash.




回答3:


From C standard draft (N1570):

6.5.5 Multiplicative operators

...

  1. The result of the / operator is the quotient from the division of the first operand by the second; the result of the % operator is the remainder. In both operations, if the value of the second operand is zero, the behavior is undefined.

And about undefined behaviour in chapter 3. Terms, definitions, and symbols:

3.4.3

  1. undefined behavior
    behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
  2. NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

So crashing the compiler is allowed.




回答4:


Others have already mentioned the relevant text from the standards, so, I'm not going to repeat that.

My C compiler's expression evaluating function takes an expression in Reverse Polish Notation (array of values (numbers and identifiers) and operators) and returns two things: a flag for whether or not the expression evaluates to a constant and the value if it's a constant (0 otherwise). If the result is a constant, the whole RPN reduces to just that constant. 1/0 is not a constant expression since it doesn't evaluate to a constant integer value. The RPN is not reduced for 1/0 and stays intact.

In C, static variables can be initialized with constant values only. So, the compiler errors out when it sees that an initializer for a static variable is not a constant. Variables of automatic storage can be initialized with non-constant expressions. In this case my compiler generates code to evaluate 1/0 (it still has the RPN for this expression!). If this code is reached at runtime, UB occurs as prescribed by the language standards. [On x86 this UB takes on the form of the division by zero CPU exception, while on MIPS this UB yields an incorrect quotient value (the CPU does not have a division by zero exception).]

My compiler properly supports short-circuiting in ||-expressions and &&-expressions. So, it evaluates 1 || 1/0 as 1 and 0 && 1/0 as 0, regardless of whether or not the right-hand operand of the logical operator is a constant. The expression evaluating function removes the right-hand operands of these operators (along with the operators) when they must not be evaluated and so 1 || 1/0 transforms into 1 != 0 (recall that the operands of && and || undergo comparison with 0), which yields 1 and 0 && 1/0 transforms into 0 != 0, which yields 0.

Another case to take care of is INT_MIN / -1 and INT_MIN % -1 (ditto for larger integer types). The quotient is not representable as a signed int (in the case of 2's complement signed integers, which is what we have in all modern CPUs) and so this is UB as well (you get the same division by zero exception on x86 at runtime). I handle this case similarly. This expression can't initialize a variable of static storage and it's thrown away if it's not evaluated in the logical &&/|| operator. It can initialize an automatic variable, possibly leading to UB at runtime.

I also issue a warning when such division is encountered.




回答5:


How the compiler should behave is unrelated to the value of the expression. The compiler should not crash. Period.

I imagine that a pedantic implementation, given an expression like this, would compile to code that will execute 1/0 at run time, but I don't think that would be seen as a good feature.

So the remaining space is that the compiler should decline to compile it, and treat it as some class of source code error.



来源:https://stackoverflow.com/questions/33916113/dividing-by-zero-in-a-constant-expression

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!