【问题标题】:extern in block scope does not conflct with typedef in file scope?块范围内的 extern 与文件范围内的 typedef 不冲突?
【发布时间】:2018-08-12 02:57:31
【问题描述】:

代码:

typedef int a;  // #1
extern int a;   // #2, error

gcc 会产生错误“'a' redeclared as a different kind of symbol”,但是当我们将extern 声明移到块作用域时,不会有警告,为什么?

typedef int a;  // #3
void foo() {
    extern int a;  // #4, ok
}

还有:

char a;  // #5
void foo() {
    extern int a;  // #6, error
}

更新:

感谢@Yunnosch 的回复,但它仍然无法回答我的问题。我们看#6,#5,当编译器看到#6时,它会尝试在文件范围标识符中查找是否存在相同的'a',虽然它们在不同的范围内,但编译器会产生错误。

然后看#4,#3,当编译器看到#4的时候,会发现同样的'a'以同样的方式存在,为什么不报错呢?

@Yunnosch 和@Stargateur 都对不同的范围进行了解释,这显然不是真的。我的观点是它与链接有关,但 #2 无法隐藏 #1 告诉我这也不是真的。

更新 2:

感谢@AnT,他给出了非常详细的解释。

【问题讨论】:

  • 你明白typedef int a;是做什么的吗?
  • @Stargateur 它引入了一种类型,我混淆的是这里 extern 的不同行为。
  • 这只是允许它的范围。
  • @Stargateur 为什么允许在块范围内?

标签: c gcc clang


【解决方案1】:

typedef 名称和变量名称都是 C 中的普通标识符。它们共享相同的名称空间。你不能有两个同名的声明,在相同的范围和相同的命名空间中声明不同的实体

6.2.1 标识符的范围
2 [...] 指定的不同实体 相同的标识符要么具有不同的范围,要么位于不同的名称空间中。[...]

此外,正如 Stefan Ram 在 comp.lang.c 6.7/3 中所建议的,这里可能更相关

6.7 声明
3 如果标识符没有链接,则标识符的声明(在声明符或类型说明符中)不得超过一个相同的范围和相同的命名空间,除了:
— 可以重新定义 typedef 名称以表示与当前相同的类型, 前提是该类型不是可变修改的类型;
— 可以按照 6.7.2.3 中的规定重新声明标签。

在任何一种情况下,关键是您的两个声明都是在相同的范围和相同的名称空间中进行的。这是您的第一个代码示例违反的要求。这就是编译器所抱怨的。

您的第二个和第三个代码示例在不同的范围中声明了两个标识符a。那里没有违反 6.2.1/2。这些示例可能存在其他问题,但情况完全不同。

您的第二个示例可能完全有效,前提是您在不同的翻译单元(在不同的文件范围内)定义全局 a,其定义不会与 typedef 声明冲突。

您的第三个示例导致未定义的行为,因为 a 的外部定义指定了与本地 extern 声明不兼容的类型。

6.2.7 兼容类型和复合类型
2 引用同一对象或函数的所有声明都应具有兼容类型;否则,行为未定义。

【讨论】:

  • 谢谢。看看我的第一个例子,为什么#2 不能在另一个翻译单元中引用a
  • 还是仅仅因为不符合6.2.1的要求?
  • @noinput:它没有机会这样做,因为它立即被 6.2.1 阻止。
【解决方案2】:

如果文件范围对象或函数不需要在文件外部可见,则应通过将其声明为静态来隐藏它。这种做法创建了更多模块化代码并限制了对全局名称空间的污染。

【讨论】:

  • 感谢您提出使用static 的想法。我将它纳入我的答案。然而,我不明白你的意思。使用 static 确实限制了文件范围,但它不会隐藏同一文件中的任何内容。您想详细说明static 的使用如何帮助解决问题或与问题的解释相关吗?我愿意在我介绍static的回答中给予信任。
【解决方案3】:

C 编译器不喜欢在作用域内看到相互冲突的符号定义,
但不要介意冲突是否只发生在不同的范围内。

例如,甚至使用相同的符号来夸大:

示例 1:基本、无警告、无错误

#include <stdio.h>
int a=0;
int main()
{
    printf("%d\n", a);
    return 0;
}

示例2:冲突相同的范围

#include <stdio.h>
int a=0;
int a=5; // main.c 3 error: redefinition of 'a'

int main()
{
    printf("%d\n", a);
    return 0;
}

示例 3:不同范围无冲突

#include <stdio.h>
int a=0;

int main()
{
    {
        int a=5; 
        printf("%d\n", a);
    }
    printf("%d\n", a);
    return 0;
}

在示例 3 中,值为 5 的“内部”a 隐藏了外部 a
值 5 很高兴地从内部范围打印出来,它隐藏了外部 a
然后,当内部范围离开时,外部值 0 被愉快地打印出来。它不再被内部范围隐藏。

在您的第一种情况下,两个as(在您的不同类型的情况下)之间出现冲突,当它们在同一范围内时,就像在我的示例 2 中一样。
在您的第二种情况下,两个 as 在两个不同的范围内并且不冲突。
我的示例 3 表明,在这种情况下,如果它们是相同的类型和相同的标识符,它们甚至不会发生冲突。

示例 4:global 和 extern local 之间的冲突类型 (交换类型,继续示例序列)

#include <stdio.h>
int a=0;
int b=1;
static int c=10;

void foo() {
    extern int b;
    extern char a;  // main.c 8 error: conflicting types for 'a'
    extern char c;  // main.c 9 error: conflicting types for 'c'
}

int main()
{
    {
        int a=5;
        printf("%d\n", a);
    }
    printf("%d\n", a);
    return 0;
}

在示例 4 中,根据您的问题编辑,extern 告诉编译器使用已在别处定义的 a,明确告诉它也在此函数范围内使用它。
然而,编译器已经看到了a 的定义,并且(假设它是唯一的,因为否则它将是一个重新定义)它抱怨不同的类型。请注意不同的错误,“冲突类型”而不是“重新定义”。
与此相反,请注意上面与b 类似的行 冲突。告诉编译器知道b,它可以在其他地方找到,具有相同的类型。这是在外部全局范围内找到的。
使用static,创建符号文件范围而不是全局,并不会改变这一点,示例使用c 进行演示。 external c 仍被标识为在此文件范围内定义的具有不同类型的冲突 c

【讨论】:

  • 虽然#4和#3在不同的作用域,#4是用extern关键字声明的,是不是也当作文件作用域?例如,如果我将#3 更改为 'char a;',编译器会产生冲突错误。
  • 我没有得到你关于 externbefore 的有趣观点。我希望我的编辑现在对答案有所帮助。
  • 我的意思是'文件范围内的外部'和'块范围内的外部'有什么区别?
  • 我的观点是#3是一个typedef声明,所以它没有链接,所以#4可以隐藏#3。
  • 编译器应该如何告诉a_meaningtypea_meaningvariable
猜你喜欢
  • 1970-01-01
  • 2014-02-24
  • 2013-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多