在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
- 在C语言中,extern关键字用于声明一个已经在其他模块中定义的变量或函数,并在本模块中引用它。在使用extern关键字时,需要注意以下几点:
-
在声明一个全局变量时,如果没有使用extern关键字,那么默认情况下该变量就已经是extern的了,也就是说它是一个外部链接的变量。因此,在文件a.c中要引用文件b.c中的全局变量v时,可以直接在a.c中声明该变量,而不需要使用extern关键字。例如:在文件a.c中引用全局变量v,该变量定义在文件b.c中:
/* 文件b.c中定义全局变量v */ int v = 1; /* 文件a.c中引用全局变量v */ #include <stdio.h> extern int v; // 可以省略extern关键字 int main() { printf("%d", v); // 输出1 return 0; }
-
被引用的变量或函数的链接属性必须是外链接(external)的,也就是说它需要在其他模块中被定义。例如:在文件a.c中定义一个具有内部链接(internal linkage)的变量v:
/* 文件a.c中定义具有内部链接的变量v */ static int v = 1;
在文件b.c中,我们尝试引用变量v:
/* 文件b.c中尝试引用变量v */
#include <stdio.h>
extern int v; // 声明变量v
int main() {
printf("%d", v); // 错误:链接时未定义的符号
return 0;
}
在编译链接时,编译器会报出链接时未定义的符号错误,因为变量v具有内部链接,只能在文件a.c中被访问。如果将文件a.c中变量v的链接属性改为外部链接,则不会出现链接错误:
/* 文件a.c中定义具有外部链接的变量v */
int v = 1;
现在,在文件b.c中引用变量v就不会报链接错误了。 总之,被extern声明的变量或函数必须在其他模块中定义并具有外部链接,否则会出现链接错误。
-
在声明一个变量或函数时,如果它已经在其他模块中定义过了,就可以使用extern关键字来引用它。举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。
/* 文件b.c中定义全局变量v */ int v = 1; /* 文件a.c中引用全局变量v */ #include <stdio.h> extern int v; int main() { printf("%d", v); // 输出1 return 0; }
-
extern关键字可以在任何作用域内使用,比如在函数fun中需要引用全局变量v时,可以在函数fun中使用extern关键字声明变量v,而不需要在文件a.c的开头声明变量v。在函数fun中引用全局变量v:
/* 文件b.c中定义全局变量v */ int v = 1; /* 文件a.c中定义函数fun */ void fun() { extern int v; printf("%d", v); // 输出1 } int main() { fun(); return 0; }
-
除了全局变量和函数之外,extern关键字还可以用于声明一个已经存在于其他模块中的静态变量或函数,让它在不同模块之间共享。例如:
在文件a.c中定义一个静态变量和一个函数:
/* 文件a.c中定义静态变量s和函数foo */
static int s = 10;
void foo() {
printf("s = %d\n", s);
}
在文件b.c中使用extern关键字声明静态变量s和函数foo,并在函数bar中引用它们:
/* 文件b.c中声明静态变量s和函数foo */
extern int s;
extern void foo();
void bar() {
s += 20;
foo();
}
int main() {
bar();
return 0;
}
在函数bar中,我们使用extern关键字来声明静态变量s和函数foo,表示它们是在其他模块中定义的,并将它们引入到当前函数中。然后我们就可以在函数bar中使用静态变量s和函数foo了。由于静态变量和函数的作用域只限于定义它们的模块内部,我们需要使用extern关键字来将它们的作用域扩展到其他模块。
总之,extern关键字不仅适用于全局变量和函数的声明,还适用于静态变量和函数的声明,让它们在不同的模块中共享。
- extern修饰函数声明。
假设我们有两个文件:a.c和b.c。在b.c中,定义了一个名为fun的函数,其原型为int fun(int mu)。在a.c中,我们需要引用b.c中的这个函数。
我们可以在a.c中声明一个extern int fun(int mu)的函数声明,告诉编译器fun函数是在其他文件中定义的。这样,在a.c中就可以使用fun函数了。
示例代码如下:
b.c:
// 在b.c中定义了一个名为fun的函数
int fun(int mu) {
// 函数体
}
a.c:
#include <stdio.h>
// 声明b.c中的fun函数
extern int fun(int mu);
int main() {
int result = fun(5);
printf("The result is %d\n", result);
return 0;
}
在a.c中,我们使用extern关键字声明了fun函数,告诉编译器fun函数是在其他文件中定义的。然后,在main函数中调用了fun函数,计算结果并输出。当我们编译a.c文件时,编译器会跳过对fun函数的定义,因为fun函数已经在其他文件中定义了。因此,我们需要在链接阶段将a.c和b.c连接起来,生成可执行文件。
对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?
使用extern关键字和包含头文件来引用函数的主要区别在于:
a.代码简洁程度:使用extern关键字可以让代码更加简洁,不需要在头文件中声明函数。而使用头文件则需要在头文件中声明函数,然后在需要使用的文件中包含头文件。
b.编译时间:使用extern关键字声明函数可以加速编译过程,因为编译器可以跳过函数的定义。而使用头文件则需要编译器编译头文件中的函数声明和定义。需要注意的是,这种差异只在大型C程序编译过程中才会明显。
c.可读性:使用头文件可以让代码更加易读,因为头文件中的函数声明可以让人很容易地了解该函数的接口和使用方式。
需要注意的是,使用extern关键字和包含头文件并不是互相排斥的,可以根据实际情况选择使用哪种方式。在大型项目中,通常会使用头文件来声明函数,以方便管理和维护。而在小型项目或者单个文件中,使用extern关键字来声明函数可以让代码更加简洁。
- 此外,extern修饰符可用于指示C或C++函数的调用约定。例如,在C++中调用C库函数时,需要在C++程序中使用extern "C"来声明要引用的函数(这种用法仅适用于C++,在C中使用会出错)。这是为了链接器,在链接时告诉链接器使用C函数约定来链接。主要原因是C++和C程序在编译完成后在目标代码中的命名规则不同。
4.extern变量仅仅是一个变量的声明,它不会为变量分配内存空间。如果在程序中定义多个相同名称的变量,会导致连接错误。在变量声明中使用extern的主要作用是将一个在*.c文件中定义的全局变量在*.h文件中声明,并使其可以被其他文件引用。这样,其他文件就可以使用该变量而不必重新定义它。
5.在 C/C++ 语言中,extern
是一个关键字,用于表明函数和全局变量的作用范围(可见性)。它告诉编译器,其声明的函数和变量可以在本模块或其他模块中使用。
通常情况下,在模块的头文件中,对本模块提供给其他模块引用的函数和全局变量使用 extern
关键字进行声明。也就是说,如果在一个 C 文件中定义了一个函数或变量,并希望将其开放给外部文件使用,就需要在头文件中使用 extern
进行声明。这样,在外部文件中只需要 include
该头文件,就可以使用其中定义的函数或变量了。
需要注意的是,在编译阶段,如果在头文件中使用了 extern
声明了一个全局变量或函数,编译器会检查是否有对应的定义。如果找不到定义,链接会失败并报错。因此,在不同文件中定义和声明时,需要确保头文件中的声明与源文件中的定义相匹配,否则会导致链接错误。
总之,extern
关键字的作用是声明一个函数或变量,使得其他模块可以引用并使用它。
6.函数的声明默认情况下是extern
的,也就是说如果没有显式地指定函数为static
或inline
,则默认为extern
。如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。因此,以下两个函数声明是等价的:
extern int f();
int f();
但是,需要注意的是,在函数定义时,不能使用extern
关键字,否则会导致编译错误。因此,函数定义和声明的语法有所不同。
此外,在一个 C 或 C++ 程序中,通常使用头文件来包含函数的声明,这可以确保在使用该函数时,编译器可以正确地解析函数的参数和返回类型。因此,在其他源文件中使用定义在一个源文件中的函数时,通常需要在该源文件中使用extern
关键字进行函数声明,或者包含定义该函数的源文件中的头文件。以下是一个例子:
// foo.c
int foo() {
return 42;
}
// bar.c
extern int foo(); // 声明 foo 函数
int main() {
return foo();
}
在上面的例子中,bar.c
文件中的 main
函数使用了定义在 foo.c
文件中的 foo
函数。因此,bar.c
文件中需要在 main
函数之前声明 foo
函数,这可以通过使用 extern
关键字来完成。
总之,可以说在一个复杂的项目中,在所有的函数声明前添加extern
修饰是一种比较好的习惯,可以帮助开发人员更好地管理和组织代码。
7.当在模块A中声明了foo(int x, int y)
为extern "C"
类型时,意味着该函数按照C语言的规则进行命名和链接。因此,如果在模块B中声明foo
函数的方式不是按照C语言的规则进行命名和链接,那么模块B将无法正确地链接到模块A中的foo
函数。以下是一个示例代码:
在模块A中声明foo
函数:
#ifdef __cplusplus
extern "C" {
#endif
int foo(int x, int y);
#ifdef __cplusplus
}
#endif
在模块B中如果这样声明foo
函数:
extern int foo(int x, int y);
这里没有使用extern "C"
来声明foo
函数,这意味着模块B会按照C++的命名和链接规则来处理foo
函数,而不是按照C语言的规则。因此,模块B将无法正确地链接到模块A中的foo
函数。
反过来,如果在模块B中声明foo
函数时使用了extern "C"
来按照C语言的规则进行命名和链接:
extern "C" int foo(int x, int y);
那么模块B就可以正确地链接到模块A中的foo
函数了。但是,如果在模块A中声明foo
函数的方式不是extern "C"
,而是使用C++的方式:
int foo(int x, int y);
那么模块B仍然无法正确链接到模块A中的foo
函数。
另外需要注意的是,使用extern "C"
声明的函数是按照C语言的规则进行命名和链接的,这意味着函数名不会被C++的命名约定所影响。因此,即使模块B中声明的函数名与模块A中声明的函数名不完全相同,只要它们按照C语言的规则进行命名和链接,就可以正确地链接到模块A中的函数。