【问题标题】:Data copy method direct assign vs memcpy in cc中的数据复制方法直接分配与memcpy
【发布时间】:2019-12-18 14:29:36
【问题描述】:

我想从不同的变量中复制数据并将其放在一个数组中,以便进一步处理。

我研究了直接赋值和 memcpy 方法,并认为 memcpy 用于复制完整的缓冲区而不是单个元素。此外,我认为将 memcpy 用于单个字节可能需要时间并浪费 CPU 周期。

您能否从下面的示例中告诉我在这种情况下应该使用什么,因为它在多线程环境中运行(不同的示例)并且变量可能会改变?

#include <stdio.h>
#include <stdint.h>

int main()
{

    printf("Direct assign method\n");

    uint8_t pack_id = 123;
    uint8_t pack_age = 76;
    uint8_t pack_cmd = 30;

    uint8_t cus_data[3] = {0};

    cus_data[0] = pack_id;
    cus_data[1] = pack_age;
    cus_data[2] = pack_cmd;

    printf("Memcpy method\n");

    pack_id = 112;
    pack_age = 89;
    pack_cmd = 25;

    memcpy(&cus_data[0], &pack_id, sizeof(pack_id));
    memcpy(&cus_data[1], &pack_age, sizeof(pack_age));
    memcpy(&cus_data[2], &pack_cmd, sizeof(pack_cmd));

    return 0;
}

【问题讨论】:

  • 对一个字节使用memcpy 可能比直接赋值花费更多时间。
  • 选择可读性最好的版本,让编译器进行优化。 memcpy() 在这里看起来确实不合适。
  • 现代编译器可能会将 memcpy 更改为赋值。如果你能读懂汇编代码,把这段代码放到godbolt.org,或者你甚至可以略读一下汇编代码,看看它没有调用memcpy。
  • 问题的前提是错误的。如果在多线程程序中进行赋值时这些变量更改,那么您将遇到数据竞争,并且程序的行为是未定义。跨度>
  • @FiddlingBits memcpy 是标准库函数,编译器可以随意优化。

标签: c memcpy


【解决方案1】:

cus_data[0] = pack_id; 永远不会比memcpy(&amp;cus_data[0], &amp;pack_id, sizeof(pack_id)); 慢。但是,如果编译器内联 memcpy 调用,它们可能同样快。

您应该做的而不是担心微优化,而是编写尽可能易读的代码。当您遇到实际的性能问题时,请担心优化。

因为cus_data[0] = pack_id; 是最易读的,所以你应该这样做。


在这种情况下,因为它是在多线程环境中运行的

没有区别。您需要保护变量免受重入错误的影响,或者您不需要保护它们。这与简单赋值与 memcpy 无关,因为它们都不能保证是原子的。

变量的大小无关紧要,因为 C 语言中的任何内容都不能保证原子访问,除非您使用 C11 _Atomic 和类似的。

【讨论】:

    【解决方案2】:

    几乎每个优化编译器(当然是 gcc、clang 和 icc)都将 memcpy(以及少数其他 libc 函数,例如 memcmp)视为由同名的真正 libc 函数支持的内置函数.

    编译器通常只会调用真正的函数,如果使用内联汇编完成工作看起来工作量太大。

    复制角色当然不属于该类别。这两个生成same assembly with gcc/clang/icc

    #include <string.h> //compilers generally know about memcpy
    //but C still requires that it should be prototyped
    
    void assign_char(char *X)
    {
        *X = 'x';
    }
    
    void memcpy_char(char *X)
    {
        memcpy(X,"x",1);
    }
    

    x86_64 输出:

     assign_char:
            mov     BYTE PTR [rdi], 120
            ret
     memcpy_char:
            mov     BYTE PTR [rdi], 120
            ret
    

    你使用哪一个并不重要。 memcpy 版本更通用(也适用于数组,即使在 C 中不能将数组分配给/从),并且允许您规避严格的别名,但编写起来也有点冗长。

    【讨论】:

      【解决方案3】:

      大多数现代计算机的地址大小是 8 字节,即 64 位。 您只是不将 8bytes 的地址分配到 1byte(8bit) 存储变量中。

      例如 memcpy(&cus_data[0], &pack_id, sizeof(uinit8_t*));

      但是,对于一个序列,您执行此操作的次数低于 3 次。 memcpy(&cus_data[0], &pack_id, sizeof(uinit8_t));

      memcpy() 函数。是这样的

      void memcpy(void *dest, void *src, size_t size) { 
      char *c_src = (char *)src; 
      char *c_dest = (char *)dest; 
      for (int i=0; i<size; i++) 
          c_dest[i] = c_src[i]; 
      

      }

      它将 src 一个字节一个字节地复制到 dest。等等..

      所以,我喜欢使用 struct,case 1 而不是上面的 进一步的情况2代码复制一个指针一次。

      struct PACK{
      uint8_t id;
      uint8_t age;
      uint8_t cmd;
      

      };

      int main() {

      struct PACK pack = { .id = 123, .age = 76, .cmd = 30 };
      uint8_t cus_data[3];
      
      // case 1
      memcpy(cus_data, &pack, 3);
      
      // case 2
      uint8_t *p = (uint8_t*)&pack;
      
      
      for(int i=0; i<3; i++)
          printf("%d\n", p[i]);
      return 0;
      

      }

      【讨论】:

      • 这与问题有什么关系?没有人试图将地址写入任何地方的 1 个字节。
      猜你喜欢
      • 1970-01-01
      • 2011-07-21
      • 2016-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多