【问题标题】:Problems with implementing memmove in c在 c 中实现 memmove 的问题
【发布时间】:2021-01-20 19:21:46
【问题描述】:

我正在尝试为某个测验实现memmove() 函数,因此我在交换操作中动态分配了一块内存,其中 ptr 缓冲区作为临时持有者,并且交换在一个循环中完成。当我提交此代码时,它给了我 3 个测试用例中的一个错误答案。当我将交换分成两个 for 循环时,所有情况都成功。那么有什么区别呢?

  • 相同的循环中交换(一种情况错误):

    uint8_t *my_memmove(uint8_t *src, uint8_t *dst, size_t length) {
        uint8_t *buffer;
        buffer = (uint8_t*)reserve_words(length);
        for (uint8_t i = 0; i < length; i++) {
            *(buffer+i) = *(src+i);
            *(dst+i) = *(buffer+i);   
        }
        return dst;
        free_words((uint32_t*)buffer);
    }
    
  • 在两个循环中交换(所有情况都成功):

    uint8_t *my_memmove(uint8_t *src, uint8_t *dst, size_t length) {
        uint8_t *buffer;
        buffer = (uint8_t*)reserve_words(length);
        for (uint8_t i = 0; i < length; i++) {
            *(buffer+i) = *(src+i);
        }
    
        for (uint8_t i = 0; i < length; i++) {
            *(dst+i) = *(buffer+i);   
        }
    
        return dst;
        free_words((uint32_t*)buffer);
    }
    
  • 使用的reserve_words函数(用户定义):

    int32_t *reserve_words(size_t length) {
        int32_t *ptr;
        ptr = (int32_t *)malloc(length * sizeof(int32_t));
        return ptr;
    }
    

【问题讨论】:

  • 考虑如果srcdst 有重叠区域会发生什么。
  • 请注意,您会泄漏内存,因为您的free() 在您的return 之后。由于您将长度乘以sizeof(int32_t),因此您还分配了四倍所需的内存。在使用分配的内存之前,您应该检查内存分配是否成功。
  • 为什么你有reserve_words函数而不是简单的malloc调用?
  • @JonathanLeffler 没有可移植的方法来判断它们是否重叠,因为您无法比较不同数组之间的指针。 memmove() 的真正实现并不受此限制。
  • @Barmar — 是的,这就是标准使用备用内存进行恶作剧的原因,但特定实现的库可以知道您是否可以便携式比较指针,尽管标准有限制,并且因此可以做出这样的决定。这就是为什么它是一个标准库函数——标准库是为特定的实现量身定制的。在大多数机器上,您可以进行这种比较——可能通过将地址转换为uintptr_t 值——但标准不保证它是可移植的(但实现知道它是安全的)。

标签: c pointers memory memory-management memmove


【解决方案1】:

这里的问题是 memmove 保证可以处理重叠的内存块。如果您想查看第一个版本失败的情况,请尝试以下操作:

char str[20]="Hello, World!";
my_memmove(&str[0], &str[1], 13);
puts(str);

输出是:

HHHHHHHHHHHHHH

很容易理解为什么。每次迭代都将写入要从下一次迭代中读取的字节。

所以你的第一个版本的my_memmove 应该被称为my_memcpy,因为memcpy 不能保证与重叠的内存区域一起工作,memmove 是。

【讨论】:

  • @chux-ReinstateMonica 是的,刚刚注意到,但谢谢
  • 这只适用于单循环的版本。
  • @Barmar 是的,但那是 OP:s 的问题,对吧? OP 想知道为什么第一个版本失败而第二个版本失败。
【解决方案2】:
  • 第一个版本在复制到临时数组时从源复制到目标:如果源和目标数组重叠并且src &lt; dst 您将在复制之前覆盖源数组的部分内容。这正是实现memmove()时的困难所在
  • 第二个版本分配了一个临时数组,从源复制到它,然后从临时数组复制到目标:没问题,除了你freereturn语句后面的临时数组,所以有内存泄漏,您可能不会为此分配分配内存。
  • 在所有复制循环中都存在一个错误:i 应定义为 size_t,而不是 uint8_t。按照编码,对于超过 255 字节的块,该函数将失败。
  • 无需分配length字来节省length字节。

memmove() 的技巧是测试dst 是否指向源数组内部,如果是,则从源数组的末尾复制到开头。没有完全可移植的方法来做到这一点,但对于大多数当前具有平坦地址空间的系统,测试if (dst &gt; src &amp;&amp; dst &lt; src + length) 工作正常。

这是一个简单的实现(请注意,您的原型具有从标准 memmove() 函数转换而来的 srcdst 指针):

uint8_t *my_memmove(const uint8_t *src, uint8_t *dst, size_t length) {
    if (dst > src && dst < src + length) {
        for (size_t i = length; i-- > 0;)
            dst[i] = src[i];
    } else {
        for (size_t i = 0; i < length; i++)
            dst[i] = src[i];
    }
    return dst;
}

【讨论】:

  • 在 memmove 中使用 const 是有争议的
  • @P__J__:为什么会这样? src 不用于写入源数组,因此可以将 const 限定指针传递给 memmove,并且相应的参数也必须是 const-qualified。源数组和目标数组可以重叠的事实意味着srcdst 不能被restrict 限定,这是另一回事。
  • 正如我所写的有争议的。 IMO 如果有任何潜在的执行路径写入此对象,则不应将其声明为constrestrict 是另一个问题
  • @P__J__: 如果memmove 通过src 指针写入对象,我同意。如果通过dst 指针修改的内存恰好与通过src 指针读取的内存重叠,则不是const 限定*src 的理由,而是restrict 不限定任一指针的原因,与memcpy相反,要求这些区域是不相交的。 restrict 的规范并不完美,但恰好适用于相关案例。
【解决方案3】:
  1. 内存泄漏
    return dst;
    free_words((uint32_t*)buffer);

您永远不会调用free_words,因为它会更早返回。

  1. 当您在一个循环中复制并且内存位置重叠时,您的算法将覆盖数据并且该功能将不起作用。

  2. 这个缓冲区完全没用,这是一个糟糕的编程示例

  3. 函数应该获取并返回void * 指针。

  4. 如果赋值的一侧是 (void *),则不必强制转换指针

void *mymemmove(void *dest, void *src, size_t size)
{
    void *saveddest = dest;
    unsigned char *ucdest = dest, *ucsrc = src;
    if(dest && src && size)
    {
        if(dest < src)
        {
            while(size--)
            {
                *ucdest++ = *ucsrc++;
            }
        }
        else if(src < dest) //this if to avoid copy if dest == src (not needed)
        {
            ucdest += size - 1; ucsrc += size - 1;
            while(size--)
            {
                *ucdest-- = *ucsrc--;
            }
        }
    }
    return saveddest;
}

【讨论】:

  • 合理,但 dest &lt; src 是 UB。善用unsigned char *。也许void *src --> const void *src?
  • @chux 通常是的,但它适用于 每个 OP 可能使用的架构。
猜你喜欢
  • 2021-03-03
  • 2011-04-04
  • 2013-06-17
  • 2021-09-26
  • 1970-01-01
  • 2013-11-05
  • 2015-03-19
  • 2013-07-30
  • 1970-01-01
相关资源
最近更新 更多