C语言中extern详细用法解析

在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。 
1. extern修饰变量的声明。 

举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。 

2. extern修饰函数声明。从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。 

使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。 

3. 此外,extern修饰符可用于指示C或者C++函数的调用规范。 

比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。 

4. 举个简单的例子:  
用C语言编写程序的时候,我们经常会遇到这样一种情况:希望在头文件中定义一个全局变量,然后包含到两个不同的c文件中,希望这个全局变量能在两个文件中共用。 

举例说明:项目文件夹project下有main.c、common.c和common.h三个文件,其中
common.h文件分别#include在main.c和common.c文件中。现在希望声明一个字符型变量key,在main.c和common.c中公用。如下图所示: 
有人想,既然是想两个文件都用,那就在common.h中声明一个unsigned char key,然后由于包含关系,在main.c和common.c中都是可见的,所以就能共用了。 
这种想法其实是很多初学者都会想到的,想起来确实有道理,但是实际写出来,我们发现编译的时候编译器提示出错,一般提示大概都类似于:Error: L6200E: Symbol key multiply defined (by common.o and main.o). 也就是说编译器认为我们重复定义了key这个变量。这是因为#include命令就是原封不同的把头文件中的内容搬到#include的位置,所以相当于main.c和common.c中都执行了一次unsigned char key,而C语言中全局变量是项目内(或者叫工程内)可见的,这样就造成了一个项目中两个变量key,编译器就认为是重复定义。 

正确的解决办法:使用extern关键字来声明变量为外部变量。具体说就是在其中一个c文件中定义一个全局变量key,然后在另一个要使用key这个变量的c文件中使用extern关键字声明一次,说明这个变量为外部变量,是在其他的c文件中定义的全局变量。请注意我这里的用词:定义和声明。例如在main.c文件中定义变量key,在common.c文件中声明key变量为外部变量,这样这两个文件中就能共享这个变量key了。 代码如下(只写跟我们所说问题有关的部分):

(1)main.c文件

#include “common.h”
unsigned char key;

(2)common.c文件

#include "common.h"
extern unsigned char key;

5. 实际运行的例子:

情景:在一个工程里面有两个文件a.c and b.c,其中它们的内容如下:

a.c:

#include <stdio.h>
int i = 3;
int p(void) {
    printf("%d\n",i);
    return 0;
 }

b.c:

#include <stdlib.h>
extern int p(void);
extern int i;
int main() {
    p();
    system("pause");
    return 0;
}

在b.c里面调用a.c里面定义的变量和函数,最后在Dev C++里面运行,b.c的输出结果为:3;

到此这篇关于C语言中extern详细用法解析的文章就介绍到这了,更多相关C语言中extern用法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言基础知识点解析(extern,static,typedef,const)

    一.extern的使用方法 下面是<C语言程序设计>中的关于extern的解释: 在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它(定义外部变量的源文件中也可以包含对该外部变量的extern声明).外部变量的定义中必须指定数组的长度,但extern声明不一定指定数组的长度. 外部变量的初始化只能出现在其定义中. 假设函数push与pop定义在一个文件中,而变量val与sp在另一个文件中定义本那个被初始化(通常不太可能这样组织程序),则

  • c语言中static和extern的用法详细解析

    一,static和extern:大工程下我们会碰到很多源文档. 文档a.c 复制代码 代码如下: static int i; //只在a文档中用int j;    //在工程里用static void init()         //只在a文档中用{}void callme()          //在工程中用{   static int sum;} 上面的全局i变量和init()函数只能用在a.c文档中,全局变量sum的作用域只在callme里.变量j和函数callme()的全局限扩充到整个

  • 深入理解C语言 static、extern与指针函数

    1.exit(0)正常退出程序 exit(1)程序异常时退出程序 2.static(静态变量)修饰局部变量 在局部变量使用static修饰,会延长局部变量的存在期.但我们需要注意一下几点: •虽然static修饰变量的生存期很长,但它始终是局部变量,不能在其他函数中使用•static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?     全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量.全局变量本身

  • 详解C语言正确使用extern关键字

    利用关键字extern,可以在一个文件中引用另一个文件中定义的变量或者函数,下面就结合具体的实例,分类说明一下. 一.引用同一个文件中的变量 #include<stdio.h> int func(); int main() { func(); //1 printf("%d",num); //2 return 0; } int num = 3; int func() { printf("%d\n",num); } 如果按照这个顺序,变量 num在main函

  • C语言中extern详细用法解析

    在C语言中,修饰符extern用在变量或者函数的声明前,用来说明"此变量/函数是在别处定义的,要在此处引用".  1. extern修饰变量的声明.  举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v.能够被其他模块以extern修饰符引用到的变量通常是全局变量.还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就

  • C语言中getchar的用法以及实例解析

    目录 getchar解析 一.getchar的返回类型及作用机制 二.根据一段代码初步了解 三.实例(“输入密码”)进一步了解 1.代码达不到理想效果 2.输入的密码中有空格 总结 getchar解析 一.getchar的返回类型及作用机制 getchar——读取字符的函数 int getchar(void) 返回类型为int,参数为void. 有人可能会有疑惑,getchar既然是读取字符的,为什么返回类型是int呢? 1.getchar其实返回的是字符的ASCII码值(整数). 2.getc

  • Go语言中append函数用法分析

    本文实例分析了Go语言中append函数用法.分享给大家供大家参考.具体如下: Go语言中append的功能十分强大,使用它可以使很多功能的实现变得更加简洁.以下为简单对比: .将一个slice插入到另一个slice的指定位置: 不使用append: 复制代码 代码如下: func insertSliceAtIndex(slice_origin []int, slice_to_insert []int,      insertIndex int) (result []int, err error

  • go语言中if语句用法实例

    本文实例讲述了go语言中if语句用法.分享给大家供大家参考.具体分析如下: if 语句看起来跟 C 或者 Java 中的一样,除了没有了 ( ) 之外(甚至强制不能使用它们),而 { } 是必须的. 复制代码 代码如下: package main import (     "fmt"     "math" ) func sqrt(x float64) string {     if x < 0 {         return sqrt(-x) + "

  • Go语言中Select语句用法实例

    本文实例讲述了Go语言中Select语句用法.分享给大家供大家参考.具体分析如下: select 语句使得一个 goroutine 在多个通讯操作上等待. select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支.当多个都准备好的时候,会随机选择一个. 复制代码 代码如下: package main import "fmt" func fibonacci(c, quit chan int) {         x, y := 1, 1         for {

  • Go语言中的switch用法实例分析

    本文实例讲述了Go语言中的switch用法.分享给大家供大家参考.具体分析如下: 这里你可能已经猜到 switch 可能的形式了. case 体会自动终止,除非用 fallthrough 语句作为结尾. 复制代码 代码如下: package main import (  "fmt"  "runtime" ) func main() {  fmt.Print("Go runs on ")  switch os := runtime.GOOS; os

  • Go语言中的range用法实例分析

    本文实例讲述了Go语言中的range用法.分享给大家供大家参考.具体如下: for 循环的 range 格式可以对 slice 或者 map 进行迭代循环. 复制代码 代码如下: package main import "fmt" var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} func main() {     for i, v := range pow {         fmt.Printf("2**%d = %d\n"

  • Go语言中slice的用法实例分析

    本文实例讲述了Go语言中slice的用法.分享给大家供大家参考.具体如下: slice 指向数组的值,并且同时包含了长度信息. []T 是一个元素类型为 T 的 slice. 复制代码 代码如下: package main import "fmt" func main() {  p := []int{2, 3, 5, 7, 11, 13}  fmt.Println("p ==", p)  for i := 0; i < len(p); i++ {   fmt.

  • go语言中linkname的用法

    在go语言的源码中,会发现很多,代码只有函数签名,却看不到函数体,如: // src/os/proc.go 68行 func runtime_beforeExit() // implemented in runtime 此处我们只看到函数签名,却看不到函数体,全局搜了一把,发现它的函数体却定义在src/runtime/proc.go中 // os_beforeExit is called from os.Exit(0). //go:linkname os_beforeExit os.runtim

  • 汇编语言中cmp指令用法笔记与总结

    本文实例讲述了汇编语言中cmp指令用法.分享给大家供大家参考,具体如下: cmp是比较指令,cmp的功能是相当于减法指令,只是不保存结果.cmp指令执行后,将对标志寄存器产生影响.其他相关指令通过识别这些被影响的标志寄存器来得知比较结果. cmp指令格式: cmp  操作对象1,操作对象2 功能: 计算操作对象1 - 操作对象2 但不保存结果,仅仅根据计算结果对标志寄存器进行设置.比如cmp ax,ax  是做ax - ax 的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位. 指令

随机推荐