文章目录
一 在标准C语言中,extern
主要作用:
1. 声明外部变量
- 跨文件共享变量:当多个源文件(
.c
文件)需要访问同一个全局变量时会用到。例如,有main.c
和func.c
两个源文件,在func.c
中定义了一个全局变量int global_var = 10;
,如果想在main.c
中使用这个变量,就需要在main.c
中使用extern
关键字进行声明,像这样:
// main.c
#include <stdio.h>
// 声明外部变量,表示global_var这个变量在别的文件中定义了,在这里只是告知编译器其存在
extern int global_var;
int main() {
printf("The value of global_var is %d\n", global_var);
return 0;
}
// func.c
int global_var = 10;
通过这种方式,main.c
就能访问到在 func.c
中定义的 global_var
变量了,使得不同的源文件之间可以共享和操作同一个全局变量。
- 避免重复定义错误:如果没有
extern
声明,而在多个源文件中直接定义同名的全局变量,链接器在链接阶段就会报错,提示重复定义。使用extern
进行正确声明,编译器就能知道变量实际定义的位置,从而正确处理对该变量的引用。
2. 声明外部函数
- 函数在其他文件中定义的情况:同样对于多文件项目,如果一个函数的定义在某个源文件中,而要在另一个源文件里调用它,需要在调用处所在的源文件中使用
extern
声明该函数(不过在C语言中,函数默认就是外部链接属性,所以extern
对于函数声明在很多时候可以省略,但明确写出有助于代码的清晰性和可维护性)。例如:
// main.c
#include <stdio.h>
// 声明外部函数,add函数实际定义在另一个文件中,这里只是告诉编译器其存在,让编译器能进行正确的编译检查
extern int add(int a, int b);
int main() {
int result = add(3, 5);
printf("The result of addition is %d\n", result);
return 0;
}
// math_functions.c
// 这里是add函数的实际定义
int add(int a, int b) {
return a + b;
}
这样就能实现在 main.c
中调用在 math_functions.c
中定义的 add
函数了。
3. 改变变量或函数的链接属性(相对高级用法)
在C语言中,可以利用 extern
与 static
等关键字配合来改变变量或者函数原本默认的链接属性。例如,默认情况下函数是具有外部链接属性的,意味着不同源文件之间可以相互调用。但如果想限制某个函数只能在当前源文件内部使用,可以在函数定义前加上 static
关键字。而如果想把一个原本具有内部链接属性(例如被 static
修饰定义的变量)的变量临时赋予外部链接属性(这种情况比较少见且需谨慎使用,要符合具体需求场景),可以使用 extern
配合一些特定的编译指令等操作来实现,不过这涉及到对链接过程以及编译机制更深入的理解和应用了。
总体而言,extern
的核心作用就是在C语言的多文件编程环境下,用于声明那些定义在其他地方的变量和函数,从而实现代码模块之间的正确交互和数据共享。
二 在keil 中extern 对 sfr 和 sbit 的声明
在单片机(以经典的51单片机为例)编程中,sfr
和 sbit
是两个很重要的用于访问特殊功能寄存器及其相关位的关键字,以下是它们的详细介绍:
1. sfr(Special Function Register,特殊功能寄存器)
- 含义与作用:
特殊功能寄存器是单片机内部用于控制和反映硬件各种功能状态的寄存器,像控制定时器、串口通信、I/O端口等操作都需要通过读写相应的特殊功能寄存器来实现。sfr
关键字就是专门用来定义这些特殊功能寄存器的,它能将一个标识符(变量名形式)和单片机内部特定的寄存器地址对应起来,方便程序员在编程中对这些寄存器进行操作,就好像给这些硬件相关的寄存器起了个好记的名字。 - 语法格式:
一般语法格式为sfr 寄存器名 = 地址值;
,例如在51单片机中定义端口P0
这个特殊功能寄存器的代码如下:
sfr P0 = 0x80; // 将标识符P0和地址为0x80的特殊功能寄存器对应起来,0x80就是P0口对应的实际物理寄存器地址
这意味着后续在程序里写 P0
时,编译器就知道实际是在操作地址为 0x80
的那个寄存器了。可以通过对 P0
赋值等操作来控制 P0
口的引脚电平状态,比如 P0 = 0xff;
就是让 P0
口的8个引脚输出高电平(假设是标准的51单片机端口操作逻辑)。
- 使用注意事项:
不同的单片机芯片其内部特殊功能寄存器的数量、地址以及功能都不一样,所以sfr
的具体定义内容需要根据实际所使用的单片机型号来进行准确编写,通常会基于芯片厂家提供的头文件或者数据手册来确定每个sfr
定义的地址等信息。并且sfr
主要用于定义字节宽度(8位)的特殊功能寄存器,对于一些需要操作寄存器中单独位的情况,就需要借助sbit
关键字了。
2. sbit(可位寻址的特殊功能寄存器位)
- 含义与作用:
很多时候,我们不仅需要对整个特殊功能寄存器进行操作,还需要单独控制或读取寄存器里面某个特定的位,sbit
关键字就是用于定义特殊功能寄存器中可位寻址的位的。它可以精准地将一个标识符与特殊功能寄存器里的某一位对应起来,便于实现诸如控制某个引脚的输入输出方向、获取某个中断标志位状态等精细操作。 - 语法格式:
常见的语法有以下几种:- 直接位寻址方式:
sbit 位变量名 = 位地址值;
,例如在51单片机中,已知TCON
(定时器控制寄存器)的地址为0x88
,其第0位(TF0
,定时器0溢出标志位)的位地址是0x88
,可以这样定义并使用:
- 直接位寻址方式:
sbit TF0 = 0x88;
// 在程序中就可以判断TF0这个位的值来知晓定时器0是否溢出了,比如:
if (TF0) {
// 定时器0溢出后的处理代码
}
- 通过特殊功能寄存器和位序号定义:
- sbit 位变量名 = 特殊功能寄存器名 ^ 位序号;,还是以 TCON 寄存器为例,TF0是 TCON 的第0位,也可以这样定义:
sfr TCON = 0x88;
sbit TF0 = TCON ^ 0;
// 后续同样可以根据TF0的值来做相应处理
- 通过字节地址和位序号定义:
- sbit 位变量名 = 字节地址值 ^ 位序号;,如:
sbit TF0 = 0x88 ^ 0;
这几种方式都能有效地定义出特殊功能寄存器中的可位寻址位,方便在程序中单独对这些位进行操作。
- 使用注意事项:
不是所有的特殊功能寄存器中的位都是可位寻址的,只有特定的一些寄存器的特定位才支持,同样需要参照单片机的数据手册来确定哪些位可以用sbit
进行这样的定义操作。而且不同单片机芯片的可位寻址范围以及对应的位功能等都各不相同,要根据具体的芯片情况来准确运用sbit
关键字。
总之,sfr
和 sbit
在单片机编程中是非常实用的关键字,帮助程序员方便、准确地与单片机内部的硬件寄存器及其具体位进行交互,实现各种硬件控制和状态获取等功能。
三 遇到的问题
sfr 和 sbit 不是标准 C 语言支持的关键字,它们是 Keil 为 80C51 系列单片机等特定硬件平台开发的 C 语言扩展关键字
此实验在keil下进行
PS:common.h文件作为头文件包含在各个.c文件中
当我其它模块想要引用在main函数中定义的标准c语言全局变量 与引用 sfr和sbit定义的全局变量的区别
测试
c标准数据类型
sbit 和 sfr数据类型
当在其他文件引用main.c中定义的全局变量如果像c标准数据类型一样的话
编译会发现
common.h(8): error C141: syntax error near ‘sfr’
common.h(9): error C141: syntax error near ‘sfr’
common.h(10): error C141: syntax error near ‘sfr’
common.h(11): error C141: syntax error near ‘sfr’
common.h(12): error C141: syntax error near ‘sfr’
common.h(13): error C141: syntax error near ‘sfr’
common.h(15): error C141: syntax error near ‘sbit’
common.h(16): error C141: syntax error near ‘sbit’
common.h(17): error C141: syntax error near ‘sbit’
出现上述错误
当在引用该变量的.c文件重新定义
编译成功
结论
1.对于c标准数据类型其他.c文件想引用时,在.c文件以extern声明即可
2.对于sfr和sbit数据类型其他.c文件想引用时,必须重新定义否则编译不通过.