指针变量加(减)一个整数。
例如:p++,p--,p+i,p-i,p+=i,p-=i等均是指针变量加(减)一个整数。
将该指针变量的原值(是一个地址)和它指向的变量所占用的存储单元的字节数相加(减)。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p290
在C语言中,任何运算都有前提条件。脱离了前提谈运算是荒谬的。
比如,一元“*”运算,其运算对象必须是非void *类型的指针。如果对一个int类型的数据或unsigned类型的数据做一元“*”运算,连编译都无法通过(这也间接说明了指针并非是一个32位无符号整数。参见§246)。
再比如,两个int类型数据相加,其前提条件是结果必须在int类型可以表示的范围之内,否则就成了一种未定义行为(undefined behavior)。
指针运算也是如此。并非所有的指针类型数据都有加(减)法运算。C语言并没有定义void *类型指针或指向函数的指针的加减法运算。换言之,指针的加减法运算只对那些指向数据对象(Object)类型的指针才可能有意义。比如:int *类型的指针可以做加减法运算。
指向数据对象类型的指针的加减法运算并没有限制运算对象是左值(lvalue)或可修改的左值(modifiable lvalue),用一句通俗的话来说,就是任何指向数据对象类型的指针类型的表达式都可能可以与一个整数做加减法运算。比如,对于代码片段
int i=2;printf("%p\n",&i +1);
来说,其中的 &i 就不是指针变量,但&i+1这个运算完全合法,因为&i是一个int *类型的表达式,它在一定条件下可以与一个int类型的数据做加法运算。
但是,即使是指向数据对象类型的指针,也不是无条件地可以进行加减法运算。譬如,前面代码片段中的&i,就不可以做减法运算,也不可以与0或1以外的其他非负整数做减法运算。否则,没有人能保证会得到什么样的结果,因为这是一种未定义行为(undefined behavior)。
指针与一个整数类型数据做加减法运算,除了要求必须是指向数据对象的指针之外,还要求这个指针必须是指向某个数组元素的指针(单个的数据对象可以视为具有一个元素的数组,如前面的i),或指向这个数组最后一个元素之后第一个元素的指针。这话说起来有点绕,下面用一个具体的例子来说明。对于
double a[5];
来说,只有当指针的值为a、a+1、a+2、a+3、a+4、a+5时,指针与一个整数的加减法才可能有意义。其中的a+5就是指向a数组最后一个元素之后第一个元素的指针。
不仅如此,对与指针进行加减法运算的整数也有确定且苛刻的要求。这个要求就是,这些整数必须能保证运算的结果依然指向这个数组中的各个元素或者指向这个数组最后一个元素之后的第一个元素。
可以指向数组元素,可以指向数组最后一个元素之后第一个元素,但绝对不可以指向数组之前的元素(即a-1是不可以的),这个性质被有些人形象地称为“左闭右开”。
同样,两个指针的减法也是有前提条件。这个前提条件就是要求两个指针必须是指向同一数组元素或者指向数组元素最后一个元素之后的第一个元素,只有在这种情况下,两个指针的减法运算才有意义。据此,不难看出下面陈述中的错误之所在。
两个指针变量可以相减。
如果两个指针变量都指向同一数组元素,则两个指针变量值之差是两个指针之间的元素个数。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p290
这段陈述有两个主要错误。首先它把指针与指针变量混为一谈,事实上并非两个指针变量才可以相减;其次它把可以进行相减运算的指针的范围缩小了。如果一个指针指向某数组之后的第一个元素,它同样也可以进行指针减法运算。
特别值得指出的是,两个指针相减得到的值可能并非是int类型。C标准规定,两个指针相减的类型是ptrdiff_t类型,这个类型在stddef.h中定义,在许多编译器上,这个类型是long int类型。
那么,不了解指针加减法运算有什么危害吗?
答案是显然的。忽视指针加减法运算的前提写出的表达中很可能是错误的表达式,这种错误往往无法依靠编译器的语法检查检测出来,因为这种错误属于C语言的未定义行为,编译器有时无法检测出这种错误。这个道理就如同编译器无法检测出代码中存在数组越界一样。更为严重的是,这种错误可能并不会立刻表现出来,在程序运行时经常“显得”很正常。程序员们都知道,这种不定时发作的错误才是最可怕的,因为它能让你防不胜防,而且这种错误很难查找。
所以,在写有关指针加减法的代码时,一定特别要注意运算的前提条件,切忌想当然。而作为教材,也应该交代清楚这些运算的前提条件。