【问题标题】:Why do C11 global and local static asserts behave differently?为什么 C11 全局和局部静态断言的行为不同?
【发布时间】:2019-11-04 08:41:34
【问题描述】:

考虑以下代码:

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"
}

使用gcc -std=c11 -O3 test.c 和 GCC 版本 7.4.0 进行编译会产生指示的错误消息。使用 GCC 9.2 的编译器资源管理器示例也可以在 here 找到。

从标有 (2) 的静态断言开始,我们使用 const 限定的局部变量。在这个优化级别,const int foo = 1; 被优化,因此初始化器不被评估。据我了解,这根据 ISO/IEC 9899:2011* 的 6.6.3 将其归类为常量表达式,并允许由_Static_assert** 对其进行评估。到目前为止一切顺利。

标有 (1) 的静态断言尝试评估包含全局变量 g_foo 的表达式,但全局变量在内存中分配空间这一事实意味着它们实际上需要被初始化。这种评估会自动取消它们成为常量表达式的资格,并且 GCC 会相应地抱怨。

然后我们到达 (3)。突然,在全局范围内失败的静态断言开始工作。怎么回事?

* 不幸的是,我使用的是2010-12-02 committee draft,而不是最终发布的规范。

** 有趣的是,在这个问题中使用非零优化级别很重要。使用-O0,变量foo 实际上是用文字1 实例化的。但是,这不再是一个常量表达式,并且使用 foo 的静态断言开始失败,并出现“静态断言中的表达式不是常量”。


编辑(2019-11-04):

  • 从代码块中删除了 *(int *)&g_foo = -1; - 这是未定义的行为,会分散主要问题的注意力。
  • 删除了关于在 C 中强制转换 const 限定变量的多余俏皮话。
  • 添加了指向 Compiler Explorer 的链接以帮助重现该问题。

【问题讨论】:

  • “然而,在 C 语言中,const 并不是那么强的语句” 它在定义中。您的分配会导致未定义的行为。
  • 嗯,抛弃 const 并修改对象是明确未定义的行为 (port70.net/~nsz/c/c11/n1570.html#6.7.3p6)。对这样一个程序的推理主要是没有实际意义的。我建议你删除那一点,因为它并不是问题的真正主题,而且 UB 会减损一个有趣的案例研究。
  • “ *(int *)&g_foo = -1;”你为什么这样做?不要试图比编译器更聪明。
  • this classifies it as a constant-expression according to 6.6p3 - 不。这都属于6.6.p10 - An implementation may accept other forms of constant expressions
  • @user694733,等人:够公平的。 const 分配是最后一分钟的补充,以强调静态断言的怪异。它是未定义的行为这一事实显然有损于重点,因此我已将其从问题中删除。

标签: c c11 static-assert


【解决方案1】:

*(int *)&g_foo = -1; 是根据 C17 6.7.3/6 明确未定义的行为:

如果尝试通过使用来修改使用 const 限定类型定义的对象 具有非 const 限定类型的左值,行为未定义。

(C11 草案 n1548 也是这么说的)

因此,您无法合理地推断此代码的行为。

就我个人而言,我会触发所有 6 个断言。尝试使用旧的 gcc 4.8 和 9.2/trunk,启用或不启用优化器。对我来说效果很好,但显然你在本地编译器上得到了不同的结果。这就是未定义行为的本质。

【讨论】:

    【解决方案2】:

    TL;DR

    原因是不能保证在编译时设置const 对象。例如,这是完全有效的:

    void foo(int x) {
        const int y = x;
        int z;
        scanf("%d", &z);
        const int t = z+x;
    

    常量变量和正则的区别在于正则既可以初始化也可以赋值,而常量需要初始化,基本意思是“声明时赋值”。

    在这个特定的示例中,它没有理由不工作,因为您使用的是静态(全局变量是静态的)变量,而静态变量需要一个常量表达式,就像 _Static_assert。这是无效的,例如:

    void foo(int x) {
        static const int y = x; // Will not compile
    

    原因是静态变量是在编译时初始化的。如果您不明确初始化它们,它们的值为零。这与具有自动(非静态)存储的变量不同。除非初始化,否则它们包含垃圾。

    所以从理论上讲,他们(编写标准的人)本可以对静态常量进行例外处理,但他们没有。因此,const 变量不算作常量表达式。就那么简单。我想原因是他们根本不认为这是值得的。

    长技术版

    C 标准要求_Static_assert 的第一个参数是整数类型的常量表达式

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

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

    标准对整数常量表达式是这样说的:

    整数常量表达式应具有整数类型,并且只能具有整数常量、枚举常量、字符常量、结果为整数常量的表达式 sizeof、_Alignof 表达式和作为强制转换的直接操作数的浮点常量的操作数。

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

    枚举常量、字符常量、sizeof 表达式、_Alignof 表达式和浮点常量显然与这里无关。这让我们只剩下整数常量。标准对整数常量这么说:

    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

    请注意,整数常量的定义不包括使用const 限定符声明的变量。将变量声明为const 只会使更改它变得不可能(或者我应该说是非法的)。它不会使它成为一个常量表达式。 “为什么不?”你可以正确地问。好吧,我不知道,但我确实怀疑当他们标准化 C 时存在一些模糊的历史原因,这在今天并不相关,但仍然存在。请记住,在设计 API 时:s!

    因此,g_foo 不算作常量表达式并且该标准不要求您的任何断言起作用。

    但请注意我们可以在此处阅读的内容:

    一个实现可以接受其他形式的常量表达式。

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

    由于编译器扩展,它可能适用于 2 和 3。我想您将不得不阅读编译器文档以了解详细信息,或者希望其他人提供答案。

    你能做什么?

    您可以使用一种解决方法。请注意,枚举常量算作整数常量,因此也算作整数常量表达式。改变

    const int g_foo = 1;
    

    enum { g_foo = 1; }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-04
      • 1970-01-01
      • 1970-01-01
      • 2016-08-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多