如果全局变量有默认的外部链接,为什么我们不能直接在另一个文件中访问它?

时间:2016-06-28 11:39:16

标签: c global-variables extern

我经历了以下问题:

上面的链接描述了如果我们在一个文件中定义全局变量并且没有指定extern关键字,那么由于翻译单元,它们将可以在另一个源文件中访问。

现在我已经file1.c定义了以下全局变量和函数:

int testVariable;

void testFunction()
{
    printf ("Value of testVariable %d \n", testVariable);
}

file2.c中有以下代码

void main()
{
    testVariable = 40;
    testFunction();
}

现在我得到了error: 'testVariable' undeclared (first use in this function) - 为什么?

注意:两个文件都使用makefile在同一个程序中使用。

根据我的理解,函数和全局变量都有默认的外部链接。那么函数我们可以直接在另一个文件中使用它的名字,但变量不是为什么?

任何人都有想法吗?

修改

从下面的答案我得到的想法就像在函数旧的编译器将猜测并添加一个隐式声明,但在变量的情况下,它不能。此外,C99删除了隐式声明,但我仍然在C99模式中收到警告,如:

warning: implicit declaration of function ‘testFunction’.

现在已经通过以下链接:

implicit int and implicit declaration of functions with gcc compiler

它说编译器将其作为诊断目的而不是出错。所以编译器可以向前处理。

但是为什么在变量的情况下它无法进一步处理?即使在函数的情况下,如果编译器继续进行,如果实际的定义不存在,那么在链接时它将失败。那么前进有什么好处?

2 个答案:

答案 0 :(得分:7)

这里有两件事情:首先是定义声明之间存在差异。另一件事是translation units的概念。

定义是定义变量的定义,它是变量存在的实际位置,编译器为变量保留空间。

编译器需要声明才能知道程序中存在某处的符号。如果没有声明,编译器将不知道符号存在。

翻译单元基本上非常简化了源文件及其所有包含的头文件。目标文件是单个翻译单元,链接器将所有翻译单元创建最终程序。

现在,程序只能有一个定义,例如全局变量可能只存在于一个转换单元中,或者在链接时会出现多个定义错误。另一方面,声明可以存在于任意数量的翻译单元中,编译器将使用它来告诉链接器翻译引用另一个(编译时未知)翻译单元的定义。

所以这里发生的是你在file1.c中有一个定义的声明。此源文件用作一个转换单元的输入,编译器为其生成单个目标文件,例如file1.o。在另一个源文件file2.c中,没有全局变量testVariable的定义或声明,因此编译器不知道它存在并且会给它一个错误。您需要声明,例如通过执行

extern int testVariable;  // This is a declaration of the variable

对于该函数来说有点复杂,因为在C标准的旧版本中,没有必要声明正在使用的函数,编译器会猜测并添加隐式声明。如果定义和隐式声明不匹配,则会导致未定义行为,这就是在C99标准中删除隐式函数声明的原因。所以你应该真正宣布这个功能:

void testFunction(void);  // Declare a function prototype

请注意,此处不需要extern关键字,因为编译器可以自动判断它是函数原型声明。

完整的file2.c应该看起来像

extern int testVariable;  // This is a declaration of the variable

void testFunction(void);  // Declare a function prototype

void main()
{
    testVariable = 40;
    testFunction();
}

答案 1 :(得分:0)

当编译器处理file2.c时,它对testVariable的存在及其类型一无所知。结果它无法生成与此类对象交互的代码。和

这样的行的目的
extern int testVariable;

是让编译器知道某个地方存在这样的对象并且类型为int。 对于函数,由于下一个规则我们没有这样的问题 - 如果没有定义函数 - 编译器假定它被定义在某处,如

int testFunction() { ... }

因此,您可以向其传递任意数量的任何参数,并尝试获取int返回值。但是如果真正的函数签名不同 - 你将在运行时得到一个未定义的行为。由于这种弱点,这种方法被认为是不好的做法,你应该在调用该函数之前声明适当的函数原型。