工作中发现很多初入职场的同事不会使用指针,或者惧怕指针。作为C语言老鸟,在这里写一些有利于初学者理解C指针的东西,希望对他们有帮助吧。
1.C语言里,指针可以说是地址变量。
比如
int n;
int *p;
n是一个变量。它的类型是int;
p也是一个变量。可以这么理解:它的类型是(int*); 官方说法是:一个指向int的指针;直白说法是:p这个地址,是按照int的形式保存数据。
本质上,p的值是一个内存地址。而(*p),也就是对p进行“地址取值”运算,指的是获取p这个内存地址所保存的内容,而怎么去取呢?换句话说,取几个字节?这就看p的类型,(int*)那就是取sizeof(int)个字节。如果你强制(*(char*)p),即对p强制转换再取值,那就取sizeof(char)个字节,得到的结果类型为char;
2.不理解指针,通常是因为不了解CPU/MCU与内存,不了解编译。也不需要很深入了解,其实知道基本概念,CPU的简单工作原理与运行流程,内存是什么在哪里,编译器做了什么,搞ARM移植的,至少知道有个叫ATPCS/APCS的东西。
3.通常我看一个新的单片机,一般最先看内核和存储器映射。下面是CM3的存储器映射
上图描述了4GB的地址空间对应的功能区域。
这个东西跟指针有什么关系?当然有关系。
以stm32为例,地址线32位,2的32次方=4G;各种寄存器,RAM,ROM等等就映射到特定的地址范围。
我们所说的内存,一般就是指上图的片上SRAM与片外RAM这两块。
我们定义指针,一般都是指向这两块地址范围内。但是指针本身可以指向内存以外的区域,比如代码区,外设区,甚至保留区(有的可能会引发异常吧,得看手册说明)等等。这些非RAM地址区域,有的可读,有的可写,有的可读写,不一而足。
比如说,
int *p;p=(int*)0x2000 0000; //就是定义一个指向地址0x2000 0000的int指针;
int a = *p; //则表示获取内存地址为0x2000 0000处的内容给变量a,一共sizeof(int)=4字节;0x2000 0000~0x2000 0003;
char c=*(char*)p; //也可以按char的形式取值,取一个字节的内容。
a=*(int*)0x00000008; //直接获取某地址处的值。
4.指针与数组。作为函数形参时,比如void f(char a[])与void f(char *a)是一样的,这个时候数组会退化为指针。但是本身数组与指针是非常不同的。数组是一片连续的内存空间,数组名代表起始地址,但数组名不仅仅作为地址。数组的首地址是不能改变的,而指针地址可以改变。
int *p;
int n=0;
int a[2]={1,1};
p=&n;//p可变
p=&a[0];//p可变
a=p;//错误,a不可以改变
另外,sizeof(p)==4 ;sizeof(a)==8;
5.多重指针。比如
int **p;
此为二重指针,即指针的指针。本质是也是指针,只不过此指针保存的内容也是做指针用。
若将int*理解为某种类型,比如写作pint ,则 int **p 可理解为 pint *p;
无论三重,四重指针,也是同样的道理,一层包一层的,并没有多复杂。
6.函数指针与函数名
定义一个函数
void fun(void){}
fun为函数名,代表函数的入口地址。函数名有些像指针,但不是指针;函数指针才是指针(有点废话了)。如下
void (*pfun)(int);
void fun(int i)
{
printf("%d\r\n",i);
}
int main(int argc, char *argv[]) {
//对函数指针赋值
pfun=&fun;
//4种调用方法
fun(1);
(*fun)(2);
(*pfun)(3);
pfun(4);
printf(" fun:%d\r\n",fun);
printf(" &fun:%d\r\n",&fun);
printf(" pfun:%d\r\n",pfun);
printf("&pfun:%d\r\n",&pfun);
return 0;
}
DEV C++运行结果是
1
2
3
4
fun:4199712
&fun:4199712
pfun:4199712
&pfun:4215760
可见函数名与函数指针可以用同样的方式调用函数。
注意有些编译器可能无法通过。