【问题标题】:Why is constant initialization need for static char* but not static char**为什么静态字符*需要常量初始化而不需要静态字符**
【发布时间】:2019-10-19 17:22:46
【问题描述】:

谁能解释一下为什么这段代码...

// main.c

#include <stddef.h>

static const int    g_a   = 1;
static const char*  g_b   = "hello";
static const char*  g_c[] = { "a",    "b",    NULL };

typedef struct Foo
{
  int           a;
  const char*   b;
  const char**  c;
} Foo;

static Foo f[] =
{
  { g_a,
    g_b,
    g_c }
};

int main( int argc, char* argv[] )
{
  return 0;
}

...产生此错误:

> gcc --version && gcc -g main.c 
gcc (GCC) 8.2.1 20181215 (Red Hat 8.2.1-6)
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

main.c:19:5: error: initializer element is not constant
     g_b,
     ^~~
main.c:19:5: note: (near initialization for 'f[0].b')

我了解到编译器要求一个常量来初始化f[0].b,所以下面的初始化是编译错误的解决方法:

static Foo f[] = { { g_a, "hello", g_c } };

但是为什么编译器在 f[0].c 的初始化时没有发出类似的“需要常量”错误?(或者,就此而言,f[0].a?)这是为什么只有f[0].b 的问题?

【问题讨论】:

  • 那将在 C++ 中编译。然而,在 C 中,您需要将这些编译时常量替换为预处理器时常量(即,使用 #define)。
  • @goodvibration - 谢谢,明白了,但我特别好奇为什么这只适用于char* 而不适用于char**
  • 如果你让 g_c 成为一个指针而不是一个数组(并将它指向一个有效的地方),它作为一个 const 初始化器也会变得无效。

标签: c compiler-errors initialization


【解决方案1】:

f[0].a 的类型为“非常量int”,并由g_a 的值进行初始化,该值的类型为“const int”,在运行时无法更改并且在编译时已知。所以这里没有错误。

f[0].b 的类型为“指向const char 的非常量指针”,并且应该由g_b 的值初始化,这也是一个“指向const char 的非常量指针”。即使它有一个初始化程序g_b 可以在运行时更改,并且 IIRC 初始化顺序是不确定的。所以它的值在编译时是未知的,因此是错误的。

f[0].c 的类型为“指向const char 的非常量指针的非常量指针”,并由g_c 初始化,这是一个类型为“指向const char 的非常量指针”的元素数组”。数组的符号可以用作编译时已知的常量指针。所以这里没有错误。

我认为这就是您所缺少的:如果您希望指针为const,请将修饰符放在指针处,在* 后面,而不是指向的值处,像这样:[const] char * const pointer.

【讨论】:

  • 唉,这看起来很有希望,因为该建议适用于我的一个交叉编译器,但它似乎是特定于编译器的,因为它在其他编译器上失败。在失败的编译器上,它报告相同的、未更改的编译错误。我想我得求助于#defines。
【解决方案2】:

正式定义:查看“地址常量”定义:“C 语言参考手册 6.19”:

地址常量是指向具有静态存储的对象的指针 持续时间或指向函数的指针。您可以使用 & 运算符或通过数组和函数名称的通常转换 在表达式中使用指针时。运算符 [], ., ->, & (address of) 和 * (pointer dereference) 以及指针转换都可以在表达式中使用,只要它们不 涉及访问任何对象的值。

实用答案:“C”要求将静态变量初始化为可以在编译时计算的常量表达式。常量计算可以使用静态/全局变量的地址——实际地址直到链接时才知道。

在这些情况下(例如,char *char_p = &char_var 或类似情况),编译器将生成汇编指令,以在链接时标记“重定位”的计算值。由静态/全局变量表示的重定位。在链接时,链接器会将静态/全局的实际地址添加到存储的值中。

考虑:char char_var = 'A' ; char *char_p = &amp;char_var;

        .file   "b.c"
        .text
        .globl  char_var
        .data
        .type   char_var, @object
        .size   char_var, 1

   # Char var initialized with a constant (65=A)
char_var:
        .byte   65
        .globl  char_p
        .section        .data.rel.local,"aw",@progbits
        .align 8
        .type   char_p, @object
        .size   char_p, 8

    # Initialize char_p to global symbol, actual address resolved at link time.
char_p:
        .quad   char_var

        .ident  "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
        .section        .note.GNU-stack,"",@progbits

链接器在链接时计算地址的能力是有限的。它仅限于静态地址 +/- 常量偏移:

  1. &static_var
  2. &static_var + 编译时间常数
  3. &static_var - 编译时间常数

但不是 '&static_var_1 - &static_var_2',它会给出提示限制的错误消息: b.c:3:9: error: initializer element is not computable at load time int v = &char_var - &char_v2 ;

【讨论】:

    猜你喜欢
    • 2012-12-25
    • 2018-06-01
    • 2017-11-10
    • 2015-08-29
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多