C语言指针的本质理解


一、先别引入指针概念

C语言中经常会遇到int*、char等类型的数据,首先我们先不提指针这个概念,把这种带有号(可多个)的数据类型当成是一种全新的数据类型,如int**、doule*******、char***等,这些都是一些新的数据类型。

有了数据类型就一定会涉及到数据类型的占用大小,操作,使用场景等问题。


一、带*类型数据类型的占用大小

对于带*类型的数据类型,不管带了几个*,在32位操作系统中都是占用4字节。

二、带*类型数据类型的加减乘除操作

#include<stdio.h>
int main()
{
	char* a = (char*)1;
	int* b = (int*)2;
	double* c = (double*)3;
	a++;
	b++;
	c = c + 3;
	printf("%d\n", a);//输出2
	printf("%d\n", b);//输出6
	printf("%d\n", c);//输出27
	return 0;
}

以上面的代码为例,对int*类型的加1,其实就是加int*去掉一个后数据类型的宽度,这里int是4字节,所以2+4=6,b的结果为6。同理c=c+3,其实就是3+38(8是double的字节宽度)
综上,我们总结一下加减运算,就是将带*的数据类型去掉一个*后看其长度,然后再进行运算,大家可以试试下面这个是多少呢?

#include<stdio.h>
int main()
{
	int*** a = (int***)1;
	a = a + 2;//去掉一个*后,int**的空间大小是4字节
	printf("%d\n", a);//输出1+2*4=9
	return 0;
}

上面说的都是加分,我们再试试减法

#include<stdio.h>
int main()
{
	int*** a = (int***)200;
	int*** b = (int***)100;

	b =(int***)(a - b);
	printf("%d\n", b);//输出(200-100)/4
	return 0;
}

可以看出减法也是一样的原理,还要把减去的值除以去掉一个*后的数据宽度。
关于乘除,不好意思,对于带*类型的数据不能进行乘除操作。

三、&运算符与*运算符的关系

#include<stdio.h>
int main()
{
	char** a = (char**)1;
	char*** b=&a;
}

上面这个示例就已经讲得很清楚了,即:&(带N个*的数据类型)得到的数据类型就是带N+1个*的数据类型。(N大于等于0)

#include<stdio.h>
int main()
{
	char** a = (char**)1;
	char* b=*a;
}

同样,上面这个示例也讲得很清楚,即*(带N个*的数据类型)得到的数据类型就是带N-1个*的数据类型,(N不少于1)

四、关于带*类型的应用—引入指针概念

带*类型,如char*等,我们常把这个类型叫做“指向char类型数据的指针”。其实我个人觉得这种带有一个*的还好解释,若给你一个如char*******的数据类型,让你用指针的概念描述一下就成了“指向char******类型数据的指针”,其实这里面的指向关系就有点一层一层嵌套的感觉【虽然实际中几乎不会这么用】

其实指针就是你指向那个数据类型在内存空间中的地址,在32位操作系统下,4GB的线性地址空间用32bit,即4字节来编号,所以指针本身宽度为4字节。


五、关于几个带*的数据类型理解-指针数组与数组指针

(1)指针数组

#include<stdio.h>
int main()
{
	int* arr[4]= {(int*)1,(int*)2,(int*)3,(int*)4};//指针数组
	printf("%d\n", sizeof(arr));//输出16
	printf("%d\n", sizeof(arr[0]));//输出4
	
	printf("%d\n", arr);//arr表示第一个元素的地址,arr的数据类型为int*[5]
	printf("%d\n", *arr);//*arr表示第一个元素值,*arr的数据类型为int*
	printf("%d\n", **arr);//因为第一个元素是一个int*类型,因此**arr表示第一个元素值地址处的值(运行时会引发异常,因为1地址用户无法访问),**arr的数据类型为int
	return 0;
}

上面的是一个指针数组,指针数组里面存了4个int类型的数据,而arr的值是数组中第一个元素的地址,但arr的数据类型为int[4] (这里说是int*[4]可能有点奇怪,是我个人为了区分而命名的,是为了区分int*,因为如果这里arr的数组类型是int*的话,那sizeof(arr)应该返回4,而不是16了)

从反汇编角度,我们看一下,用VS2019调试,在第三个printf处加一个断点,然后查看反汇编窗口。
反汇编1
反汇编2可以看出,arr其实就是[ebp-18],即数组第一个元素的地址,*arr就是第一个元素的值,**arr就是把第一个元素的值作为地址,取这个地址处的值。

(2)数组指针

#include<stdio.h>
int main()
{
	int(*px)[5];//声明一个数组指针
	px = (int(*)[5])4;//给数组指针赋值

	int a[5] = { 0,1,2,3,4 };
	px = &a;//&a的类型是int(*)[5]
	printf("%d\n", px);//px的类型是int(*)[5],px本身的值就是数组第一个元素的首地址
	printf("%d\n", a);//a数组第一个元素的地址,
	printf("%d\n", *px);//a数组第一个元素的地址,*px也是数据第一个元素的首地址
	printf("%d\n", **px);//a数组第一个元素
}

上面是一个数组指针的,px的数据类型是int(*)[5],说到这里大家可能觉得比较乱,指针数组的类型是int*[5],这俩有什么区别?其实我个人认为这里要把握住本质,关于int*[5]与int(*)[5]只是我这里用于区分的文字表述。

下面还是从反汇编的角度,理解一下。

在这里插入图片描述在这里插入图片描述
可以看出px作为main函数中定义的数组指针,其占用的空间大小就是4字节,这个例子里面其内容就是数组的第一个元素的首地址。

这里不禁产生疑惑,比如数组int a[10],a本身就可以表示第一个元素的首地址,为什么还要再搞一个数组指针,我认为一是在传递参数的时候可以用,而是a本身是常量,不能加减运算,但是数组指针是可以加减的,如下:

#include<stdio.h>
int main()
{
	int(*px)[5];//声明一个数组指针
	px = (int(*)[5])4;//给数组指针赋值
	px=px+1;
	printf("%d\n", px);//输出24,为4+1*5*4。这里5是数组元素数目,4是每一个元素的宽度(这里int为4字节)
	return 0;
}

六、关于几个带*的数据类型理解-指针函数与函数指针

有了上面关于指针数组与数组指针的概念,这里理解函数指针与指针函数应该容易一些。

(1)函数指针

#include<stdio.h>
int test(int a, int b) {
	return a+b;
}
int main()
{
	int(*px)(int, int)=test;//px为函数指针
	printf("%d\n", test);//test函数第一行代码地址
	printf("%d\n", px);//test函数第一行代码地址
	printf("%d\n", *px);//test函数第一行代码地址
	printf("%d\n", **px);//test函数第一行代码地址
	printf("%d\n", *****px);//test函数第一行代码地址
	printf("%d\n", px(1, 2));//调用函数
	return 0;
}

上面的px为函数指针,其数据类型为int(*)(int,int)类型,用反汇编看一下。
反汇编可以看出来px的值就是函数的首地址,并且无论是*px、**px等,取到的都是px的值。

(2)指针函数

指针函数,就是一个普通函数,只不过返回值的类型是指针类型,即带有*的数据类型。

#include<stdio.h>
int* test(int a) {
	return &a;
}
int main()
{
	int b = 3;
	printf("%p\n", test(b));
	return 0;
}

七、总结

经过上面的分析,基本上对这种带有*的类型变量有了一定的认识,最重要的是从汇编或者更加底层的角度理解其含义到底是什么,而不是单纯去记忆谁指向谁的地址。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值