【问题标题】:memcpy vs for loop - What's the proper way to copy an array from a pointer?memcpy vs for 循环 - 从指针复制数组的正确方法是什么?
【发布时间】:2011-06-11 08:55:42
【问题描述】:

我有一个函数foo(int[] nums),我理解它基本上等同于foo(int* nums)。在foo 内部,我需要将nums 指向的数组的内容复制到foo 范围内声明的一些int[10]。我理解以下内容无效:

void foo (int[] nums) 
{
    myGlobalArray = *nums
}

复制数组的正确方法是什么?我应该像这样使用 memcpy:

void foo (int[] nums)
{
    memcpy(&myGlobalArray, nums, 10);
}

还是应该使用 for 循环?

void foo(int[] nums)
{
    for(int i =0; i < 10; i++)
    {
        myGlobalArray[i] = nums[i];
    }
}

我还缺少第三种选择吗?

【问题讨论】:

标签: c++ arrays pointers copy


【解决方案1】:

是的,第三种选择是使用 C++ 构造:

std::copy(&nums[0], &nums[10], myGlobalArray);

使用任何理智的编译器,它:

  • 在大多数情况下应该是最佳的(将在可能的情况下编译为memcpy()),
  • 是类型安全的,
  • 当您决定将数据类型更改为非原始数据类型(即它调用复制构造函数等)时,可以优雅地应对,
  • 在您决定更改为容器类时优雅地应对。

【讨论】:

  • 我似乎在评论中留下了一个重要的词:“可能”
  • @Oliver Charlesworth 是 &amp;nums[10] 当数组大小为 10 时允许的? nums + 10 会代替吗?
  • @user2635088 nums[10]*(nums + 10) 相同,因此&amp;nums[10] 将是&amp;(*(nums + 10)),编译器可能会将其优化为nums + 10。但是,是的,一般来说,&amp;nums[10] 对大小为 10 的数组有效,因为您可以将数组末尾 1 的地址仅用于迭代目的。只是不要取消引用该地址,那将是未定义的行为。
【解决方案2】:

Memcpy 可能会更快,但您使用它的可能性更大。 这可能取决于您的优化编译器的智能程度。

您的代码不正确。应该是:

memcpy(myGlobalArray, nums, 10 * sizeof(int) );

【讨论】:

  • 嘿!为什么 Memcpy 会更快??
  • c 库的作者花了很多时间优化它。如果您编写代码来做同样的事情,那么这取决于编译器对您的代码的优化程度。
  • myGlobalArray 是数组。应该是memcpy(myGlobalArray, nums, 10 * sizeof(int));
  • 它当前显示您所写的内容。也许它被编辑了?
【解决方案3】:

一般来说,最坏的情况是在未优化的调试版本中,memcpy 没有内联,并且可能会执行额外的健全性/断言检查,相对于 for 循环而言,需要少量额外的指令。

但是memcpy 通常可以很好地实现以利用内在函数等,但这会因目标体系结构和编译器而异。 memcpy 不太可能比 for 循环实现更糟糕。

人们经常对 memcpy 大小以字节为单位这一事实感到困惑,他们会这样写:

// wrong unless we're copying bytes.
memcpy(myGlobalArray, nums, numNums);
// wrong if an int isn't 4 bytes or the type of nums changed.
memcpy(myGlobalArray, nums, numNums);
// wrong if nums is no-longer an int array.
memcpy(myGlobalArray, nums, numNums * sizeof(int));

您可以在这里通过使用允许您进行某种程度反思的语言功能来保护自己,即:根据数据本身而不是您对数据的了解来做事,因为在通用函数中您通常不会对数据一无所知:

void foo (int* nums, size_t numNums)
{
    memcpy(myGlobalArray, nums, numNums * sizeof(*nums));
}

请注意,您不希望“myGlobalArray”前面的“&”,因为数组会自动衰减为指针;您实际上是在将“nums”复制到内存中指向 myGlobalArray[0] 的指针所在的地址。

编辑说明:当我的意思不是int nums[] 时,我会打错int[] nums,但我决定添加C array-pointer-equivalence 混乱对任何人都没有帮助,所以现在是int *nums :))

在对象上使用memcpy 可能很危险,请考虑:

struct Foo {
    std::string m_string;
    std::vector<int> m_vec;
};

Foo f1;
Foo f2;
f2.m_string = "hello";
f2.m_vec.push_back(42);
memcpy(&f1, &f2, sizeof(f2));

这是复制非 POD(普通旧数据)对象的错误方法。 f1 和 f2 现在都有一个 std::string 认为它拥有“你好”。其中一个在销毁时会崩溃,并且他们都认为自己拥有相同的包含 42 的整数向量。

C++ 程序员的最佳实践是使用std::copy

std::copy(nums, nums + numNums, myGlobalArray);

Remy Lebeau 的注释 或自 C++11 起

std::copy_n(nums, numNums, myGlobalArray);

这可以在编译时决定要做什么,包括使用 memcpymemmove 并可能在可能的情况下使用 SSE/向量指令。另一个好处是,如果你这样写:

struct Foo {
    int m_i;
};

Foo f1[10], f2[10];
memcpy(&f1, &f2, sizeof(f1));

稍后更改 Foo 以包含 std::string,您的代码将中断。如果你改为写:

struct Foo {
    int m_i;
};

enum { NumFoos = 10 };
Foo f1[NumFoos], f2[NumFoos];
std::copy(f2, f2 + numFoos, f1);

编译器将切换您的代码以执行正确的操作,而无需为您做任何额外的工作,并且您的代码更具可读性。

【讨论】:

  • void foo (int[] nums, size_t numNums) 中使用 int[] 在 Visual Studio 2017 中引发错误。如果我使用 int* 就可以了。但是,在这种情况下,我不能在数组上使用 sizeof - 它总是只返回指针的大小。
  • @Kari 已修复,这是一个错字,意为int nums[],但对于您的第二点,这是因为数组指针等价。将T x[] 放入函数原型中相当于T* x,因为C 数组没有运行时大小信息,因此它们衰减为指针。 c-faq.com/aryptr/aryptrequiv.html
  • @kfsone: 另请注意,在 C++11 及更高版本中,还有std::copy_n()std::copy_n(nums, numNums, myGlobalArray);
  • @RemyLebeau 添加:)
  • 我会为答案添加一些细微差别。启用适当优化的 memcpy 将内联 IF 大小参数在编译时已知。在这种情况下,memcpy() 始终是正确的选择。如果不知道大小参数,那么您需要考虑函数调用成本与 memcpy 微调实现的加速增益。这需要测量才能找到,但我怀疑对于小于 16 个字节的副本,也许 for 循环是更好的选择...stackoverflow.com/questions/11747891/…
【解决方案4】:

为了提高性能,请使用 memcpy(或等效项)。它是高度优化的特定于平台的代码,用于快速分流大量数据。

为了可维护性,请考虑您在做什么 - for 循环可能更具可读性和更易于理解。 (错误的 memcpy 是导致崩溃或更糟的快速途径)

【讨论】:

    【解决方案5】:

    基本上,只要您处理的是 POD 类型(Plain Ol' Data),例如 int、unsigned int、指针、纯数据结构等......您可以安全地使用 mem*。

    如果您的数组包含对象,请使用 for 循环,因为可能需要 = 运算符来确保正确分配。

    【讨论】:

      【解决方案6】:

      对于大约 10-20 字节和更少的简单循环稍微快一些(它是一个比较+分支,请参阅 OP_T_THRES),但对于更大的尺寸,memcpy 更快且可移植。

      另外,如果要复制的内存量是恒定的,可以使用memcpy让编译器决定使用什么方法。

      旁注:memcpy 使用的优化可能会在多线程环境中显着降低您的程序在多线程环境中的速度,因为当您复制大量数据高于 OP_T_THRES 大小标记时,因为它调用的指令不是原子的并且是推测性的当多个线程访问同一内存时,此类指令的执行和缓存行为表现不佳。最简单的解决方案是不在线程之间共享内存,只在最后合并内存。无论如何,这是一个很好的多线程实践。

      【讨论】:

        猜你喜欢
        • 2016-09-25
        • 2015-03-03
        • 2012-06-18
        • 2011-03-12
        • 1970-01-01
        • 1970-01-01
        • 2020-12-04
        • 2014-02-03
        • 2010-09-18
        相关资源
        最近更新 更多