14

我正在修复一些代码的错误,编译器(合法地)警告说该函数dynscat()没有被声明——其他人对可接受的编码标准的想法——所以我追踪了函数的定义位置(很容易)以及哪个头文件声明了它(没有; 咕噜!)。但我期待找到结构定义的细节对于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()函数是静态的;extern 声明消除了此代码片段的编译器警告。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 也可以)?哪个板块?或者我只是碰到了一个奇怪的盒子,它可以在它移植到的所有机器上运行,但没有得到标准的正式批准?

4

6 回答 6

15

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

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

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

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

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

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

于 2012-08-30T15:50:56.417 回答
8

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

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

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

extern char a[];

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

于 2012-08-30T15:33:03.870 回答
5

你的线

extern struct t_dynstr qqparse_val;

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

标准说:

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

这意味着如果它具有链接,则类型可能不完整。所以&qqparse_val以后做是没有问题的。sizeof(qqparse_val)由于对象类型不完整,您将无法做到这一点。

于 2012-08-30T16:06:59.387 回答
4

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

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

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

于 2012-10-24T22:52:53.597 回答
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于尽管他们在地理上分开。

于 2012-08-30T22:43:04.123 回答
2

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

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

约束

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

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

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

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

6.5.3.4 sizeof 和 _Alignof 运算符

约束

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

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

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

于 2012-08-30T15:35:34.817 回答