【问题标题】:Constant pointer array or pointer to an array ? What is faster in C?常量指针数组还是指向数组的指针? C中什么速度更快?
【发布时间】:2011-01-25 18:35:40
【问题描述】:

您好,我现在正在上 C 中级班,我突然想到了这个想法:

int multi[3][4]; // const multidimensional array

int* ptr = *multi; // ptr is a pointer variable that hold the address of multi const array

那么,对于访问多维数组位置,什么更快/更小/优化?

这个:

multi[3][1]; // index the value position

*(*(multi+2)+1); // pointer to the position on the array

或(已更新)

ptr += 9; // pointer arithmetic using auxiliary pointer

由于“multi”是一个 const 数组,编译器应该已经“知道”元素位置的本地化,如果使用指向该数组的变量指针可能需要更多的处理时间,另一方面可能会更快搜索我要显示的项目时。什么是更快/更小/优化的方法?

提前谢谢你。

【问题讨论】:

  • 你应该更关心代码的可读性。大多数人没有意识到你不能回答关于 C 的“纳米”性能比较。你可以回答这样的问题,但你必须指定一个实现(或一组实现)。
  • 使其可读。即使 如果 有性能提升 - 我怀疑有 - 使用可读版本。 “过早的优化是万恶之源。” -克努斯
  • C 已经是一种很难使用的语言。为什么要让它变得更难?
  • 好吧,应该有人对我的教授这么说。他的话:1.*(ptr+1)是作弊; 2. 你不应该使用 arr[row][col];等
  • @Maris Melo:叹息。这是反对使用 C 作为教学语言的又一个论据。已经制度化的民间传说、错误信息和明显的不良做法的数量是惊人的。这么多学生对C有问题的原因是因为它通常教得不好。告诉你的教授编写代码并分析不同的版本;他可能会对结果感到惊讶。

标签: c arrays pointers pointer-arithmetic


【解决方案1】:

它们都以相同的方式编译,*(pointer_to_first_element + x + y)

【讨论】:

  • 在下面查看我的答案。使用常规下标运算符会产生最少的指令(针对该代码和编译器设置)。
  • @john:我猜我的 7 个赞成票中有 5 个应该是给你的。。;)我对一维数组做了类似的工作,生成的 asm 是一样的,所以我认为 asm 会二维数组也一样。
【解决方案2】:

首先

int multi[3][4];

不是 const 数组。此声明中没有任何 const

其次,

int* ptr = *multi;

将使ptr 指向元素multi[0][0]。数值上与multi[0]的地址和整个multi的地址相同,但类型不同。

第三,

multi[3][1];

根据定义与

相同
*(*(multi + 3) + 1);

因此性能上没有合理的差异。它与上述内容的关系尚不清楚。

第四,

*ptr + 9;

里面没有任何“指针算术”。相当于

multi[0][0] + 9;

这是一个普通的积分加法。同样,它与上述内容的关系尚不清楚。

最后,您的问题的标题是“常量指针数组或指向数组的指针”,而在问题的实际文本中我都没有看到。

【讨论】:

  • 关于 *ptr + 9,值得一提的是它不包含指针运算,因为一元 * 运算符的优先级高于二元 +。如果代码是 *ptr++,它将包含指针算术。 ++ 与 * 具有相同的优先级,但在这种情况下,运算符是从右到左计算的。这些规则不容易跟踪,这就是为什么在一行中混合多个运算符时不应依赖运算符优先级而是使用括号的原因。
  • @Lundin:我不知道您为什么将“优先级”问题如此复杂化。在 C/C++ 中,一元运算符的优先级总是高于二元运算符,而后缀运算符的优先级总是高于前缀运算符。为一元 * 和后缀 ++ 分配 equal 优先级有什么意义?
  • 这个标准使它过于复杂。其实我是不正确的,postfix++ 的优先级比 * 高,我把后缀优先级和前缀一混淆了。我的观点是,这些运算符存在规则很难跟踪,所以使用括号!不要依赖它们,也不要假设程序员知道或记住它们。
  • @Lundin:该标准没有任何运算符优先规则。该标准具有定义运算符和操作数分组的语法。在这种特定情况下,它非常简单:一元运算符的优先级高于二元运算符,后缀运算符的优先级高于前缀运算符。
  • @Andrey 您对分组是正确的,尽管它们是运算符优先规则的一部分。参见 ISO 9899:1999 6.5 $3“运算符和操作数的分组由语法指示。74)”,注 74:“语法指定运算符在表达式求值中的优先级,与本小节的主要小节,最高优先级在前。”
【解决方案3】:

如果你的代码被扔掉,不管它有多快,因为没有其他人可以理解和维护它;此外,棘手的代码有时会阻止您的编译器进行更好的优化。

例如:

int main(void)
{
  int arr[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
  int *p = *arr; // == arr[0] == &arr[0][0]

  int x;

  x = arr[2][3];         // Straightforward array access
  x = *(*(arr+2)+3);     // Ugly pointer arithmetic
  x = *(ptr + 11);       // Slightly less ugly pointer arithmetic

  return 0;
}

我通过gcc -c -g -Wa,-a,-ad > foo.lst 运行上述代码,以获得交错生成的程序集和源代码。

这是x = arr[2][3];的翻译:

movl      -16(%ebp), %eax     
movl      %eax, -8(%ebp)      

这是x = *(*(arr+2)+3);的翻译:

leal      -60(%ebp), %eax
addl      $44, %eax
movl      (%eax), %eax
movl      %eax, -8(%ebp)

最后,x = *(ptr + 11); 的翻译:

movl      -12(%ebp), %eax
addl      $44, %eax
movl      (%eax), %eax
movl      %eax, -8(%ebp)

不要试图超越你的编译器。这已经不是 1970 年代了。 gcc 知道如何有效地进行数组访问,而无需您告诉它。

你甚至不应该考虑这个级别的性能除非你已经调整了你的算法和数据结构,在你的编译器上使用了最高的优化设置(FWIW , -O1 为所有三个版本生成相同的代码),而您仍然未能满足硬性能要求(在这种情况下,正确的答案通常是购买更快的硬件)。如果没有首先通过分析器运行代码以找出真正的瓶颈,则不应更改任何东西测量,不要猜测。

编辑

当然,当文字23 被变量替换时,情况就会发生变化。在这种情况下,*(ptr + offset); 看起来最好。但不是很多。我仍然会认为,在这个级别上,清晰度更重要。

【讨论】:

    【解决方案4】:

    a[i] 意思是 *(a+i),其实你也可以写i[a],你的编译器会接受的。

    *(ptr+i) 理论上可能比ptr[j][k] 稍微快一点,因为您只进行一次加法(而ptr[j][k] 可能需要 2 次)。

    【讨论】:

    • a[i] 表示 *(a+i) 不是 (a+i)
    【解决方案5】:

    我不确定 const 数组的来源,但为了讨论,我们假设原始帖子中有一些。

    由于原始海报没有明确提及 PC 编程,因此 const 数组和常规数组不一定以相同的方式编译。

    在嵌入式系统中,如果 const 数组分配在真正的非易失性内存中,即具有真正 ROM 的嵌入式应用程序,则 const 数组可能比非 const 数组慢。速度慢与 ROM 的访问时间有关,并且与硬件高度相关。


    关于指针算法,这是访问数组的唯一方法。 C 语言中的数组语法是编译器人们所说的“语法糖”,即它只是为了外观。像

    这样的数组访问

    arr[i]

    被编译器翻译成

    *(arr+i)

    它们在性能和功能上是相当的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-25
      相关资源
      最近更新 更多