问题
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