我有以下C程序:
#include<stdio.h>
static void p(void);
static int c;
int main(int argc,char **argv) {
p();
printf("%d",c);
return 0;
}
void p(void) {
printf("From the Function P\n");
}
int c=232;
编译器gcc的输出错误是: 错误:'c'的非静态声明遵循静态声明
当我查看 C标准ISO / IEC 9899:TC2 :
6.2.2标识符的链接
1在不同范围或同一范围内多次声明的标识符可以是 通过称为linkage的过程来引用相同的对象或函数.21)有 三种联系:外部,内部和无。
2在构成整个程序的翻译单元和库集中 具有外部链接的特定标识符的声明表示相同的对象或 功能。在一个翻译单元内,每个内部标识符的声明 连接表示相同的对象或功能。标识符的每个声明都没有 link表示一个独特的实体。
3如果对象或函数的文件范围标识符的声明包含存储类 说明符静态,标识符具有内部链接.22)
4对于在范围中使用存储类说明符extern 声明的标识符 该标识符的先前声明是可见的,23)如果先前声明指定内部或 外部链接,后面声明中标识符的链接与 在先前声明中指定的联系。如果没有事先声明,或之前 声明指定无链接,然后标识符具有外部链接。
5如果声明函数的标识符没有存储类说明符,则其链接 确定与使用存储类说明符extern 声明的完全相同。如果 声明对象的标识符具有文件范围而没有存储类说明符, 它的联系是外部的。
6以下标识符没有链接:声明为除。之外的任何标识符 对象或功能;声明为函数参数的标识符;块范围 没有存储类说明符extern声明的对象的标识符。
7如果在翻译单元内,同一标识符同时显示内部和外部 联系,行为未定义。
21)不同的标识符之间没有联系。 22)函数声明只有在文件范围内时才能包含存储类说明符static;看到 6.7.1。
Q.1我无法理解规则4和5?有什么区别btw 链接的确定方式与使用存储类说明符extern声明的完全相同。和连接是外部的
Q2。为什么会出现这个错误,因为我可以从规则5推断出c具有静态decl。然后是extern decl。所以最后的decl.shud也是静态的。? 如果有人痛苦地从一开始就解释所有的规则,那么我就会认真对待。或者建议我一个清楚地解释所有规则的链接。*
注意:thnx对于任何建议正确编辑这个问题因为它可能重复我问这个问题becuz我无法理解thnx提前在本网站发布的类似问题的答案。
答案 0 :(得分:3)
首先,请记住,此处引用的标准部分涉及两个单独的事项:1。函数声明2.对象(例如,变量)声明。你的问题似乎表明两者之间存在一些混淆。
正如规则2所述,使用关键字static
声明的函数或对象仅具有内部链接。也就是说,它们在当前翻译单元之外是不可见的。当您第一次声明static int c
时,您说c
只有内部链接,并且在此翻译单元之外无法看到。您在任何函数之外声明此变量,即您给它的文件范围。
稍后,您重新声明int c = 232
。您也在文件范围内声明此变量 - 它不在任何函数内。规则5说:
If the declaration of an identifier for an object has file scope and no storage-class
specifier, its linkage is external.
由于您使用文件范围声明它并且没有任何存储类说明符,因此默认为外部链接。
int c
的这两个声明相互矛盾:第一个声明static
,指定内部链接;第二个,没有存储类说明符,默认为extern
。由于这个矛盾,你得到了从GCC看到的错误。
规则4说:
For an identifier declared with the storage-class specifier extern in a scope in which
a prior declaration of that identifier is visible, if the prior declaration specifies
internal or external linkage, the linkage of the identifier at the later declaration
is the same as the linkage specified at the prior declaration.
但这特别指的是重新声明为extern
的标识符。这意味着如果您按如下方式重新声明c:extern int c = 232
,则不会收到错误。 (但是,您可能会收到警告。)当我在第二个声明之前使用extern
关键字时,我按预期得到了以下输出:
From the Function P
232
为什么这样做? C标准的基本原理提供了解释:
The appearance of the keyword extern in a declaration, regardless of whether it is
used inside or outside of the scope of a function, indicates a pure reference (ref),
which does not define storage. Somewhere in all of the translation units, at least
one definition (def) of the object must exist. An external definition is indicated
by an object declaration in file scope containing no storage class indication.
// § 6.2.2, p. 33
当您第一次声明static int c
,然后第二次声明int c = 232
没有关键字extern
时,编译器会尝试为{{1}预留存储空间再次 - 它试图重新定义c
。但由于c
已经在范围内,因此第二个定义将失败,您将收到错误。
当您明确地将其重新声明为c
时,extern c = 232
关键字指定这不是需要预留存储空间的新对象;存储在别处。由于extern
已经在早期c
声明的范围内,因此第二个声明不会覆盖它;它将static
变量的值设置为232.
如果您在该文件中有一个需要设置该静态变量值的函数,这很有用:
static c
对于规则5,第一句话只是默认情况下函数为static int c;
void funA (void) {
extern int c = 232; // "extern" specifies that this is not new storage
}
,除非它们被指定为extern
;第二句话说默认情况下全局变量(在文件范围内声明)是static
。如果我在另一个函数中声明一个函数,它仍然具有extern
范围;但是,在函数内声明的变量具有自动存储,并且不在该函数之外的范围内。澄清:
extern
希望这有帮助!
答案 1 :(得分:1)
static int c;
表示c由于static
而具有文件范围和内部链接,但稍后将c
再次定义为int c
,而没有任何存储类说明符尝试为其提供外部链接默认情况下(规则5)),因为它是在同一文件范围中先前声明的。
答案 2 :(得分:1)
可以在不同的翻译单元中看到具有外部链接的标识符,这些翻译单元链接在一起成为可执行文件。没有外部链接的标识符只能在定义它的翻译单元中(可能仅在特定范围内)中看到。从C.99§5.1.1.1开始:
程序的单独翻译单元通过(例如)调用其标识符具有外部链接的函数,对标识符具有外部链接的对象的操作或数据文件的操作来进行通信。翻译单元可以单独翻译,然后链接以生成可执行程序。
有关C.99§6.2.2¶4的说明,请考虑以下事项:
static int c;
void foo () { assert(c == 1); }
int main () {
extern int c;
c = 1;
foo();
return 0;
}
断言条件将评估为true。这是因为C.99§6.2.2¶4表示c
中的extern int c
与先前声明的c
中的static int c
相同,如果范围中有一个,则在此case是c
。如果没有extern int c
的声明,则main()
中的static int c;
/*...*/
int c = 5;
会有外部链接。
在C.99§6.2.2¶5中,第一句仅适用于函数名。它说如果函数声明不包含任何存储类,则函数名称具有外部链接。
C.99§6.2.2¶5中的第二句指出,如果一个对象在没有任何存储类的文件范围内声明,那么该对象的名称将具有外部链接。因此,在您的代码段中:
c
存在冲突,因为首先声明{{1}}具有内部链接,然后再进行外部链接。
答案 3 :(得分:0)
关于Q1:
在引用的第5段中,第一句将仅应用于功能的标识符。在int c=232;
中,c
是对象的标识符,而不是函数。因此,第一句不适用。
第二句适用于对象的标识符。它说,对于文件范围的声明而没有存储类说明符,链接是外部的。
因此,在您问题的int c=232;
中,链接是外部的。
关于Q2:
正如我们现在所看到的,第一个声明static int c;
给出了c
内部链接,第二个声明int c=232;
给出了c
外部链接。这些冲突,所以这是一个错误。