CSAPP:理解指针

C语言中最难理解也是最容易出错的的就是指针了,以前学C的时候就最害怕指针,CSAPP中3.10章节理解指针部分,非常有利于加强对指针的理解。

指针是C语言的一个重要特征 。它以一种统一方式,对不同数据结构中的元素产生引用。对于编程新手来说,指针总是会带来很多的困惑,但是基本的概念其实很简单。在此,我们指点介绍一些指针和它们映射到机器代码的关键原则。

  • 每一个指针都对应到一个类型。这个类型表明指针指向哪一类对象。以下面的指针声明为例:
int *ip;
char **cpp;

变量ip是一个指向int类型对象的指针,而cpp指向的对象自身就是一个指向char类型对象的指针。通常如果对象类型为T,那么指针的类型为 *T。特殊的 void * 类型代表通用指针。比如说,用 malloc 函数返回的就是一个通用指针,然后通过显示强制类型转换,或者赋值操作那样的隐式类型转换,将它转换成一个有类型的指针。指针类型不是机器代码的一部分;它们是C语言提供的一种抽象,帮助程序员避免寻址错误。

  • 每个指针都有一个值。这个值是某个指定类型对象的地址,特殊的NULL(0)值表示该指针没有指向任何地方。
  • 指针用&运算符创建。这个运算符可以应用到任何的lvalue类型的C表达式上,意味着它是可以出现赋值语句左边的表达式。这样的例子包括变量以及结构、联合和数组的元素。我们已经看到,因为leal指令设计用来计算存储器引用的地址的,&运算符的机器代码实现常常用这条指令来计算表达式的值。
  • 运算符*用于指针见的间接引用。其结果是一个值,它的类型与该指针的类型相关。间接引用是通过存储器引用来实现的,要么是存储到一个指定的地址,要是从指定的地址读取。
  • 数组与指针紧密相关。一个数组的名字可以像一个指针变量一样引用(但是不能修改)。数组引用(例如 a[3])与指针运算和间接引用(例如 *(a+3) )有一样的效果。数组引用和指针运算都需要用对象大小对偏移量进行伸缩。当我们写表达式 p+i ,得到的地址为 p+L*i ,其中L是与p关联的数据类型的大小。
  • 将指针从一种类型强制转换到另一种类型,只改变他们的类型,而不改变他们的值。强制类型转换的一个效果是改变指针运算的伸缩。来看一个例子:如果p是一个 char *类型的指针,它的值为 p ,那么表达式 (int *)p+7 计算为 p+28, 而 (int *)(p+7)计算为p+7。(强制类型转换的优先级高于加法)
  • 指针也可以指向函数。这提供了一个很强大的存储和向代码传递引用的功能。这些引用可以被程序的某个其他部分调用。例如我们有一个函数,用下面这个原型定义:
int fun(int x, int *p)

然后可以声明一个指针 fp ,将它赋值为这个函数,其代码为:

int (*fp)(int, int *);
fp = fun;

然后用下面的指针来调用这个函数:

int y = 1;
int result = fp(3, &y);

函数指针的值是该函数机器代码表示中第一条指令的地址。

理解函数指针

函数指针声明的语法对程序员新手来说特别难以理解,对于以下的声明:

int (*f)(int *)

要从 f 往外读,因此我们看到一个 (*f) 表明,f是一个指针,而 (*f) (int *) 表明f是一个指向函数的指针,这个函数以一个 int * 作为参数。左后我们知道,它是指向以 int * 为参数并返回 int 的函数的指针。

*f 两边的括号是必须的,否则声明成了 int *f(int *),会被理解为 (int *) f(int *) 会被解释称一个函数原型,其返回值为 int *

本文就是CSAPP 3.10 章节的抄录。