分享

周立功:安全有效地使用C掌握指针—变量的访问

 甲基丁酸 2017-07-09

周立功:安全有效地使用C掌握指针—变量的访问

2017-07-05 电子工程师时间
电子工程师时间

ee-technotes

分享电子工程相关技术文档:设计技巧,应用笔记,参考设计,设计方案


经周立功教授授权,特对本书内容进行连载。第一章为程序设计基础,本文为1.3.2变量的访问。

周立功

1.   间接访问

指针变量与普通变量没有任何区别,由于通过变量名即可获取变量的值,因此指针变量的值就是它存储的内存地址。如果想要获取内存地址中存储的值,就必须使用特殊的语法“*”。假设0x22FF70存储单元中保存的是ptr的值0x22FF74,则通过ptr就可以找到iNum的值0x64,详见图1.7。每当指针的指向改变时,便绘制新的箭头;每当变量的值发生改变时,更新它的值。通过这些操作,即使再复杂的系统,也能够理解了。显然指针变量对所指向的变量的访问,自然也就成了对变量的“间接访问”。即:

* 指针变量

即间接引用操作符(*)返回指针变量指向的内容,通常又将“*”称为解引用指针。当指针变量为空指针时,则“*指针变量”毫无意义。


如果ptr指向iNum,&iNum表示变量iNum的地址。使用*ptr等同于iNum,表示存储在&iNum地址上的值。即除了可以通过*ptr输出iNum的值,还可以赋值。比如:

printf('%p\n', *ptr);

注意,%p与%x的区别在于,%p会将数字显示为十六进制大写。


2.  直接访问

定义变量的目的是通过“变量名”引用“变量的值”,由于程序经过编译后已经将“变量名”转换为“变量的地址”,因此对变量的取值都是通过地址进行的,则直接按变量名取值的访问方式就是“直接访问”。比如:

iNum  = 0x64;                     //对变量iNum的直接访问

*ptr   = 0x80;              //对变量iNum的间接访问

显然,无论是采用间接访问还是直接访问,这2个语句作用是相同的。由于指针变量也是变量,因此在程序中同样也可以直接使用,而不必通过间接访问的方法。比如:

int   *ptr1,*ptr2;

ptr1 =ptr2;

即这样ptr1与ptr2指向同一个对象。另,也可以将ptr2指向的值复制到ptr1中。比如:

 *ptr1 =*ptr2;                     //数值赋值


综上所述,指针存储的是地址,因此直接使用“裸”指针得到的是地址。要获取或调整存储在该地址中的值,必须额外添加的“*”。而变量存储的是数据值,因此直接使用变量得到的是数据值。要获取变量的地址,必须额外添加“&”。


3.   强制类型转换

实际上指针(存储单元的地址)也是无符号整数,因此指针可以与整型变量的类型互相转换。比如:

unsigned int a = 5, b;

b = (unsigned int)&a;

由于&a的类型为unsigned int*,因此需要强制转换&a为unsigned int,只有这样才能将变量a的地址保存在b中,那么将如何通过b取得a的值呢?必须先将b强制转换为指针,才能读取a的值,详见程序清单 1.9。

程序清单 1.9指针类变量类型转换范例程序

1       #include

2       int main(int argc, char *argv[])

3       {

4                 unsigned int a = 5, b;

5

6                 printf('&a=%x\n%&b=%x\n',&a, &b);

7                 b = (unsigned int)&a;               //将&a转换为unsigned int型整数

8                 printf('(unsigned int*)&b = %x\n', (unsigned int *)&b);  //输出b的地址

9               printf('(unsignedint *)b = %x\n', (unsigned int *)b);        //输出b的值,即a的地址

10             printf('*(unsignedint *)b = %x\n', *(unsigned int *)b);    //将b强制转换为指针再取得a的值

11               return0;

12     }

在C语言中也常常遇到这样的情况,如果要将数据0x05存入绝对地址0x22FF74中,那么下面这条语句是否正确呢?

*0x22FF74= 0x05;

这是非法的。因为0x22FF74是int型整数,而*间接访问操作只能用于指针表达式。


既然通过指针可以向其指向的内存地址写入数据,那么这里的内存地址0x22FF74就是指针,因此必须先通过强制转换将0x22FF74转换为指向“unsigned int *”类型。然后通过“*”向0x22FF74内存写入数据,“*(unsigned int *)0x22FF74”表示读取0x22FF74地址里面的内容,其内容就是保存在地址为0x22FF74存储器内的数据。比如:


*(unsigned int *)0x22FF74 = 0x05;

printf('*(unsigned int *)0x22FF74 = 0x%x\n', *(unsigned int*)0x22FF74);

上述方法不是用于访问某个变量,而是通过地址访问内存中某个特定的位置,比如,系统通过与输入输出设备控制器之间的通信,以及与I/O的输入输出操作来获得相应的结果。事实上,计算机与设备控制器的通信就是通过在某个特定内存地址读取和写入值来实现的,表面上看起来这些操作访问的是内存,其实际上访问的是设备控制器接口。


在强制类型转换运算符中和类型作为sizeof的操作数时,虽然初学者对一些复杂的类型名感到难以理解,但实际上却有规律可循。其声明规则为在标识符(变量名或函数名)的声明中,将标识符取出后,剩下的部分自然就是类型名。比如:

void (*func)();

其类型名为“void (*)()”。void (*)()是将void (*func)()的标识符func去掉后形成的,所以该类型名被解释为指向返回void函数的指针,func是指向返回void函数的指针。


同理,double*[3]是将double *p[3]的标识符p去掉后形成的,所以该类型名被解释为指向double的指针数组,p是指向double的数组(元素3个)的指针。


在指针的定义中,void *表示通用指针的类型,它可以作为两个具有特定类型指针之间相互转换的桥梁。注意,“从C99版本开始,将void *类型指针赋值给其它类型指针时,不再需要进行强制类型转换”。比如:

int *pInt,;

void *pVoid;

pInt =pVoid;

当函数可以接受任何类型的指针时,则将其声明为void *类型指针。比如:

void*memcpy(void *dst, const void *s2, size_t n);

其作用是从s2复制n个字符到dst,并返回dst的值,任何类型的指针都可以传入memcpy()函数中。如果void作为函数的返回类型,则表示不返回任何值。如果void位于参数列表中,则表示没有参数。size_t是C标准库中预定义的类型,专门用于保存变量的大小。


如果要开发可移植性高的程序,应该避免对指针进行强制类型转换,同时不要用强制类型转换掩盖编译器提示的警告。


综上所述,指针存储的是地址,直接使用指针得到是地址;要获得或调整存储在该地址中的值,必须添加额外的“*”。变量存储的是数据值,因此直接使用变量得到的是数据值;而要获得变量的地址,就必须额外添加“&”。注意,sizeof操作符可以用在void指针上,无法将这个操作符用在void上,比如:

size_t size = sizeof(void *);

size_t size = sizeof(void);

size_t类型表示C中任何对象所能达到的最大长度,它是无符号整数。size_t用作sizeof操作符的返回值类型,同时也是很多函数参数类型,比如,malloc和strlen。

注意,打印size_t类型的值时,由于它是无符号整数,因此不能选错格式,通常推荐的格式为%zu或%u或%lu。


阅读

''

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多