刚开始学习C语言的时候,感觉最难理解的就是指针,什么指针变量,变量指针,指向指针的变量,指向变量的指针?一堆概念,搞得人云里雾里的,今天不讨论这些概念的问题,从最底层来分析C语言中为什么要使用指针,指针存在的意义又是什么呢?
首先从一个简单的例子来看,写一段代码来交换x、y的值。
void main( void )
{
u8 x = 10, y = 20;
u8 temp;
__asm( "sim" ); //禁止中断
SysClkInit();
delay_init( 16 );
LED_GPIO_Init();
Uart1_IO_Init();
Uart1_Init( 9600 );
ADC_GPIO_Init();
__asm( "rim" ); //开启中断
while( 1 )
{
LED = ~LED;
printf( "x = %d,y = %d\r\n", x, y );
temp = x;
x = y;
y = temp;
printf( "---> x = %d,y = %d \r\n\r\n\r\n", x, y );
delay_ms( 200 );
}
}
在STM8单片机中,交换x、y的值,并将值打印出来,打印结果如下:
通过第三个变量temp很轻松的就将x、y的值交换了。
但是为了程序的美观性,不想再主程序中写这么多的代码,于是决定用一个函数在外部来实现这个功能。
void swap( u8 x, u8 y )
{
u8 temp;
temp = x;
x = y;
y = temp;
}
void main( void )
{
u8 x = 10, y = 20;
__asm( "sim" ); //禁止中断
SysClkInit();
delay_init( 16 );
LED_GPIO_Init();
Uart1_IO_Init();
Uart1_Init( 9600 );
ADC_GPIO_Init();
__asm( "rim" ); //开启中断
while( 1 )
{
LED = ~LED;
printf( "x = %d,y = %d\r\n", x, y );
sawp( x, y );
printf( "---> x = %d,y = %d \r\n\r\n\r\n", x, y );
delay_ms( 200 );
}
}
在主函数外面定义一个函数,专门用来交换x,y的值。程序运行结果如下:
此时发现x和y的值并没有交换,这是怎么回事?那么在交换函数内部,也将x和y的值打印出来。修改代码如下:
void swap( u8 x, u8 y )
{
u8 temp;
printf( "in: x = %d,y = %d\r\n", x, y );
temp = x;
x = y;
y = temp;
printf( "in: ---> x = %d,y = %d \r\n\r\n\r\n", x, y );
}
打印结果如下:
可以看到在交换函数内部,x和y的值已经交换了,但是在函数外部,x和y的值并没有交换。这是为什么呢?单步调试直接观察x和y在单片机内部分存储情况。
进入主程序之后,首先观察主函数内的x和y在内存中的存储情况,x值为10,也就是16进制的0x0A,在内存中0x000009的位置存储,y的值为20,也就是16进制的0x14,在内存中0x00000B,位置存储。
接下里进入到swap函数中。为了方便观察,将swap函数内部的x和y替换成了m和n。
可以看出在进入子函数之后,m和n的地址和值,都和main函数中的x和y一样。接下来交换m和n的值。
交换完成后发现,内存中0x000009位置的值和0x00000B位置的值亚发生了交换。然后退出子函数,返回到main函数中。
这时候奇怪的事情发生了,刚才内存中交换的值又变回来了?这是为什么呢?在这里就不得不说在C语言中关于局部变量的问题,局部变量在C语言中是没有固定的存储位置的,它是由系统的堆栈统一来管理的,当进入函数内部时,系统就会给这些局部变量临时分配一个存储空间存储它的值,当程序要离开函数时,系统就会将局部变量的值保存在堆栈中,然后变量的存储位置就被释放了。当程序进入另一个函数中时,又会给这个函数内部局部变量分配空间,这时候可能就会出现两个函数中的局部变量都使用了同一个内存空间。此时这个内存空间的值改变后,并不影响上一个函数中局部变量的值,因为上一个函数中的局部变量值此时在堆栈中存放。当程序要离开当前的这个函数时,又会将当前的局部变量值保存在堆栈中。回到上一个函数后,又将堆栈中存储的值恢复给变量,此时变量的地址又是临时申请的,可能此刻申请的地址值还是和上一次一样。但是并不代表这个地址就永远属于这个局部变量。
这个就很类似于超市中的储物柜,你要进去超市买东西,先将东西存到一个柜子中,买完东西后,又将东西存储物柜取了出来。然后隔了几个小时,又要去这个超市买东西,又需要将东西存起来,但是此时存储的柜子编号还是上上一次存储时一样。但是这并不能代表这个柜子的编号就是专属于你的了。它只是储物柜临时分配给你的空间,当你取出东西后这个空间就会被系统收回,如果你下一次还需要用,系统又会自动给你分配,但是这两次分配的刚好是一个编号而已。
那么要如何解决这种变量交换的问题呢?有两种方法,第一种就是直接将要交换的这个两个变量定义为全局变量,让它在程序运行的过程中独占一个地址空间,这样就不会有其他变量来使用这个位置了。但是这样的话就会比较浪费内存空间,只使用了一次,但是却要永久的占用。第二种方法就是直接使用指针。
下面将代码改成使用指针的方式。
void swap( u8 *m, u8 *n )
{
u8 temp;
temp = *m;
*m = *n;
*n = temp;
}
void main( void )
{
u8 x = 10, y = 20;
__asm( "sim" ); //禁止中断
SysClkInit();
delay_init( 16 );
LED_GPIO_Init();
Uart1_IO_Init();
Uart1_Init( 9600 );
ADC_GPIO_Init();
__asm( "rim" ); //开启中断
while( 1 )
{
LED = ~LED;
printf( "x = %d,y = %d\r\n", x, y );
sawp( &x, &y );
printf( "---> x = %d,y = %d \r\n\r\n\r\n", x, y );
delay_ms( 200 );
}
}
在向swap函数传递参数的时候需要使用&符号。打印输出结果
此时x和y的值已经成功交换了。现在也将子函数内部的交换情况打印一下。
void swap( u8 *m, u8 *n )
{
u8 temp;
printf( "in: m = %d,n = %d\r\n", m, n );
temp = *m;
*m = *n;
*n = temp;
printf( "in: ---> m = %d,n = %d \r\n\r\n\r\n", m, n );
}
从输出的结果来看,怎么m和n的值一个时1021,一个时1020.这个值是什么呢?直接单步调试看。
此时main函数中x的地址变成了0x0003FD,y的地址变成了0x0003FC.接着进入子函数。
这时可以看出m的值为0x03FD,n的值为0x03FC.*m的值为0x0A也是就10,*0x14也就是20.
接下来开始交换值。
可以看出m和n的值没变,但是m和n的值交换了。接下来回到主函数中。
此时主函数中x和y的值也交换了。
那刚才串口打印出来的1021和1020是什么呢?1021的十六进制是0x03FD,1020的十六进制是0x03FC.也就是说刚才打印的m的值和x的地址一样,n的值和y的地址一样。
那为什么m和n会变成x和y地址,*m 和 *n又会变成 x 和 y 的值。这里就要说指针的本质了。在存储器内部,它是不认识什么变量和指针的,对于存储空间来说,它只有地址和值。也就是说在什么地址处,存储什么值。对于普通的变量来说,变量的名称就会被编译器编译成地址,也就是说x和y就是它自己地址的别名。x和y的值就是地址中对应的值。
当操作普通变量x和y的时候,系统默认操作的就是它的值。而指针刚好和它相反,指针默认是把地址作为它的值,当操作指针的时候,默认操作的就是地址。为了将普通变量和指针进行区分,那么如果要使用指针的时候,就需要给它贴一个标签,告诉系统,我这个是特殊变量,它是直接操作地址的,不是操作值的。
所以在定义指针的时候给变量前面加一个*号,就表示告诉系统,我这个是特殊的。比如定义了一个int *m。就表示告诉系统,当我默认操作m的时候,你就给我它的地址,而不要给我它的值。当需要取值的时候就需要添加上标签 *m,告诉系统我现在要取的值,不是地址,这是特殊情况,不要把默认的地址给我。
当要将普通变量传递给指针时,因为直接操作变量默认就是普通变量的值,而指针存贮的是地址,所以当普通变量和指针传递数据的时候,也要给普通变量添加一个标签 & ,这个符号就告诉系统,我现在不要默认的值,我要的是特殊情况的地址。
所以将x和y传递给指针的时候,前面要加&符号。
swap( &x, &y );对应的就是 swap( u8 *m, u8 *n );
刚才上面不是说了吗,指针默认的是地址,加上*号就是值了。那么这样直接传递过去不就是相当于 *m = &x 了吗?
由于这个子函数是定义和传值在一起操作了,省略了一步,标准操作应该是。
int *m;
m=&x;
先定义一个指针,然后将普通变量的特殊情况,也就是取普通变量的地址,传递给指针的默认情况。这样m的默认情况下就代表的值x的地址,而x的值就是*m。
如果定义变量和给变量赋值在一条语句时,上面的代码就可以简写为
int *m = &x;
所以上面的函数 swap( &x, &y ); 给指针传递值的时候,指针的定义和赋值是在一条语句完成的, swap( u8 *m, u8 *n ); 这是一种常用的简写形式。
在swap函数内部操作 *m 也就相当于直接操作的是x,操作 *n 就是直接操作的y,所以交换 *m 和 *n的值,就相当于交换x和y的值。
在没有使用指针时通过子函数交换,此时传递的是变量的值,相当于把变量的值拷贝了一份,给了子程序。
而有了指针之后,相当于将变量的地址直接给了子函数。相当于给变量x和y又起了一个别名。操作别名的时候,也就相当于直接操作的是x。
由此可见,指针只是为了方便编写程序而设置的一种给变量起别名的方法,也就是相当于给自己柜子配了了一把钥匙。只要别人有这个钥匙,也就可以打开你的柜子。所以指针在使用的时候会有危险性。
如果系统中有关键数据,那么如果这个数据用指针传递给了外部函数,那么当外部函数修改数据的时候,系统就会存在风险。有可能外部函数修改了一个值,而这个值是非法的,自己的系统就奔溃了。所以在使用指针的时候一定要注意安全性问题。
通过上面的例子,相信对指针就有了更深层次的理解了。它是为了方便操作变量而设置的特殊情况,是被贴了标签的变量。至于什么指针变量,变量指针,那都是起的名字而已,搞不清楚这些概念也不用去纠结,只要在使用的时候,知道如何使用就行了。