C标准的哪一部分允许编译此代码?

C标准的哪一部分允许编译此代码?,c,C,我正在修复一些代码的bug,编译器(合法地)警告说没有声明函数dynscat(),这是其他人关于可接受的编码标准的想法,因此我跟踪了函数的定义位置(足够简单)和声明它的头(none;Grrr!)。但是我希望发现结构定义的细节对于qqparse\u val的extern声明是必要的: extern struct t_dynstr qqparse_val; extern void dynscat(struct t_dynstr *s, char *p); extern void qqcat(cha

我正在修复一些代码的bug,编译器(合法地)警告说没有声明函数
dynscat()
,这是其他人关于可接受的编码标准的想法,因此我跟踪了函数的定义位置(足够简单)和声明它的头(none;Grrr!)。但是我希望发现结构定义的细节对于
qqparse\u val
extern
声明是必要的:

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声明消除了编译器对这段代码的警告。
dynscat()
函数声明完全丢失;再次,添加它可以平息警告

通过显示代码片段,很明显只使用了变量的地址,因此在某种程度上,结构的细节未知并不重要。是变量
extern struct_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也可以)?哪个部门?或者我只是偶然发现了一个奇怪的例子,它可以在它移植到的所有机器上工作,但没有得到标准的正式认可?

看起来像是一个获取不完整类型对象地址的例子

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

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

extern char a[];

您可以为
a
的元素赋值也就不足为奇了,对吧?尽管如此,它仍然是一个不完整的类型,一旦您将这样一个标识符设置为
sizeof

的操作数,编译器就会告诉您以下是我对标准(C11)的看法

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

约束

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

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

在这里,除了对象(而不是位字段或寄存器)之外,我们不对对象指定任何要求

另一方面,让我们看看sizeof

6.5.3.4运营商的规模和规模

约束

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

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


因此,我认为这是一种不被明确拒绝的情况。

您拥有的是一种不完整的类型(ISO/IEC 9899:1999和2011-所有这些参考在这两种情况下都是相同的-§6.2.5¨22):

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

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

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

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

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

您的行

extern struct t_dynstr qqparse_val;
是对象的外部声明,而不是定义。作为一个外部对象,它“有联系”,即外部联系

标准上说:

如果一个对象的标识符是在没有链接的情况下声明的,那么该对象的类型应该在其声明符的末尾完成


这意味着,如果它有链接,则类型可能不完整。因此,事后执行
&qparse\u val
没有问题。由于对象类型不完整,您无法执行的操作将是sizeof(qqparse_val)

关注第一行:

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\u val
,尽管其地理位置不同。

需要声明才能“提及”某事物。 “使用”某物需要一个定义。 声明可能会提供一些有限的定义,如“int A[];” 什么使我感到难受
int f(struct _s {int a; int b;} *sp)
{
    sp->a = 1;
}