块作用域中的extern与文件作用域中的typedef不冲突吗?

时间:2018-08-12 02:57:32

标签: c gcc clang

代码:

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

gcc会生成错误“重新声明为另一种符号'a'”,但是当我们将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”,尽管它们在不同的作用域中,但编译器会生成错误。 / p>

然后看#4,#3,当编译器看到#4时,它将以相同的方式发现相同的'a'存在,为什么它不生成错误?

@Yunnosch和@Stargateur都针对不同的范围进行了解释,这显然是不正确的。我的观点是关于链接的事情,但是#2无法隐藏#1告诉我这也不是事实。

更新2:

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

3 个答案:

答案 0 :(得分: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 所有引用相同对象或函数的声明均应具有兼容的类型;否则,行为是不确定的。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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。它不再被内部作用域隐藏。

在您的第一种情况下,当两个a在相同范围内时(如我的示例2),就会发生冲突(在您的类型不同的情况下)。
在您的第二种情况下,两个a位于两个不同的范围内,并且没有冲突。
我的示例3显示,在那种情况下,即使它们具有相同的类型和相同的标识符,它们甚至不会冲突。

示例4:全局和外部局部之间的类型冲突 (交换类型,以继续示例序列)

#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