Why do C11 global and local static asserts behave differently?

三世轮回 提交于 2020-01-24 00:47:06

问题


Consider the following code:

const int g_foo = 1;

// (1):
_Static_assert(g_foo > 0, "g_foo > 0");  // error: expression in static assertion is not constant.
_Static_assert(g_foo > 2, "g_foo > 2");  // error: expression in static assertion is not constant.

void Foo(void) {
  const int foo = 1;

  // (2):
  _Static_assert(foo > 0, "foo > 0");  // No issue.
  _Static_assert(foo > 2, "foo > 2");  // error: static assertion failed: "foo > 2"

  // (3):
  _Static_assert(g_foo > 0, "g_foo > 0");  // No issue.
  _Static_assert(g_foo > 2, "g_foo > 2");  // error: static assertion failed: "g_foo > 2"
}

Compiling using gcc -std=c11 -O3 test.c with GCC version 7.4.0 produces the indicated error messages. A Compiler Explorer example using GCC 9.2 can also be found here.

Beginning at the static asserts labeled with (2), we are using a const-qualified local variable. At this optimization level, const int foo = 1; is optimized out and, consequently, the initializer is not evaluated. From what I understand, this classifies it as a constant-expression according to 6.6.3 of ISO/IEC 9899:2011* and allows it to be evaluated by _Static_assert**. So far so good.

The static asserts labeled with (1) attempt to evaluate expressions containing the global variable g_foo, but the fact that globals are allocated space in memory means that they actually need to be initialized. This evaluation automatically disqualifies them from being a constant-expression and GCC complains accordingly.

Then we get to (3). Suddenly, the static asserts that failed in the global scope start working. What's going on?

* I'm unfortunately using the 2010-12-02 committee draft instead of the final published spec.

** As an interesting aside, the use of a nonzero optimization level is important in this question. With -O0, the variable foo actually gets instantiated with a literal 1. However, this is no longer a constant-expression and the static asserts using foo start failing with "expression in static assertion is not constant."


Edits (2019-11-04):

  • Removed *(int *)&g_foo = -1; from the code block - it's undefined behavior and distracts from the main question.
  • Removed a superfluous quip about casting const qualified variables in C.
  • Added a link to Compiler Explorer to help with reproducing the issue.

回答1:


*(int *)&g_foo = -1; is explicitly undefined behavior as per C17 6.7.3/6:

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

(the C11 draft n1548 says the very same)

Therefore you cannot sensibly reason about the behavior of this code.

Personally I get all 6 asserts to trigger. Tried both with an old gcc 4.8 as well as 9.2/trunk, with or without optimizer enabled. Works fine for me, but apparently you get different results on your local compiler. Such is the nature of undefined behavior.




回答2:


It does not work because

The C standard requires the first argument of _Static_assert to be a constant expression of integer type.

static_assert-declaration:
         _Static_assert ( constant-expression , string-literal ) ;

https://port70.net/~nsz/c/c11/n1570.html#6.7.10

The standard says this about integer constant expressions:

An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, _Alignof expressions, and floating constants that are the immediate operands of casts.

https://port70.net/~nsz/c/c11/n1570.html#6.6p6

Enumeration constants, character constants, sizeof expressions, _Alignof expressions, and floating constants are obviously not relevant here. That leaves us with only integer constants. And the standard says this about integer constants:

integer-constant:
      decimal-constant integer-suffixopt
      octal-constant integer-suffixopt
      hexadecimal-constant integer-suffixopt
decimal-constant:
      nonzero-digit
      decimal-constant digit
octal-constant:
      0
      octal-constant octal-digit
hexadecimal-constant:
      hexadecimal-prefix hexadecimal-digit
      hexadecimal-constant hexadecimal-digit
hexadecimal-prefix: one of
      0x   0X
nonzero-digit: one of
      1   2   3   4   5   6   7   8   9
octal-digit: one of
      0   1   2   3   4   5   6   7
hexadecimal-digit: one of
      0   1   2   3   4   5   6   7   8   9
      a   b   c   d   e   f
      A   B   C   D   E   F
integer-suffix:
      unsigned-suffix long-suffixopt
      unsigned-suffix long-long-suffix
      long-suffix unsigned-suffixopt
      long-long-suffix unsigned-suffixopt
unsigned-suffix: one of
      u   U
long-suffix: one of
      l   L
long-long-suffix: one of
      ll   LL

https://port70.net/~nsz/c/c11/n1570.html#6.4.4.1p1

Note that the definition of integer constant does NOT include variables declared with const qualifier. Declaring a variable as const only makes it impossible (or should I say illegal) to change it. It does not make it a constant expression. "Why not?" you may rightfully ask. Well I don't know, but I do suspect that there were some obscure historical reason when they standardized C that is not relevant today but still lives on. Remember that when designing API:s!

Therefore, g_foo does not count as a constant expression and the standard does not require any of your asserts to work.

But do note what we can read here:

An implementation may accept other forms of constant expressions.

https://port70.net/~nsz/c/c11/n1570.html#6.6p10

It's possible that it works for 2 and 3 because of compiler extensions. I guess you will have to read the compiler documentation for details or hope that someone else comes up with an answer.

What can you do?

There is a workaround that you can use. Notice above that enumeration constants counts as integer constants and thus also as integer constant expressions. Change

const int g_foo = 1;

to

enum { g_foo = 1; }


来源:https://stackoverflow.com/questions/58689753/why-do-c11-global-and-local-static-asserts-behave-differently

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