最近我从用户“chux”那里了解到,将
1
添加到不代表数组元素的地址是合法的。具体来说,标准中有如下规定(C17草案,6.5.6¶7)
出于这些运算符的目的,指向不是数组元素的对象的指针的行为与指向长度为 1 的数组的第一个元素的指针的行为相同,并且对象的类型作为其元素类型。
使得编写
&var + 1
是合法的,其中 var
not 表示为 arr[i]
对于一些类型为 T arr[n]
的数组,其中 0
≤i
<n
.
这样做的用例是什么?我找到了 Aaron Ballman 的example(在SEI CERT C Coding Standard 网站上)提到了“分配位置”。不引用他的整个例子,本质似乎是可以使用一次调用
malloc
为多个对象分配空间,这样就可以像这样分配给它们:
T1 *objptr1 = (T1 *)malloc(sizeof(T1) + sizeof(*objptr2));
*objptr1 = ...;
memcpy(objptr1 + 1, objptr2, sizeof(*objptr2))
这是我的玩具示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
float obj2 = 432.1;
long *objptr1 = (long *)malloc(sizeof(*objptr1) + sizeof(obj2));
*objptr1 = 123456789L;
memcpy(objptr1 + 1, &obj2, sizeof(obj2));
printf("%ld\n", *objptr1); // 123456789
printf("%f\n", *(float *)(objptr1 + 1)); // 432.100006
return 0;
}
我希望这抓住了成语的本质。但是,似乎也可以简单地使用
(char *)
-cast 和 sizeof
代替:
memcpy((char *)objptr1 + sizeof(*objptr1), &obj2, sizeof(obj2));
在一般情况下,
&var + 1
比(char *)&var + sizeof var
短,所以也许这就是优势。
但这就是全部吗? 如果 var 不是数组元素,写 (&var + 1) 的用例是什么?
关于这个成语的合法性可能相关的是this answer to another question.
如果 var 不是数组元素,写 (&var + 1) 的用例是什么?
并非所有脱离语言语义的东西都有特定的用途。大多数计算机语言都是为一致性和充分性而设计的。有些还旨在简化。然而,很少有人明确以极简为目标,C 不是其中之一。
为指向标量的指针定义指针算法的主要原因是它更容易定义指针算法。指向标量的指针不是特例,这很好,因为不一定可以将它们与指向数组元素的指针区分开来(或者:实现不需要使之成为可能)。此外,使指向标量的指针等同于指向单元素数组的单个元素的指针是没有问题的,因为指针类型相同并且标量的表示与相同数据的单元素数组的表示相同类型。
鉴于指针算法是通过依赖于标量和单元素数组之间的语义等价来为指向标量的指针定义的,
&scalar + 1
的用例与&single_element_array[0] + 1
的用例完全相同,在需要的上下文中依靠语义等价。反过来,这些情况与&n_element_array[n-1] + 1
的情况几乎相同。
那么,也许一个更好的问题是,为什么该语言通常允许计算刚好超过数组末尾的指针,以及那可能有什么用处。据我所知或曾经能够确定,这些主要是为了方便。例如,如果您被允许计算(但不是取消引用)指向刚好超过数组末尾的指针,则通过指针遍历数组会容易得多。并且希望能够通过 [ inclusive_start, exclusive_end ) 指针对来表达子数组。然而,这些都不是必不可少的。
指针基本上是指向内存地址并指定它们所指向的实体类型的变量/参数。
数组是奇特的指针,基本上有一个指向首地址的指针以及一个类型和一个大小。
您的问题想知道是否还有其他非数组的指针算术用例。基本上是的。让我们想象一下文件加载到内存中的情况。现在,如果你知道所述文件的起始地址,那么你可以有一个指向它的指针并增加你的指针以处理字符。基本上,只要您有一些更大的内存块,其中包含多个可以循环的相似但不一定等效的项目,那么进行指针运算就很有意义。你不一定需要数组。