C语言指针分析C语言复杂指针
Posted im18620660608
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言指针分析C语言复杂指针相关的知识,希望对你有一定的参考价值。
前言
指针是一种保存变量地址的变量,简单指针容易分析,但是当指针稍微复杂,就很容易无从下手,比如:
char *(*(**foo[][8])())[];
1
下面将会由浅入深的探究指针
普通指针
#include <cstdio>
#include <iostream>
using namespace std;
int main()
int a = 1;
int * pa = &a;
int ** ppa = &pa;
printf("%p %d %p %p %d %p %p %d ", &a, a, &pa, pa, *pa, ppa, *ppa, **ppa);
return 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
输出如下:
0000004d389ffa04 1 0000004d389ff9f8 0000004d389ffa04 1 0000004d389ff9f8 0000004d389ffa04 1
1
几点解释:
pa是指向int类型的指针,它的值是变量a的地址
ppa是指向pa的指针(二维指针)
占位符 %p 对应指针变量的值
&p指的是指针p在内存中的地址
运算符结合顺序
结合的优先级由大到小:() > [] > *
著名的右左法则:先看最里面的括号,再看右边,再看左边。每当你遇到括号,你应该改变你的阅读方向。解析完括号内的所有内容后,跳出括号。重复此过程,直到解决整个语句。
Right-Left Rule: First look at the innermost parenthesis, then look to the right, and then to the left. Whenever you encounter parentheses, you should switch your reading direction. Once you have parsed everything inside the parentheses, jump out of the parentheses. Repeat this process until the entire statement is resolved.
参考:https://blog.karatos.in/a?ID=00250-f7e0610c-459c-431a-a3ab-d9a50a7d5598
int* f();
由上述结合顺序得,f先和()结合
f 是一个函数 ,它的返回值是int* 类型,也即是指向int 类型的指针
int (* f)();
由于()的存在,f先和*结合,再和右侧的()结合
f是一个函数指针,它指向的函数的返回值是int类型
程序中的每个函数都位于内存中的某个位置,所以存在指向那个位置的指针
int* (* f)();
类似上述, f是一个函数指针,它指向的函数的返回值是指向int类型的指针
int *f[]
f是一个数组,该数组每个元素都是指向int类型的指针
int (*f)[10]
f是一个指针
指向一个int类型的数组,该数组有十个int类型的值
int ** f
可以参考【C语言指针】char* argv[] 、char **argv、命令行传参
int (*f[])();
f是一个数组
数组元素的类型是函数指针
函数的返回值是一个整型值
int *(*f[])();
f是一个数组
数组元素的类型是函数指针
函数的返回值是指向 int 类型的指针
int (*(*(*pfunc)(int *))[5])(int *)
pfunc是一个函数指针
函数的参数是指向int类型的指针
函数的返回值是一个指针,指向一个具有五个元素的数组,该数组的每一个元素都是一个指针
此指针指向一个函数,该函数的参数为int * ,返回类型为int
char *(*(**foo [][8])())[];
foo是一个二维数组,数组的每个元素都是指针,指向函数,函数返回一个指针,指向一个数组,该数组每个元素都是指向char类型的指针
cdecl 辅助分析
通过包管理器安装cdecl程序
$ sudo apt install cdecl
$ cdecl
cdecl> explain char *(*(**foo[][8])())[]
declare foo as array of array 8 of pointer to pointer to function returning pointer to array of pointer to char
1
2
3
4
5
6
7
————————————————
版权声明:本文为CSDN博主「郭同学如是说」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43864567/article/details/122778415
嵌入式编程中,如何使用复杂指针?
1.说明
在C语言编程中,指针是最容易出错的地方,尤其是在很多指针同时出现的时候,看的眼花缭乱的,本文从嵌入式中常用的复杂角度进行分析,彻底搞清楚C语言中的容易弄错的指针使用问题。
2.函数指针与指针函数
在C语言中,函数是有他的地址,同理,函数有也有他的地址,如果如果我们把函数的地址赋值给函数指针,那么我们就可以间接的通过函数指针调用函数地址了。
函数指针的定义如下:
数据类型 (*fun)(参数列表);
由于()的优先级高于*。
指针函数的定义如下:
数据类型 * fun(参数列表);
其返回值为数据类型 *。
实例:通过函数指针调用函数指针
第一步:定义函数指针
int* (*pfun)(int*,int*);
这里调用了一个数据类型为int *的函数指针,其中两个参数为两个int*。
第二步:定义指针函数
int* fun(int*, int*);
这里函数的返回值是int *。
第三步:实现函数指针
int* fun(int* a, int* b)
{
int* ret = 0;
(*ret) = (*a) + (*b);
return ret;
}
第四步:把函数的地址赋值给函数指针
int main(int argc, char** argv)
{
int a = 3, b = 2,c = 0;
pfun = fun;
c = *((*pfun)(&a,&b));
rt_kprintf("c is %d ", c);
return0;
}
其中最关键的是赋值和调用,赋值时采用的是pfun = fun;,而间接调用函数时采用的是*((*pfun)(&a,&b));。
3.const修饰的指针问题
首先看一下下面的语句:
constint *p;
intconst *q;
int *const r;
constint * const x;
在进行C语言编程时,经常会用const来修饰一个变量,这样阻止一个变量被改变。
前面两个const int *p;与int const *q;表达的含义一样,p和q都被申明为const int类型的指针。也就是说,在程序中,不可以修改*p和*q的值。为了阅读便利,通常采用const在前面的方式。
int a = 3, b = 2;
constint *p = &a;
p = &b;
*p = 5;//err
rt_kprintf("*p is %d ", *p);
其中*p的值不可以被修改,但是p的值是可以被修改的。
对于int *const r;
int *const r = &a;
r = &b;//err
*r = 6;
rt_kprintf("r is %d ",*r);
其中r=&b是错误的。
结合上述操作,得到const int * const x = &a;。这个是需要在使用的时候进行赋值,而且不可以修改,也就是
x = &b;//err
*x = 6;//err
这些操作都是错误的。
4.函数指针直接跳转的问题
我们在真实的项目开发过程中,可能需要直接跳转到函数的某个地址去指针。
void (*function_p)(void); //定义函数指针function_p,无返回值,无参数
function_p = my_func; //函数指针指向function函数
(*function_p)(); //采用函数指针运行函数
这个等同于直接调用my_func函数,那么这个有什么意义呢?
其实这样提出了一个思路,就是可以根据函数的地址,跳转到函数中。比如我们在bootloader中,当把二进制文件加载到内存中后,如何去执行这个kernel程序呢?也就是实现一个bootloader到kernel的跳转。
((void(*)())0x80000)();
这里就是说0x80000处的地址是函数类型,并且没有返回值。当我们的kernel地址为0x80000时程序跳转过去,不再返回。这就是一个比较经典的例子。
5.回调函数
回调函数可以说是C语言对函数指针的高级应用。简而言之,回调函数就是通过函数指针调用的函数。也就是说我们把函数的指针通过函数参数传递给函数使用,这时我们就可以认为被调用的函数是回调函数。
我们来分析一个rt-thread中具体例子,来分析回调函数的妙用。
用过rt-thread操作系统的人都知道,rt-thread采用了设备驱动框架,也就是开发的过程中可以采用虚拟文件系统的操作对驱动设备进行操作。看一下rt_device结构体内容。
/**
* Device structure
*/
struct rt_device
{
struct rt_object parent;/**< inherit from rt_object */
enum rt_device_class_type type; /**< device type */
rt_uint16_t flag; /**< device flag */
rt_uint16_t open_flag; /**< device open flag */
rt_uint8_t ref_count; /**< reference count */
rt_uint8_t device_id; /**< 0 - 255 */
/* device call back */
rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
#ifdef RT_USING_DEVICE_OPS
conststruct rt_device_ops *ops;
#else
/* common device interface */
rt_err_t (*init) (rt_device_t dev);
rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
rt_err_t (*close) (rt_device_t dev);
rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_t (*write) (rt_device_t dev, rt_off_t pos, constvoid *buffer, rt_size_t size);
rt_err_t (*control)(rt_device_t dev, int cmd, void *args);
#endif
#if defined(RT_USING_POSIX)
conststruct dfs_file_ops *fops;
struct rt_wqueue wait_queue;
#endif
void *user_data; /**< device private data */
};
其中我们重点分析下面回调函数的接口。
rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
第一个函数就是说底层设备接收到数据时,可以调用这个回调函数,上层应用实现这个接口即可。
第二个接口也是底层接口调用上层应用层接口的例子。
根据rt-thread的设备编程模型
第一步:找到设备
rt_device_find
返回一个rt_device_t类型的设备句柄。
第二步:实现rx_indicate函数
xxx_dev->rx_indicate = xxx_rx_indicate;
其中xxx_rx_indicate就是我们需要实现的函数,这里可释放信号量,告知其他线程有消息到来。
第三步:底层调用接口
dev->rx_indicate(dev,size);
有消息到来时,调用该接口,上层应用如果实现了这个接口,就会执行该函数,如果没有实现,可以判断dev->rx_indicate为空,不执行。
这样,程序实现降低耦合性调用的问题。如果我们直接调用函数,那么程序设计中耦合性太强,这个也是rt-thread利用回调函数降低耦合性的一个经典例子。
6.总结
好好理解指针使用对于C语言编程非常重要,磨刀不误砍材工,只有把基础打好,上层建筑才能稳固。也只有基础不断的积累,不断的总结,思想境界才能有所提高。程序设计不仅仅是口头功夫,也不是两三个月的快速入门能够熟练掌握,需要日积月累,不积跬步,无以至千里,不积小流,无以成江海。以此自勉。 合作微信17727800897
以上是关于C语言指针分析C语言复杂指针的主要内容,如果未能解决你的问题,请参考以下文章
C语言的指针传递,指针的指针的问题,谁能帮我分析分析这个问题?
C 语言二级指针作为输入 ( 指针数组 | 复杂指针解读 )