我修错了一些代码,并且编译器警告(合法地)函数dynscat()
未被声明 - 别人对可接受的编码标准的想法 - 所以我追踪了函数定义的位置(很容易) )以及哪个标题声明了它(无; Grrr!)。但我期望找到extern
qqparse_val
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()
原始代码中的dynscat()
函数是静态的; extern声明会对此代码片段的编译器警告进行平息。 extern struct t_dynstr *p_parseval;
函数声明完全丢失了;再次,添加它会消除警告。
通过显示代码片段,很明显只使用了变量的地址,因此在一个层面上有意义的是,结构的细节未知是无关紧要的。如果是变量gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c
,你就不会看到这个问题;这将是100%预期。如果代码需要访问结构的内部,那么将需要结构定义。但我总是期望如果你声明变量是一个结构(而不是结构的指针),编译器会想知道结构的大小 - 但显然不是。
我曾试图激怒GCC抱怨,但事实并非如此,即使是GCC 4.7.1:
{{1}}
代码已经在AIX,HP-UX,Solaris,Linux上进行了十年的编译,因此它不被GCC特定的接受。
这是否允许C标准(主要是C99或C11,但C89也会这样做)?哪个部分?或者我只是碰到了一个奇怪的案例,该案例适用于移植到的所有机器,但未被标准正式批准?
答案 0 :(得分:15)
你所拥有的是一种不完整的类型(ISO / IEC 9899:1999和2011 - 所有这些参考文献都是相同的 - §6.2.5¶22):
未知内容的结构或联合类型(如§6.7.2.3中所述)是不完整的 类型。
不完整的类型仍然可以是左值:
§6.3.2.1¶1(左值,数组和函数指示符)
左值是具有对象类型或除void之外的不完整类型的表达式; ...
因此,它就像任何其他具有左值的一元&
一样。
答案 1 :(得分:8)
看起来像是一个不完整类型的对象地址。
使用指向不完整类型的指针是完全理智的,你每次使用指针到虚空时都会这样做(但是没有人告诉你: - )
另一种情况是你宣布像
这样的东西extern char a[];
您可以分配a
的元素,这对您来说并不奇怪吗?尽管如此,它仍然是一个不完整的类型,编译器会在您将此类标识符作为sizeof
的操作数后立即告诉您。
答案 2 :(得分:5)
你的行
extern struct t_dynstr qqparse_val;
是对象的外部声明,而不是定义。作为外部对象,它“具有联系”即外部联系。
标准说:
如果声明对象的标识符没有链接,则对象的类型应在其声明符的末尾完成,...
这意味着如果它具有链接,则类型可能不完整。所以之后做&qqparse_val
没有问题。 1}},因为对象类型不完整,所以你无法做到的。
答案 3 :(得分:4)
必须声明“引用”某事。 定义是“使用”某些东西的必要条件。 声明可以提供一些有限的定义,如“int a [];” 让我感到困惑的是:
int f(struct _s {int a; int b;} *sp)
{
sp->a = 1;
}
gcc警告'struct _s'在参数列表中声明。并声明“其范围仅限于此定义或声明,......”。但是,它不会在“sp-> a”上给出错误,该错误不在参数列表中。在编写'C'解析器时,我必须决定定义范围的结束位置。
答案 4 :(得分:3)
专注于第一行:
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 */
第二行看起来和原作一样,但现在它指的是由于第一行已经存在的类型。
第一行有效,因为这就是不透明结构的完成方式。
第二行有效,因为您不需要完整的类型来执行extern声明。
组合(第二行在没有第一行的情况下工作)起作用,因为将类型声明与变量声明组合在一起工作。所有这些都使用相同的原则:
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
,尽管它们存在地理分离。
答案 5 :(得分:2)
这是我对标准(C11)的想法。
第6.5.3.2节:地址和间接运算符
<强>约束强>
第1段:一元&amp;的操作数运算符应该是函数指示符,[]或一元*运算符的结果,或者是一个左值,它指定一个不是位字段的对象,并且不用寄存器存储类说明符声明。
第2段:一元*运算符的操作数应具有指针类型。
这里,我们没有对对象指定任何要求,除了它是一个对象(而不是一个位域或寄存器)。
另一方面,让我们来看看sizeof。
6.5.3.4 sizeof和_Alignof运算符
<强>约束强>
第1段: sizeof运算符不应该应用于具有函数类型或不完整类型的表达式,这种类型的带括号的名称,或者指定位的表达式 - 现场成员。 _Alignof运算符不应用于函数类型或不完整类型。
这里,标准明确要求对象不是不完整的类型。
因此,我认为这是一个未被明确拒绝的案例。