【问题标题】:error: initializer element is not a compile-time constant from initializing a structure of known size this is C错误:初始化器元素不是初始化已知大小的结构的编译时常量,这是 C
【发布时间】:2017-08-07 15:24:03
【问题描述】:

这是直接来自 Schreiner 和 Friedman 编译器书籍 page33 的 C(lex 驱动程序代码),刚刚输入它并抛出错误:

“错误:初始化元素不是编译时常量。

根据 K&R 第 124 页结构数组初始化,检查了具有不同名称的相同代码位。那里清楚地说明了编译时已知的这个 IS 的大小。这是 gcc 的问题吗? 我将命令行编译为: lex samplec.l;然后, cc -DDEBUG lex.yy.c -ll -o lexc ; 此时它会引发错误。 OBB

%{
/*
**
**  samplec.l -- lexical analysia
**
*/

#ifdef      DEBUG

#  include <assert.h>

  main ()
    {
     char * p;

     assert(sizeof(int) >= sizeof(char *));

     while (p = (char *) yylex())
       printf("%-10.10s is \"%s\"\n",p, yytext);
  }

  s_lookup() {}

  int yyerrs = 0;

#   define token(x) (int) "x"

#else ! DEBUG

#   define "y.tab.h"
#   define token(x) x

#endif DEBUG

#define END(v) (v-1 + sizeof v / sizeof v[0])
static int screen();
%}

letter                      [a-zA-Z]
digit                       [0-9]
letter_or_digit             [a-zA-Z0-9]
white_space         [ \t\n]
blank                       [ \t]
other                       .

%%

^"#"{blank}*{digit}+({blank}+.*)?\n yymark();

">="                    return token(GE);
"<="                        return token(LE);
"=="                        return token(EQ);
"!="                        return token(NE);
"+="                        return token(PE);
"-="                        return token(ME);
"*="                        return token(TE);
"/="                        return token(DE);
"%="                        return token(RE);
"++"                        return token(PP);
"--"                        return token(MM);

{letter}{letter_or_digit} return screen();

{digit}+            { s_lookup(token(Constant));
                          return token(Constant);
                    }

{white_space}*              ;

{other}                     return token(yytext[0]);

%%

/*
**
**  reserve word screening
**
*/

static struct rwtable {
    char *rw_name;
    int rw_yylex;
   } rwtable[] ={
   "break", token(BREAK),
   "continue", token(CONTINUE),
   "else", token(ELSE),
   "if", token(IF),
   "int", token(INT),
   "return", token(RETURN),
   "while", token(WHILE)
   };

static int screen()
   {
    struct rwtable * low = rwtable,
            * high = END(rwtable),
            * mid;

    int c;

    while (low <= high)
      {
       mid = low + (high-low)/2;
       if ((c = strcmp(mid->rw_name, yytext)) == 0)
            return mid->rw_yylex;
       else if (c < 0)
            low = mid+1;
       else
            high = mid-1;
     }
     s_lookup(token(Identifier));
     return token(Identifier);
}

【问题讨论】:

  • 请发minimal reproducible example - 这里缺少完整的内容。
  • token 是函数吗?
  • 您缺少一个初始化层次结构,即一些 {} 对。请仔细检查您是否真的逐字逐句复制了这本书。
  • token的定义是什么?
  • @AnttiHaapala 请求的 MCVE 当然不可编译。在这种情况下,“可验证”的意思是,它得到的只是你所询问的错误。

标签: c


【解决方案1】:

D 所著的Unix 编译器构造简介一书。 Friedman Schreiner, H. George Friedman Jr. 已经过时了(1985 年):现代编译器不再使用它使用的 C 方言。

token 的定义使用了一种过时的字符串化形式:

#define token(x) (int) "x"

这在很久以前就被弃用了,在 ANSI-C 之前。

您可以尝试将此定义更改为

#define token(x) #x

但代码依赖于其他一些假设,这些假设在您当前的系统上可能会失败:

  • sizeof(int) &gt;= sizeof(char*) -> 在大多数当前的 64 位系统上不正确

我建议你寻找关于编译器构造的更新教程:你可以在网上找到很多好的参考资料,还有很多开源软件。

【讨论】:

  • 这不是一种过时的字符串化形式。 Lex 要求标记为ints,因此代码将字符串地址转换为ints。这样做是为了调试代码可以通过将int 转换回指针来打印令牌名称。
  • 真正的问题是找到一个编译器来编译K&R C。目的是尝试产生一个。我一直担心我的原始问题被 Lex 组件跟踪,因此我的原始代码 sn-p 简短而甜蜜。感谢大家的意见。 OBB
  • @pat:恐怕您误读了宏:#define token(x) (int) "x" 是一种过时的字符串化形式。 token(GE) 过去使用原始 K&R C 编译器扩展为 (int) "GE",实际上调试代码将通过将其转换回 (char *) 将其打印为字符串。今天,所有符合标准的 C 编译器都会将其扩展为 (int) "x",这违背了最初的目的。
  • @pat 当然它是过时的形式;如果不是,则允许实现合并字符串"x",并且所有标记都将具有 same 值。
  • 哦,是的,我明白你的意思了。我的错。
【解决方案2】:

问题是指针被强制转换为int。您的指针很可能是 64 位,但您的 ints 只有 32 位。链接器无法表示截断的指针,因此这些值不是编译时常量。此外,如果编译器能够表示这些截断的指针,当您尝试将 int 转换回指针时,您会感到非常惊讶,因为它可能无法正确恢复丢失的 32 位。

某些平台提供intptr_tuintptr_t 类型,它们是有符号和无符号整数类型,保证足够宽以允许在指针类型之间进行转换而不会丢失。当然,这在这里没有帮助,因为 lex 想要一个普通的旧 int

【讨论】:

  • Lex 不限于int,但这是一个完全的红鲱鱼,因为该结构的成员的类型不是由 lex 确定的。 (转换可以在运行时完成。)转换为更广泛的整数类型仍然不是常量表达式。
  • 该结构的成员类型必须与yylex 的返回类型匹配,因为该表的目的是将看起来像标识符的保留字转换为标记号。我从来没有说过转换为更宽的类型是一个常量表达式,我只是介绍了uintptr_t 来说明int 和指针不保证宽度相同的事实。
  • 您说“......所以这些值不是编译时常量”,这意味着如果它们没有被截断,它们就会被截断。这是误导恕我直言。将const char* 存储在表中并在运行时转换为uintptr_t 是有效的;当然,如果YYSTYPEuintptr_t,那会更有意义,但我要说的是结构成员的类型,它不必与函数返回的类型相同。 (更好的是使用%union 来完全避免演员表。)
  • 好吧,如果我们将关联数据存储在yylval 中会更好,但这里不是这样。该代码试图将字符串地址用作标记数字,而不是标记
  • 进一步调查是,char * 大于 int。所以我把它变成了一个长整数,并用长整数替换了整数。原来的错误现在消失了,谢谢大家的帮助和建议。但其他困难仍然存在,我将首先尝试解决自己。 OBB。
【解决方案3】:

发生错误是因为静态存储持续时间的对象的初始化器必须是常量;并根据C11 6.6p6

整数常量表达式应为整数类型,并且具有整数常量、枚举常量、字符常量、结果为整数常量的 sizeof 表达式、_Alignof 的操作数表达式和作为强制转换的直接操作数的浮动常量。 整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,除非是作为 sizeof 或 _Alignof 运算符的操作数的一部分。

"x" 的类型为char *,指针不是算术类型,因此转换为整数的指针不能是整数常量表达式。

【讨论】:

  • 又该如何解决呢?您提到了 C11,但访问旧方言很容易:例如,在 macOS 上,您可以传递 -std=c99-std=c89。这对解决问题有帮助吗?
  • 当然,它绝对不会解决这个问题。这个程序是一个丑陋的 hack,必须修复代码,而不是编译器开关。
猜你喜欢
  • 1970-01-01
  • 2011-09-02
  • 1970-01-01
  • 2014-05-25
  • 2016-06-03
  • 2017-12-03
  • 2014-02-18
  • 2012-03-22
相关资源
最近更新 更多