C 是否需要对不完整的类型使用extern?
您是否绝对需要将extern与不完整的类型一起使用,例如C 是否需要对不完整的类型使用extern?,c,C,您是否绝对需要将extern与不完整的类型一起使用,例如inta[]是否允许它在链接文件中使用数组和定义?我的逻辑是,它不保留内存,所以它不是一个定义,而是一个声明(就像一个函数原型,编译器也不需要extern来隐式地将它留给链接器)。我会自己测试,但目前无法 由于您询问您是否绝对需要使用extern声明一个名义上不完整类型的标识符,因此从技术上来说,答案是“否”,原因有两个: C标准是自愿的。没有什么要求你服从它 如果您使用的是C标准,并且您声明inta[]外部(任何函数外部)和inta[5
inta[]
是否允许它在链接文件中使用数组和定义?我的逻辑是,它不保留内存,所以它不是一个定义,而是一个声明(就像一个函数原型,编译器也不需要extern来隐式地将它留给链接器)。我会自己测试,但目前无法 由于您询问您是否绝对需要使用extern
声明一个名义上不完整类型的标识符,因此从技术上来说,答案是“否”,原因有两个:
- C标准是自愿的。没有什么要求你服从它
- 如果您使用的是C标准,并且您声明
inta[]一个翻译单元中的代码>外部(任何函数外部)和
在另一个翻译单元内,并且您在程序中使用inta[5]={3,-7,24,5,7}
,该行为不由C标准定义。也就是说,C标准“允许”您这样做,但不定义结果a
extern
来获得定义的结果,并且可能是您想要的结果,那么答案是“是”。如果您声明int a[]代码>在翻译单位中,根据C 2018 6.9.2 2,这是一个暂定定义:
对于具有文件作用域但没有初始值设定项、没有存储类说明符或具有存储类说明符static
的对象,其标识符的声明构成了一个暂定定义。如果翻译单元包含一个或多个标识符的暂定定义,而翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型为翻译单元的末尾,具有等于0的初始值设定项
这意味着,如果您声明inta[]代码>外部,并且不要在同一翻译单元(包含所有包含文件的源文件)中定义它,这就好像您编写了inta[]={0}代码>,它将定义为一个元素的数组。因此,它实际上是一个定义,而不仅仅是一个声明
为了防止它成为一个试探性的定义并成为一个定义,您需要将它声明为extern int a[]代码>
如果没有,则此定义将包含在一个源文件中,而定义将包含在正在链接的另一个文件中。然后,如果在程序中使用a
,则违反了C 2018 6.9 5:
…如果在表达式中使用了通过外部链接声明的标识符(而不是作为结果为整数常量的sizeof
或\u Alignof
运算符的操作数的一部分),则整个程序中的某个地方应正好有一个标识符的外部定义;否则,不得超过一个
该“应”是外部定义语义的一部分,而不是约束的一部分,因此受4.2:
如果违反了出现在约束或运行时约束之外的“应”或“不应”要求,则行为未定义
这就解释了我上面的第二个要点
然而,除此之外,这是一个实例,其中C标准的“未定义行为”实际上在一些常见用途中。在常见的Unix工具中,一个翻译单元中的暂定定义将根据需要与其他翻译单元中的非暂定定义进行解析。因此,如果您使用支持此功能的工具,那么“否”也是您想要回答的问题的答案。我使用了repl.it(clang),它有一个有趣的结果
情景1:
#include<stdio.h>
int a[5] = {1,2,3};
#include<stdio.h>
int a[5] = {1,2,3};
#include<stdio.h>
int a[5] = {1,2,3};
#包括
int a[5]={1,2,3};
#包括
外部内部a[];
int main()
{
printf(“%d”,a[1]);/“2”
printf(“%lu”,sizeof(a));//错误“将sizeof应用于不完整的int[]类型无效”
返回0;
}
情景2:
#包括
int a[5]={1,2,3};
#包括
INTA[];
int main()
{
printf(“%d”,a[1]);/“2”
printf(“%lu”,sizeof(a));//错误“将sizeof应用于不完整的int[]类型无效”
返回0;
}
情景3:
#include<stdio.h>
extern int a[];
int main()
{
printf("%d", a[1]); // '0'
printf("%lu", sizeof(a)); // error 'invalid application of sizeof to incomplete type int []'
return 0;
}
#包括
外部内部a[];
int main()
{
printf(“%d”,a[1]);/“0”
printf(“%lu”,sizeof(a));//错误“将sizeof应用于不完整的int[]类型无效”
返回0;
}
情景4:
#包括
int a[5]={1,2,3};
#包括
外部INTA[2];
int main()
{
printf(“%d”,a[1]);/“2”
printf(“%lu”,大小为(a));/“8”
返回0;
}
情景5:
#include<stdio.h>
int a = 2;
#包括
INTA=2;
#包括
INTA;
int main()
{
printf(“%d”,a);/“2”
返回0;
}
仅静态int a代码>将生成“0”,但int a代码>似乎隐式地被视为extern inta代码>尽管(extern)int a=1如果在另一个文件中初始化,则代码>将被视为多定义错误(如果在另一个文件中未初始化,即int a;
,则另一个文件将在主文件中使用覆盖的外部初始化)<代码>(外部)内部a一个文件中的代码>和int a另一个文件中的code>会导致在两个文件中使用一个零初始化的未初始化int a<代码>外部内部a代码>和其他文件中的任何内容都不会导致错误
请参阅以了解原因。如果这是标准的一部分,那么这不是一个完全不敏感的规则,但是有一种简单/优雅的方式可以减少特殊情况:每个想要成为声明的filescope对象声明都需要一个#include<stdio.h>
int a[5] = {1,2,3};
#include<stdio.h>
extern int a[2];
int main()
{
printf("%d", a[1]); // '2'
printf("%lu", sizeof(a)); // '8'
return 0;
}
#include<stdio.h>
int a = 2;
#include<stdio.h>
int a;
int main()
{
printf("%d", a); // '2'
return 0;
}