C语言基础复习(二)函数指针与位操作
函数指针与回调函数
函数指针
作用:硬件驱动程序和用户应用程序相互分开,硬件驱动程序提供API函数,用户应用程序将函数作为回调函数的方式进行使用。
回调机制的好处是,在程序执行期间可以动态更改被调用
回调函数:作为参数传递给另一个函数的函数,接受回调作为参数的函数预计会在某个时间点执行它。
使用方法:函数名带括号就是函数指针,没括号是指针函数
把函数A的地址赋给一个函数指针p,以p为参数,赋值给函数B,函数B通过p调用A
函数指针是一个指向函数的指针变量,32位单片机中存放大小为4字节的地址
int *(*pfunc)(int,int*,void*)*;
typedef int *(*pfunc)(int,int*,void*)*;
一般用typedef定义函数指针类型。
typedef和define区别:typedef 语句是在编译过程中被解析的,而#define是在编译之前的预处理过程中被解析的
typedef uint8_t (*func_ptr) (void);
,就相当于把uint8_t (*) (void);
定义成了另一个别名 func_ptr
了。这个func_ptr
就表示了函数指针类型。
typedef int *(*pfunc)(int,int*,void*)*;
相当于把int(*)(int,int*,void*)
;定义成别名pfunc
相当于把 int(*)(int,int*,void*)
取一个别名pfunc
回调函数
作用:
虽然只是函数指针的应用,但通过函数指针的当时区分传入不同的函数入口地址去执行不同的函数,可以节省单片机的ram和rom的开支。
实现:
例如将callback函数看做底层函数,main函数看成上层应用函数,现在有上层方法A,上层方法B,在使用底层callback函数进行功能设计时,转回去拿到具体的方法A,方法B,再把结果返回main,此时方法A,B就是回调函数。
举例:
EcuM_AL_Reset是回调函数,属于对象外的,该函数被调用后,会MCAL标准函数Mcu_PerformReset来重启CPU
钩子函数
hook函数实际也是函数指针,因为也是用户定义的,也可以理解为回调函数,二者之间区别主要是回调函数主要是目的处理,hook函数主要是过程监控。
定义函数fun1,fun2,再定义一个pfun函数指针,在main函数里通过pfun指向fun1,fun2,这个过程称为挂钩子。在不确定main函数的功能的情况下,留下函数指针作为接口,挂上不同的函数就可以完成不同的功能。
HOOK函数相当于一个监视器,捕获消息队列中需要的内容去处理
挂钩子的过程也称为注册。在注册函数中,使用者把自己编写的钩子函数挂在已经声明的函数指针上,这个注册函数的参数是要挂上的钩子函数的地址,即函数指针。
举例:Autosar中的hook函数机制
1)由操作系统调用,在特定的context中取决于操作系统的实现
2)高于所有task
3)不被第二类中断程序打断。
4)属于操作系统的一部分
5)可以由用户定义功能
截获行为的函数调用(相当于监控器)。所有特定于应用程序的Hook函数(Startup, Shutdown and Error)必须返回(不接受阻塞或无限循环)。
当应用程序或操作系统在出现严重错误时请求系统关闭时调用 (ShutdownHook)
在Shutdown OS时,操作系统将调用钩子函数ShutdownHook,勾到EcuM_ShutDown那边,然后关闭(如下图: 多核系统关闭过程)。用户通常可以在ShutdownHook中自由定义任何系统行为
FUNC(void, OS_SHUTDOWNHOOK_CODE) ShutdownHook(StatusType Fatalerror)
{
if( GetCoreID() == OS_CORE_ID_MASTER )
{
EcuM_Shutdown();
}
}
位操作
1. 移位操作
①<< 左移 :左移几位就把左边的数去掉几位,右边补0;相当于25->26
源操作数 * 2的N次方(N取决于移动的位数) = 移动后的结果。
②>>右移:右移几位就把右边的数去掉几位,左边补0
源操作数 / 2的N次方(N取决于移动的位数) = 移动后的结果(*只取整数部分*)
算术左移和逻辑左移相同
算术右移符号位要一起移动,左边补符号位,11100算术右移一位为11110
2.逻辑运算
- &(与) 和0一起使用 可以清零
- |(或) 可以置1
- ^(异或)
3.举例
将特定数置1
0xf8 把第2位到第6位置1
1111 _1000
2到6 1_1111 =0x1f
从第二位开始置1 0x1f<<2 = 111_1100
第a位到第b位置1,(b-a+1)转为16进制
从哪一位开始置1 则左移多少位,<<a
把第4位到第8位和第23到25位同时置1
((0x1f <<4) | (0x7<<23))
将特定数置0
先置1 再按位取反
~((0x1f <<3)
操作 | 表达式 |
---|---|
设置整型数a的bit4 | a|=(1<<4) |
设置整型数a的bit4~bit7 | a|=((0x1f)<<4) |
清除整型数a的bit15 | a&=((~a<<15)) |
代码举例
- 清除特定位的值
VOID Clear_data( const UCHAR indat )
{
UCHAR byte_id;
UCHAR bit_dat;
UCHAR bit_id;
UCHAR bit_dat_inv;
/* indat/8 取得其数组id*/
byte_id = (UCHAR)If_Shift_LR( (ULONG)indat, ZSHIFT3 );
/* indat%8 取得其bit位*/
bit_id = (UCHAR)( indat & ZMASK_07 );
bit_dat = (UCHAR)If_Shift_LL( ZMASK_BIT0, (ULONG)bit_id );
bit_dat_inv = ~bit_dat;
/*&= 清除该位*/
array[ byte_id ] &= bit_dat_inv;
}
- memcpy函数
3.以2Byte为单位进行复制处理,从起始地址开始每2个Byte拷贝到目标地址,n是拷贝次数,如果16个字节,以2Byte为单位复写,n=8,以4Byte为单位复写,n=4
VOID If_Memcpy2( VOID* const dst, const VOID* const src, SIZE_T n )
{
USHORT* dst_p = (USHORT*)dst;
const USHORT* src_p = (const USHORT*)src;
/*n = n/2 如果是4个byte为单位进行复写,IfCfc_Shift_LR( (ULONG)n, 2 );*/
n = (SIZE_T)If_Shift_LR( (ULONG)n, 1 );
if ( n != 0 )
{
do
{
*(dst_p) = *(src_p);
dst_p++;
src_p++;
n--;
}
while ( n > 0 );
}
}
数据类型与关键字
enum枚举
注意点:
1.在同一个作用域能不能出现重名的枚举常量名;
虽然定义了两个枚举类型 enum1,enum2,但如果其成员常量名相同则会报错
2.不可以定义相同的变量但是可以定义相同的值;
`typedef enum
{
enumA=0,
enumB=4,
enumC=5,
enmuD=6,
enmuE=6,
}A_enum;`
union联合体
注意点
- 内存空间相同但储存不同的数据类型,并不是同时储存,而是一次只能存储一种数据类型。
- 联合体的大小都相等,每个联合可以储存各种数据类型。共用体的长度为其最大的成员的长度。
用途:
数据的格式在不同场合下不同时,节省内存。
const关键字
可以保护被修饰的东西,防止意外修改,增强程序的健壮性
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
inline关键字
关键字inline 必须与函数定义体放在一起
只适合函数体内代码简单的函数数使用
- static inline
inline函数不能在两个不同的文件中出现,一个.h不能被两个不同的文件包含,一个inline在不同的.C里面生成了不同的实例
c文件中的仅inline函数是不内联的,因为没有static,编译会认为它是全局的,因此像普通函数一样编译了。
加入static,这样内部调用函数时,会内联,而外部调用该函数时,则不会内联。
static关键字
修饰变量:
修饰全局变量:仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量
修饰局部变量:普通局部变量存储在栈空间,编译器不会初始化;修饰之后的局部变量,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变
修饰函数:
静态函数只能在声明它的文件中可见,其他文件不能引用该函数
volatile关键字
每次读取数据,必须在内存上取,而不是使用保存在寄存器或者cache里的备份(直接取内存原始地址)
没有声明的话,对一个变量进行多次赋值,没有生成之间汇编代码,直接取最后的值寻址赋值给该变量;声明之后每个变量的赋值都形成了汇编代码,没有被优化。
易变的
多线程的程序,共同访问内存时多个程序可以操纵这个变量;和外部设备的状态对应,通过驱动程序和中断改变该变量的值,程序并不知道
用途:
-
并行设备的硬件寄存器(状态寄存器),每次对它的读写都可能有不同意义
-
一个中断服务子程序中会访问到的非自动变量;当变量在触发某中断程序中修改,对于编译器,主函数里没有修改这个变量,可能只执行一次从内存到某寄存器的读操作,而后每次只会从该寄存器中读取变量副本,使得中断程序的操作做了跟没做一样。
-
多线程任务中的共享变量。
一个参数可以既是const又是volatile——只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
一个volatile的指针——当一个中断服务子程序修改一个指向一个主函数的指针时
宏定义
用途
-
防止头文件被重复包含
-
重新定义一些类型
-
得到指定地址
-
得到指定地址上的一个字节
#define MEM_B( x ) ( *( (byte *) (x) ) )
#define MEM_W( x ) ( *( (word *) (x) ) )
Translation unit
在函数块外部名字声明(函数和变量)若只能在一个已知的translation unit是可见的,称为内部链接。他们对于linker(连接器)是不可见。若声明的函数或变量对于其他的目标文件是看见的,则称为具有外部连接,对于linker是可见的。
编译的基本单元是.c文件