在探索 ivector() 函数的行为之前,让我们回顾一下 C 中有关数组和指针的一些基本事实。
考虑代码
int a[10];
for(i = 0; i < 10; i++)
a[i] = 100 + i;
这会在内存中产生一个数组,我们可以这样想:
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a: | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
0 1 2 3 4 5 6 7 8 9
假设我们现在说
int *ip = a;
由于correspondence between arrays and pointers in C,这就相当于说
int *ip = &a[0];
无论如何,我们最终都会得到一个指向a 的第一个单元格的指针,如下所示:
+-----+
ip: | * |
+--|--+
|
v
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
a: | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
现在,指针算术:当您将整数添加到指针时,您“移动”了指针,使其指向底层数组中的下一个元素。
确保您了解此代码打印数字 102 的所有不同方式:
int *ip2 = ip + 2;
printf("%d %d %d %d\n", *(ip + 2), ip[2], *ip2, ip2[0]);
(如果您不了解所有四个表达式 *(ip+2)、ip[2]、*ip2 和 ip2[0] 如何计算为数字 102,请阅读此内容或询问。
这是“数组和指针之间的对应关系”的另一个方面,也是我们理解ivector函数的基础。)
指针减法也有效:调用
printf("%d %d\n", *(ip2 - 1), ip2[-1]);
打印 101,两种方式略有不同。
现在,让我们看看ivector() 函数。
它试图帮助我们模拟不一定从 0 开始的数组。
如果我们调用
int a2 = ivector(0, 9);
for(i = 0; i <= 9; i++) a2[i] = 100 + i;
我们最终会得到一个几乎与之前完全相同的数组:
+-----+
a2: | * |
+--|--+
|
v
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
唯一的区别是数组本身没有名字:
这是我们通过调用malloc 获得的匿名内存区域。
现在假设我们调用
int a3 = ivector(-5, 5);
for(i = -5; i <= 5; i++) a3[i] = 100 + i;
现在我们最终得到了一个 11 元素的“数组”,我们可以认为它看起来像这样:
+-----+
a3: | *-----------------------------+
+-----+ |
v
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
-5 -4 -3 -2 -1 0 1 2 3 4 5
请注意,我们可以谈论a3[0]、a3[3]、a3[-2] 等,就像这是一个下限为-5 的常规数组一样。
关键是你要问的ivector末尾的减法:
return retval - nl;
这不会从数组的值或任何东西中减去任何东西;
又是指针运算,从 指针值 retval 中减去 nl。
对于电话ivector(-5, 5),这转换为
return retval - -5;
当然等价于
return retval + 5;
所以我们得到了一个指向分配区域的 5 个元素的指针。
现在假设我们调用
int *a4 = ivector(1, 10);
for(i = 1; i <= 10; i++) a4[i] = 100 + i;
这就是一切崩溃的地方。
目的是我们最终得到这样的图片:
+-----+
a4: | * |
+--|--+
|
v
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
1 2 3 4 5 6 7 8 9 10
但是有一个非常明显的问题:a4 实际上并没有指向分配的数组。
基于指针算术的工作方式,以及传统上由直接编译器为简单计算机架构实现的方式,您可以说服自己这段代码“应该”正常工作,并且您可以访问 @ 987654355@, a4[2], ... 直到 a4[10]。
如果你尝试访问a4[0],当然会出现可怕的问题,但没关系,你不应该那样做,因为a4 是一个从 1 开始的数组。
不幸的是,这最后一段代码不保证可以工作。如果您计算一个指向数组“外部”的指针(您声明的实际数组,或者您通过调用malloc 获得的类似数组的内存块),则指针算术是不定义的。
如果您尝试计算这样的指针,则行为是未定义的,即使您从未尝试访问越界指针指向的内存。
所以大多数知识渊博的 C 程序员会建议你不要编写像 ivector 这样的代码(或者如果你这样做,只为 nl <= 0 调用它......但当然这几乎没有达到目的) .