何时对数组使用指针与使用访问运算符的通用规则是什么?

What is a good general rule for when to use pointers for arrays versus using access operators?

访问运算符示例:

void print_elements ( int * arr, int n )
{
    for (int k = 0; k < n; ++k) printf("%d\n", arr[k]);
}

指针运算例子:

void print_elements ( int * arr, int n )
{
    for (int * pa(arr), * pb(arr+n); pa != pb; ++pa) printf("%d\n", *pa);
}

第二个涉及循环的 2 个变量,papb,而第一个只涉及一个额外的变量,k。再一次,你在做的那一秒

increment pa by 1, dereference

而在第一个你做的

increment k by 1, add k to the pointer arr, derefence

以便第一个使用较少的操作来遍历循环。

综合考虑,哪个更快?对于整数类型,比较器 < 是否比 != 快?使用 < 似乎 "safer" 因为总是有一种非理性的恐惧,即像 pa != pb 这样的条件作为条件失败,因为如果 pa 可以跳过 pb 如果循环被重构。将指针递增 1 通常比递增大于 1 更快吗?我想考虑一切可能。

这将属于 "premature optimization" 的范围。除非你有特定的(衡量的)理由,否则你通常应该更喜欢最简单和最直接的代码(在这种情况下是你的第一个函数)。现在任何编译器都有可能将这两个函数优化为大致相同,即使不完全相同。

如果您怀疑可以进行一些优化,您应该先 测量 事情 benchmarking/profiling,尽管即使在像这样的简单函数中,您也必须小心很容易得到错误的结果。

如果我们用以下内容替换您的函数:

volatile int Output = 0;

void print_elements1(int * arr, int n)
{
    for (int k = 0; k < n; ++k) Output += arr[k];
}

void print_elements2(int * arr, int n)
{
    for (int * pa(arr), *pb(arr + n); pa != pb; ++pa) Output += *pa;
}

原因是 printf() 是 "slow",如果我们对您的原始函数进行基准测试,我们实际上只会测试它的速度。使用大小为 1 亿的数组测试这些函数以获得 decent/repeatable 计时,我们得到:

  • print_elements1() = 560 毫秒
  • print_elements2() = 230 毫秒

啊,我们看到指针访问速度是数组索引的两倍多....但并不快!让我们颠倒测试顺序,看看我们得到了什么:

  • print_elements2() = 650 毫秒
  • print_elements1() = 260 毫秒

嗯...现在指针访问速度慢了一倍!这是怎么回事?我不完全知道,但它可能必须归因于 CPU/memory 缓存。我们可以尝试通过 运行 基准测试之前的两个函数来消除这种影响:

  • print_elements1() = 230 毫秒
  • print_elements2() = 230 毫秒

相同的时间,至少在我的基准计时器的误差范围内。

这个故事的寓意是,当今的编译器和计算机都是复杂的机器,您的编译器很可能在优化大多数代码方面比您做得更好。当您进行优化时,首先通过 profiling/benchmarking 进行测量,以确定要处理的代码的最有效区域(如果有)。

What is a good general rule for when to use pointers for arrays versus using access operators?

尽可能始终使用数组索引语法,因为它更具可读性。指针算术语法更难阅读,通常不应该用于迭代。

The second involves 2 variables

源代码中使用的变量数量是对性能和内存消耗的非常糟糕的衡量。实际的机器代码需要将结果存储在某处,无论您是否显式声明变量。如果您声明的变量多于机器代码实际需要的数量,编译器很可能会将它们优化掉。

循环需要知道何时结束迭代。它可以通过在循环中的每一圈在运行时计算 arr+n 来做到这一点(不太可能发生,因为它会很慢)或者它可以通过在启动之前将 arr+n 保存在临时内存位置来做到这一点环形。在第一个示例中,您没有声明这样的变量,因此在实际的机器代码中可能会有一个未命名的变量用于此目的。使两个示例等效。

so that the first one uses fewer operations to iterate through the loop

不是,不是。 C 标准强制 arr[i] 100% 等同于 *(arr + i)。数组语法只是 "syntactic sugar"。这两种情况极有可能生成相同的机器代码。

(以上等价规则是C允许some weird, ugly crap的原因)

All things considered, which is faster?

由于上述原因,它们同样快。

Is the comparator < faster than != for integral types?

一般应该没什么区别。这一切都归结为在给定 CPU 上可用的汇编程序指令。除了那些与 0 进行比较的比较之外,所有比较方式很可能同样快,它们可能会在某些 CPUs 上为您节省几纳秒。

I want to consider everything possible.

那么我强烈建议您考虑程序的可读性和可维护性,而不是手动微优化。前者造就了当今优秀的程序员,而不是后者。我们不再是 1980 年代了。

正如其他人所指出的,性能问题不会导致我们在访问数组元素时采用一种或另一种表示法。

如果表达同一事物的方式不止一种,请使用更容易理解的方式。

又是品味问题。

我更喜欢索引运算符 [] 并执行 a[1],只要我不需要获取数组元素的地址即可。在后一种情况下,我使用 + 运算符,即 a + 1 而不是 &a[1].