如何安全地使用指针索引数组

How to index arrays using pointers safely

编辑:如果您根本不同意此处的 Fedora 指南,请解释为什么这种方法在 objective 方面比经典循环更糟糕。据我所知,即使是 CERT 标准也没有就在指针上使用索引变量做出任何声明。

我目前正在阅读 Fedora Defensive Coding Guide,它建议如下:

Always keep track of the size of the array you are working with. Often, code is more obviously correct when you keep a pointer past the last element of the array, and calculate the number of remaining elements by substracting the current position from that pointer. The alternative, updating a separate variable every time when the position is advanced, is usually less obviously correct.

这意味着对于给定的数组

int numbers[] = {1, 2, 3, 4, 5};

我应该不会用经典的

size_t length = 5;
for (size_t i = 0; i < length; ++i) {
    printf("%d ", numbers[i]);
}

而是这个:

int *end = numbers + 5;
for (int *start = numbers; start < end; ++start) {
    printf("%d ", *start);
}

或者这个:

int *start = numbers;
int *end = numbers + 5;
while (start < end) {
    printf("%d ", *start++);
}
  1. 我对建议的理解正确吗?
  2. 我的实现是否正确?
  3. 最后两个哪个更安全?

Is my understanding the recommendation correct?
Is my implementation correct?

是的,看起来是这样。

当您有更复杂的项目数组时,有时会使用方法 for(type_t start = &array; start != end; start++)。这主要是风格问题。

当您出于某种原因已经有了开始和结束指针时,有时会使用这种样式。或者在您对大小并不真正感兴趣,而只是想反复比较数组末尾的情况下。例如,假设您有一个带有开始指针和结束指针的环形缓冲区 ADT,并且想要遍历所有项。

这种循环方式实际上就是为什么 C 明确允许指针指向数组越界的 1 项的原因,您可以将结束指针设置为指向数组后的一项而不调用未定义的行为 (只要该项目未取消引用)。

(它与 C++ 中的 STL 迭代器使用的方法完全相同,尽管 C++ 中有更多的基本原理,因为它有运算符重载。例如,C++ 中的 iterator++ 不一定给出一个在下一个内存单元中相邻分配的项目。例如,迭代器可用于遍历 linked 列表 ADT,其中 ++ 将在行后面转换为 node->next。)

然而,声称这种形式总是首选的只是主观的胡说八道。特别是当你有一个整数数组并且知道大小时。您的第一个示例是 C 中最易读的循环形式,因此总是尽可能首选。

在某些 compilers/systems 上,第一种形式也可以提供比第二种形式更快的代码。指针运算可能会在某些系统上给出较慢的代码。 (而且我认为第一种形式可能会在某些系统上提供更快的数据缓存访问,尽管我必须与一些编译器大师一起验证该假设。)

Which of the last 2 is safer?

两种形式都比另一种更安全。否则将是主观意见。 “...通常不太明显正确”的说法是无稽之谈。

选择哪种款式视具体情况而定。


总体而言,您 link 的那些 "Fedora" 指南似乎包含许多有问题的代码、有问题的规则和公然的意见。看起来更像是有人想炫耀各种 C 技巧,而不是认真尝试编写编码标准。总的来说,它闻起来像 "Linux kernel guidelines",我也不建议阅读。

如果您想要严格的编码标准 for/by 专业人员,请使用 CERT-C or MISRA-C

您对文本建议的理解是正确的,您的实施也是如此。但是关于建议的基础,我认为您混淆了 safecorrect

并不是说使用指针比使用索引更安全。论据是,在对代码进行推理时,使用指针更容易确定逻辑正确。安全是关于故障模式:如果代码不正确(引用数组外的位置)会发生什么。正确性更为基本:该算法可证明地执行了它打算执行的操作。我们可能会说正确的代码不需要安全。

该推荐可能受到几年前 Andrew Koenig 在 Dr. Dobbs 系列中的影响。 How C Makes It Hard To Check Array Bounds。 Koenig 说,

In addition to being faster in many cases, pointers have another big advantage over arrays: A pointer to an array element is a single value that is enough to identify that element uniquely. [...] Without pointers, we need three parameters to identify the range: the array and two indices. By using pointers, we can get by with only two parameters.

在 C 中,引用数组外部的位置,无论是通过指针还是索引,都同样不安全。编译器不会抓到你(不使用标准扩展)。 Koenig 认为,空中的球越少,您就越有可能使逻辑正确。

结构越复杂,越明显他是对的。如果您想更好地说明差异,请两种方式都编写 strcat(3) 。使用索引,您在循环 中有两个名称和两个索引。可以将一个索引与另一个的名称一起使用。使用指针,不可能。你所拥有的只是两个指针。