提问人:Gopi 提问时间:7/17/2015 最后编辑:Serge BallestaGopi 更新时间:7/18/2015 访问量:282
定义和声明是否匹配?
Should definition and declaration match?
问:
在我的 .h 文件中,我有
extern int a[4];
在我的 .c 文件中我有
int a[10];
那么这有什么问题吗?
声明和定义大小重要吗?不对?
如果我写入其中一个文件,输出是什么?
这是未定义的行为吗?sizeof(a)
答:
如果在源文件中包含头文件,则 的两个声明必须具有与 C 相同的类型:a
(C11,6.7页4)“同一作用域内引用同一对象或函数的所有声明都应指定兼容类型。”
即使这两个声明位于两个转换单元中,它们也需要具有相同的类型:
(C11,6.2.7p2)“所有涉及同一对象或功能的声明都应具有兼容的类型;否则,行为是未定义的。
评论
extern const int x;
int x = 42;
看起来像这样:
extern int a[4];
int a[10];
int main()
{
return 0;
}
GCC 报告冲突的类型:
cc -Wall -g -ggdb -pipe -pedantic -std=gnu99 test.c -o test
test.c:2:5: error: conflicting types for ‘a’
int a[10];
^
test.c:1:12: note: previous declaration of ‘a’ was here
extern int a[4];
^
评论
正如 @ouah 所说,它是正式的 Undefined Behavior,并且容易出错,因此它不应该存在于生产代码中。
但它会被接受并得到正确的结果,但大多数(如果不是全部)常见的编译器(gcc、clang、msvc 会这样做)
如果将 .h 文件包含在 .c 中,则会出现错误,因为您正在将 a 重新定义为不同的类型(正如其他人已经说过的那样)。extern int a[4];
int a[10];
如果仅在其他编译单元中包含 .h,则链接器应忽略大小并正确链接它。
简单地说,您将在定义它的 .c 中以及包含声明它的其他编译单元中得到它。sizeof(a) == 10 * sizeof(int)
sizeof(a) == 4 * sizeof(int)
工作实例:
foo.c:
#include <stdio.h>
int a[10];
void display();
int main() {
for(int i=0; i<sizeof(a)/sizeof(a[0]); i++) {
a[i] = i;
}
printf("sizeof(a)=%d\n", sizeof(a));
display();
return 0;
}
foo2.c:
#include <stdio.h>
extern int a[4];
void display() {
printf("sizeof(a)=%d\n", sizeof(a));
for(int i=0; i<sizeof(a)/sizeof(a[0]); i++) {
printf(" %2d", a[i]);
}
fputs("\n", stdout);
}
编译 + 链接 : : 甚至没有警告cc foo.c foo2.c -o foo
执行:
sizeof(a)=40
sizeof(a)=16
0 1 2 3
这在 fortran 的 commons 中很常见,其中编译单元只能声明 common 的开头,但我无法想象在 C 中这种恐怖的真实用例。
它起作用的原因
编译器无法在编译时检测到同一程序中存在类型不兼容的声明,因为它们位于不同的翻译单元中,因此被处理但不同的编译阶段 - 可能在不同的时间。
在链接时,链接器只能看到不同声明的地址,并确保所有 .o(或 .obj)都获得相同的地址。在不破坏多语言兼容性的情况下,很难做不同的事情:这是在C模块和汇编语言模块之间共享数组的方式。a
为什么你不应该使用它
可以说,没有什么能阻止编译器在面对标准定义的未定义行为时执行写入操作。但汉斯·帕桑特(Hans Passant)曾经给了我一个链接,指向一篇关于未来编译者研究的文章。以下是一些摘录:
本文是关于对 C 抽象计算机的一种新的内存安全解释,它提供了更强大的保护,有利于安全和调试 ...[作者]证明,C的内存安全实现不仅可以支持指定的C抽象机器,还可以支持与现有代码兼容的更广泛的解释。通过在硬件中强制执行模型,我们的实现提供了内存安全性,可用于为 C 语言提供高级安全属性。
[实现] 内存功能表示为三元组(base、bound、permissions),它松散地打包成一个 256 位值。这里 base 提供了一个进入虚拟地址区域的偏移量,并限制了访问区域的大小......特殊的功能加载和存储指令允许将功能溢出到堆栈或存储在数据结构中,就像指针一样......需要注意的是,指针减法是不允许的。
添加权限后,功能可以成为向引用的内存授予某些权限的令牌。例如,内存功能可能具有读取数据和功能的权限,但不能写入它们(或者只是写入数据而没有功能)。尝试任何不允许的操作都会导致陷阱。
结果证实,在不牺牲低级语言优势的情况下,可以保留能力系统内存模型(提供不可绕过的内存保护)的强语义。
(强调我的)
TL/DR :没有什么能阻止未来的编译器在对象(编译)模块内添加数组的大小信息,如果它们不兼容,则引发错误。目前存在针对此类特征的研究
评论
sizeof
评论
conflicting types for 'a'