定义和声明是否匹配?

Should definition and declaration match?

提问人:Gopi 提问时间:7/17/2015 最后编辑:Serge BallestaGopi 更新时间:7/18/2015 访问量:282

问:

在我的 .h 文件中,我有

extern int a[4];

在我的 .c 文件中我有

int a[10];

那么这有什么问题吗?

声明和定义大小重要吗?不对?

如果我写入其中一个文件,输出是什么? 这是未定义的行为吗?sizeof(a)

c

评论

2赞 n. m. could be an AI 7/17/2015
它们必须匹配。为什么要找麻烦?
1赞 Gopi 7/17/2015
@n.m.问题是,如果它们不匹配怎么办?编译器对此很满意。但是分配的内存是 10 * sizeof(int),所以 sizeof(a) 应该是 10 * sizeof(int) 或 sizeof(a)
1赞 musefan 7/17/2015
你为什么不试试呢?似乎它甚至没有编译:conflicting types for 'a'
1赞 musefan 7/17/2015
你要找谁为你投票?这个问题表明没有研究努力
3赞 musefan 7/17/2015
@Gopi:测试比等待答案更快。如果你手头没有IDE,那就像我一样使用在线IDE。这样说吧:我能够比任何人都更快地在线测试它,而且在你开始写问题之前,你已经抢占了先机。你怎么可能这么快就需要答案,但你无论如何都无法编码?

答:

7赞 ouah 7/17/2015 #1

如果在源文件中包含头文件,则 的两个声明必须具有 C 相同的类型:a

(C11,6.7页4)“同一作用域内引用同一对象或函数的所有声明都应指定兼容类型。”

即使这两个声明位于两个转换单元中,它们也需要具有相同的类型:

(C11,6.2.7p2)“所有涉及同一对象或功能的声明都应具有兼容的类型;否则,行为是未定义的。

评论

0赞 Quentin 7/17/2015
我对这里使用“兼容”很感兴趣。有没有不一样的类型,但仍然适合这个?不同的简历资格,也许?
0赞 ouah 7/17/2015
@Quentin类型兼容性本质上是“类型相同”+其他一些情况。具有不同限定符的类型不兼容:例如,并且是未定义的行为。extern const int x;int x = 42;
6赞 dragosht 7/17/2015 #2

看起来像这样:

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];
            ^

评论

3赞 musefan 7/17/2015
加 1 表示在发布问题之前做 OP 自己应该做的事情
2赞 ouah 7/17/2015
@musefan,如果 .h 不包含在 .c 中,而是包含在另一个 .c 中,则即使它是未定义的行为,您也不会收到错误。
0赞 Serge Ballesta 7/17/2015
@ouah你是完全正确的(请参阅我的答案以获取一个工作示例),但看起来 C 程序员不喜欢这样的笑话:-(
0赞 Serge Ballesta 7/17/2015 #3

正如 @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 :没有什么能阻止未来的编译器在对象(编译)模块内添加数组的大小信息,如果它们不兼容,则引发错误。目前存在针对此类特征的研究

评论

0赞 ouah 7/17/2015
我没有投反对票,但以说它是无害的开始你的答案是不好的。首先,这是未定义的行为,而且您正确地表明,这将产生不同的结果,从而导致令人讨厌的错误。sizeof
1赞 ouah 7/17/2015
它没有很好的定义:请参阅我的答案和标准中的两句话。第一个引号是“shall”,违反约束之外的 shall 是未定义的行为,第二个引号明确表示它是未定义的行为。
0赞 Serge Ballesta 7/18/2015
@ouah : 我改变了我的答案。多亏了你的评论,现在应该更适合将来的参考。