【问题标题】:copying array vs copying int cost and performance in c在 c 中复制数组与复制 int 成本和性能
【发布时间】:2021-10-05 10:49:05
【问题描述】:

我正在阅读一本关于 c 的书,以下段落对我来说有点不清楚:

令人惊讶的是,在上面的示例中传递指针效率不高!这是因为 int 类型是 4 个字节,复制它比复制其指针的 8 个字节更有效。但对于结构和数组,情况并非如此。由于复制结构和数组是按字节完成的,并且它们中的所有字节都应该一个一个地复制,因此通常最好改为传递指针。

据我所知,CPU 中的所有操作都仅限于算术(加或 minعس)或按位操作,所以

作者复制数组和结构是什么意思,int复制不就是位移操作吗?

二:指针是数组吗?

注意:书名是 Extreme C,由 packT 出版 下面的例子就是作者所指的:

#include <stdio.h>
void func(int* a) {  
int b = 9;
*a = 5;  a = &b; 
}
int main(int argc, char** argv) {  
int x = 3;
int* xptr = &x;
printf("Value before call: %d\n", x);
printf("Pointer before function call: %p\n", (void*)xptr);  func(xptr);  
printf("Value after call: %d\n", x);
printf("Pointer after function call: %p\n", (void*)xptr);
return 0; 
}

'''

【问题讨论】:

  • 如果您显示本书所指的实际代码会有所帮助。
  • 我认为 C 或 C++ 没有任何用于复制数组的内置机制。由程序员决定如何复制数组。关于structs,我认为默认情况下,我们更有可能看到逐个成员的分配,而不是逐个字节的副本。所以我很确定我不同意作者的观点,尽管我不太确定他们在说什么。
  • @TimRandall:C 标准对实现是逐个成员、逐个字节还是逐个存储单元复制结构保持沉默。它授权实现使用它想要的任何方法(允许它们复制或忽略填充字节)并且不需要它们中的任何一个。我希望 POD 的 C++ 标准是相同的。
  • @RemyLebeau 帖子已更新

标签: c++ arrays c function data-structures


【解决方案1】:

书上写的不明白,也是错的。

假设似乎是 8 字节指针比 4 字节整数“更难”复制。这对几乎所有现代 CPU 来说都是错误的。

此外,关于复制数组的部分是完全错误的。这不是 C 所做的。在 C 中传递数组不涉及副本。这实际上就像传递一个指针。

然而,关于结构的部分是正确的......只要结构不仅仅是一个简单的整数或字符,而是“更大的东西”。

作者说的复制数组是什么意思

听起来很垃圾……因为 C 不会通过复制来传递数组

作者复制...结构是什么意思,

结构是按值复制的。因此,将结构传递给函数涉及复制结构的每个字节。如果结构很大,那会相当昂贵。

是指针数组吗?

没有。指针是指针。但是...在正确的情况下,指针可以用作数组,因为*(p + i)p[i] 相同

【讨论】:

    【解决方案2】:

    作者对复制数组和结构是什么意思?

    让我们比较两个处理大量数据的函数(例如,struct 有很多数据成员):

    void f(const big_type_t* p_big_type);
    
    void g(const big_type_t big_type);
    

    两者都可以有效地从调用者指定的big_type_t 对象中读取值,但在前一种情况下,f() 只需要传递一个指针(在现代日常硬件上通常为 8 个字节)来告诉它调用者在哪里有一个 big_type_t 对象供它使用。在后一种情况下,g() 按值传递参数要求编译器制作调用者的 big_type_t 参数的完整副本,并将其复制到堆栈上 g() 隐式知道找到它的位置。 struct 中数据的每个字节都必须被复制(除非编译器足够聪明,可以根据 as-if 规则进行优化,但这有点分散注意力 - 通常最好这样编写代码如果没有得到很好的优化,它并不是不必要的低效)。

    对于内置数组,情况就不同了。 C 和 C++ 通过指针隐式传递数组,所以...

    void h(const int* my_array);
    void i(const int my_array[]);
    

    ...两者的调用方式相同,my_array 参数实际上是指向调用者指定的第一个 int 的指针。

    在 C++ 中还有std::array&lt;&gt;s,它们实际上是struct/classes,带有一个静态大小的数组数据成员(即template &lt;typename T, size_t N&gt; struct array { T data_[N]; ... })。它们可以按值传递,与结构相同。因此,对于大型 std::array 对象,通过指针或引用访问比进行完整复制更有效。

    有时,一个函数确实需要一个副本,因为它可能需要在不影响传递给该参数的调用者指定的变量的情况下对它进行排序之类的操作。在这种情况下,通过指针或引用传递的意义不大。

    int 不是复制位移操作吗?

    不...术语“位移”在编程中具有非常特殊的含义。考虑一个 8 位整数 - 比如0x10010110。如果我们将这个值向右移动一位,我们会得到0x01001011——在左边引入一个 0,在右边丢弃一个 0。如果我们再次将新值向右移动,我们可以得到0x00100101(在左侧添加 0;在右侧丢弃)或者 - 所谓的循环移位或旋转 - 0x100100101`,最右边的位移动到成为最左边的位。移位发生在 CPU 寄存器中,移位后的值可以存储回变量所在的内存中,或用于某些计算。

    所有这些都与内存复制完全无关,即一个值中的位(至少在概念上,没有优化)复制到“另一个”值中。对于大量数据,这通常确实意味着实际上将从内存读取的值中的位复制到内存的另一个区域。

    第二:指针是数组吗?

    不,他们不是。但是,当您有一个数组时,它很容易“衰减”为指向其第一个元素的指针。例如:

    void f(const char* p);
    f("hello");
    

    在 C++ 中,“hello”是 char[6] 类型的字符串文字(因为末尾隐含一个空字符。当调用 f 时,它从数组形式衰减为指向第一个字符的指针 - @987654343 @。这通常需要让被调用函数访问数组数据。在 C++ 中,您也可以这样做:

    template <size_t N> void f(const char(&arr)[N]);
    f("hello");
    

    上述调用不涉及从数组到指针的衰减 - arr 绑定到字符串字面量数组,N 派生为 6

    【讨论】:

      【解决方案3】:

      作者复制数组和结构是什么意思,是不是int复制位移操作?

      当您将struct 类型的对象作为参数传递给函数时,该结构的内容 会被复制到形参中:

      struct foo { 
        ...
      };
      
      void do_something_with( struct foo arg )
      {
        // do something with arg
      }
      
      int main( void )
      {
        struct foo f = { 1, 2.0, "three" };
        ...
        do_something_with( f );
        ...
      }
      

      对象main:fdo_something_with:argstruct foo 的两个独立实例 - 当您将f 作为参数传递时,其内容将复制到arg。您对arg 的内容所做的任何更改都不会影响f 的内容。

      问题是,本书的作者对数组的看法是错误的——当您将数组表达式作为参数传递给函数时,您实际上传递的是指向第一个元素的指针,不是 整个数组。

      第二:指针是数组吗?

      数组不是指针 - 但是,除非它是sizeof 或一元&amp; 运算符的操作数,否则表达式 类型为“N 元素数组”的T”将被转换或“衰减”为“指向T的指针”类型的表达式,其值将是数组第一个元素的地址。

      当您将数组表达式作为参数传递给函数时,函数实际接收的是指向数组第一个元素的指针 - 不会像上面的 struct 那样复制数组。

      最后 - 虽然运行时效率确实很重要,但正确性、清晰度和可维护性也很重要更多。如果有意义将参数作为指针传递(例如您希望函数修改参数),那么一定要这样做。但不要开始将 everything 作为指针传递,因为它可能会加快速度。首先让事情变得清晰和正确 - 然后,衡量代码的性能并据此采取行动。您的大部分运行时性能提升来自使用正确的数据结构和算法,而不是您传递参数的方式。

      【讨论】:

        【解决方案4】:

        虽然示例代码有很多不足之处和一些错误,但我认为作者所说的要点是,对于小数据类型,直接按值将参数传递给函数更有效(@ 987654321@) 而不是通过指针传递 (int *)。调用函数时,会将参数压入堆栈,int 类型需要 2 个字节,但int * 参数可能需要 4 或 8 个字节,具体取决于系统。

        当传递 struct 作为参数时,结构的总大小通常会大于 4 或 8 个字节,因此传递指向 thr struct 的指针可能更有效,因为只有 4 或 8 个字节会需要复制到栈中。

        我不确定作者为什么提到数组,因为数组不能按值传递给函数,除非它包含在 struct 中。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-20
          • 1970-01-01
          • 1970-01-01
          • 2021-11-21
          • 2011-08-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多