【问题标题】:Which part of the C standard allows this code to compile?C 标准的哪一部分允许编译此代码?
【发布时间】:2012-08-25 08:54:57
【问题描述】:

我正在修复一些代码的错误,编译器(合法地)警告说函数 dynscat() 没有被声明——其他人对可接受的编码标准的想法——所以我追踪了函数的定义位置(很容易)和哪个标头声明了它(无;Grrr!)。但我期待找到结构定义的细节对于qqparse_valextern 声明是必要的:

extern struct t_dynstr qqparse_val;

extern void dynscat(struct t_dynstr *s, char *p);
extern void qqcat(char *s);

void qqcat(char *s)
{
    dynscat(&qqparse_val, s);
    if (*s == ',')
        dynscat(&qqparse_val, "$");
}

原代码中的qqcat()函数是静态的; extern 声明消除了代码的这个 sn-p 的编译器警告。 dynscat() 函数声明完全丢失;再次,添加它会消除警告。

通过显示的代码片段,很明显只使用了变量的地址,因此在某种程度上,结构的细节未知并不重要。如果变量extern struct t_dynstr *p_parseval;,你不会看到这个问题;这将是 100% 的预期。如果代码需要访问结构的内部,则需要结构定义。但我一直认为,如果你声明变量是一个结构(而不​​是指向结构的指针),编译器会想知道结构的大小——但显然不是。

我尝试过让 GCC 抱怨,但它没有,即使是 GCC 4.7.1:

gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c

该代码已经在 AIX、HP-UX、Solaris、Linux 上编译了十年,因此它不是特定于 GCC 的。

问题

C 标准是否允许这样做(主要是 C99 或 C11,但 C89 也可以)?哪个板块?还是我只是碰到了一个奇怪的案例,它可以在它移植到的所有机器上运行,但没有得到标准的正式认可?

【问题讨论】:

  • 如果 dynstr.h 被定义在某个地方应该没问题,不是吗?
  • @Shark:代码正是显示的内容;不需要标题。
  • 究竟有什么令人惊讶的?你可以获取类型不完整的对象的地址吗?
  • 这个问题包含大量不必要的信息。我建议将其删减为基本要素,例如:“为什么 C 标准允许 extern struct foo;… /* Now inside a function. */ &foo,以及哪些部分提供了相关信息?”

标签: c


【解决方案1】:

您拥有的是不完整的类型(ISO/IEC 9899:1999 和 2011 — 所有这些参考在两者中都是相同的 — §6.2.5 ¶22):

未知内容的结构或联合类型(如 §6.7.2.3 中所述)是不完整的 输入。

不完整的类型仍然可以是左值:

§6.3.2.1 ¶1(左值、数组和函数指示符)

左值是具有对象类型或除 void 以外的不完整类型的表达式; ...

因此,它就像任何其他具有左值的一元 &

【讨论】:

  • 我很惊讶这不是公认的答案,因为问题包括“C 标准的哪个部分......”和“哪个部分?”。接受的答案没有提及哪个部分或哪个部分。
【解决方案2】:

看起来像是获取类型不完整的对象的地址。

使用指向不完整类型的指针是完全明智的,每次使用指向 void 的指针时都会这样做(但没有人告诉过你 :-)

另一种情况是,如果您声明类似

extern char a[];

您可以分配给a 的元素并不奇怪,对吧?尽管如此,它还是一个不完整的类型,一旦你将这样的标识符作为sizeof 的操作数,编译器就会告诉你。

【讨论】:

  • 为什么是恐怖?您可以使用不完整的类型结构来发挥自己的优势,例如在隐藏内部的地方创建“不透明类型”,例如struct FILE 中有什么内容。所有访问都通过函数获取指向不完整类型的指针,这是标准 IO 库所做的。
  • 所以本质上,编译器没有抱怨 Jonathan 的第一行代码这一事实意味着执行时发生错误的另一种可能性。
  • @Frank:实际上,它减少了出错的可能性,因为编译器不允许代码对struct t_dynstr 执行任何操作,struct t_dynstr 的行为会受到该结构定义的任何影响.如果 OP 的代码以依赖于结构内容的方式使用该类型,并且后来修改了该结构而不重新编译 OP 的代码,则可能导致未定义的行为。相比之下,将struct t_dynstr 保留为不透明类型可以让编译器确保不存在此类用法。
【解决方案3】:

你的线路

extern struct t_dynstr qqparse_val;

是对象的外部声明,而不是定义。作为一个外部对象,它“具有链接”即外部链接。

标准说:

如果对象的标识符被声明为没有链接,则对象的类型应在其声明符的末尾完成,...

这意味着如果它具有链接,则类型可能不完整。所以之后做&qqparse_val是没有问题的。你不能做的是sizeof(qqparse_val),因为对象类型不完整。

【讨论】:

    【解决方案4】:

    声明对于“引用”某事是必要的。 定义是“使用”某物所必需的。 声明可能会提供一些有限的定义,如“int a[];” 让我难过的是:

    int f(struct _s {int a; int b;} *sp)
    {
        sp->a = 1;
    }
    

    gcc 警告 'struct _s' 在参数列表中声明。并声明“它的范围只是这个定义或声明,......”。但是,它不会在参数列表中没有的“sp->a”上给出错误。在编写“C”解析器时,我必须决定定义范围的结束位置。

    【讨论】:

    • 嗨,丹,欢迎来到 Stack Overflow。它是有原因的,它是一个纯函数声明结构的范围是原型参数列表。如果以这种方式定义函数,则函数原型标题中定义的所有变量(以及扩展类型)都在函数体的范围内。警告的原因与该类型是否可以被任何其他函数用来调用该类型有关。这涉及到对标准的一些棘手阅读(C 2011 中的§6.2.7 兼容类型和复合类型,以及对 §6.7.2、§6.7.3 和 §6.7.6 的 x-refs )。
    【解决方案5】:

    关注第一行:

    extern struct t_dynstr qqparse_val;
    

    它可以分为创建类型和变量的单独步骤,从而产生这对等效的行:

    struct t_dynstr; /* declaration of an incomplete (opaque) struct type */
    extern struct t_dynstr qqparse_val; /* declaration of an object of that type */
    

    第二行看起来和原来的一样,但现在它指的是由于第一行而已经存在的类型。

    第一行有效,因为这就是不透明结构的完成方式。

    第二行有效,因为您不需要完整的类型来进行外部声明。

    这种组合(第二行没有第一行)是有效的,因为将类型声明与变量声明结合起来通常是有效的。所有这些都使用相同的原理:

    struct { int x,y; } loc; /* define a nameless type and a variable of that type */
    struct point { int x,y; } location; /* same but the type has a name */
    union u { int i; float f; } u1, u2; /* one type named "union u", two variables */
    

    extern 后面紧跟一个类型声明,这看起来有点滑稽,就像您试图将类型本身设为“extern”一样,这是无稽之谈。但这不是它的意思。 extern 适用于 qqparse_val,尽管它们在地理上是分开的。

    【讨论】:

      【解决方案6】:

      这是我对标准 (C11) 的看法。

      第 6.5.3.2 节:地址和间接运算符

      约束

      第 1 段:一元 & 运算符的操作数应为函数指示符、[] 或一元 * 运算符的结果,或指定非位对象的左值-field 并且未使用寄存器存储类说明符声明。

      第2段:一元*运算符的操作数应具有指针类型。

      在这里,我们没有对对象指定任何要求,除了它是一个对象(而不是位域或寄存器)。

      另一方面,让我们看看 sizeof。

      6.5.3.4 sizeof 和 _Alignof 运算符

      约束

      第 1 段: sizeof 运算符不得应用于具有函数类型或不完整类型的表达式、此类类型的括号名称或指定位的表达式-场成员。 _Alignof 运算符不得应用于函数类型或不完整类型。

      这里,标准明确要求对象不能是不完整的类型。

      因此,我认为这是允许未明确拒绝的情况。

      【讨论】:

      • 令我惊讶的是,第一行代码是可以接受的;既然可以接受,后面的代码显然是合法的。因此,对于“其余代码”,您的观察是有效的(谢谢),但难题是第一行(我可能只是迟钝;这不是第一次),但是您对标准的引用不要真正解决第一行。
      猜你喜欢
      • 1970-01-01
      • 2011-09-17
      • 1970-01-01
      • 1970-01-01
      • 2014-12-16
      • 2016-03-16
      • 1970-01-01
      • 1970-01-01
      • 2011-03-04
      相关资源
      最近更新 更多