简体   繁体   English

复制没有memcpy()的字节

[英]copy bytes without memcpy()

It's a home work. 这是一个家庭作业。 I want to implement memcpy() . 我想实现memcpy() I was told memory area must not overlap. 我被告知记忆区域不能重叠。 Actually I don't understand what that means, because this code works fine, but there is a possibility of memory overlap. 实际上我不明白这意味着什么,因为这段代码工作正常,但存在内存重叠的可能性。 How to prevent it? 怎么预防呢?

void *mem_copy(void *dest, const void *src, unsigned int n) {
    assert((src != NULL) && (n > 0));  
    int i = 0;
    char *newsrc = (char*)src;
    char *newdest = (char*)dest;
    while (i < n) {
        newdest[i] = newsrc[i];
        i++;
    }
    newdest[i]='\0';
    return newdest;
}

Actually I don't understand what doest that mean [for memory to overlap] 其实我不明白什么意思[内存重叠]

Consider this example: 考虑这个例子:

char data[100];
memcpy(&data[5], &data[0], 95);

From the program's point of view, the range of addresses from src to src+n must not overlap the range from dest to dest+n . 从程序的角度来看,从srcsrc+n的地址范围不得与destdest+n的范围重叠。

if there is possibility of memory overlap, how to prevent it? 如果存在内存重叠的可能性,如何预防呢?

You can make your algorithm work with or without an overlap by deciding to copy overlapping regions from the back if src has numerically lower address than dest . 如果src数字地址低于dest则可以通过决定从后面复制重叠区域来使算法在有或没有重叠的情况下工作。

Note: Since you are doing memcpy , not strcpy , forcing null termination with newdest[i]='\\0' is incorrect, and needs to be removed. 注意:由于您正在执行memcpy而不是strcpy ,因此使用newdest[i]='\\0'强制空终止是不正确的,并且需要删除。

When source and destination memory blocks overlap, and if your loop copies one element after the other starting from index 0 , it works for dest < source , but not for dest > source (because you overwrite elements before having copied them) and vice versa. 当源和目标内存块重叠时,如果循环从索引0开始复制一个元素,则它适用于dest < source ,但不适用于dest > source (因为在复制之前覆盖元素),反之亦然。

Your code starts copying from index 0 , so you can simply test which situations work and which not. 您的代码从索引0开始复制,因此您可以简单地测试哪些情况有效,哪些无效。 See the following test code; 请参阅以下测试代码; It shows how moving a test string forward fails, whereas moving the string backwards works fine. 它显示了如何向前移动测试字符串失败,而向后移动字符串工作正常。 Further, it shows how moving the test string forward works fine when copying from backward: 此外,它还显示了从后向复制时向前移动测试字符串的工作原理:

#include <stdio.h>
#include <string.h>

void *mem_copy(void *dest, const void *src, size_t n) {
    size_t i = 0;
    char* newsrc = (char*)src;
    char* newdest = (char*)dest;
    while(i < n) {
        newdest[i] = newsrc[i];
        i++;
    }
    return newdest;
}

void *mem_copy_from_backward(void *dest, const void *src, size_t n) {
    size_t i;
    char* newsrc = (char*)src;
    char* newdest = (char*)dest;
    for (i = n; i-- > 0;) {
        newdest[i] = newsrc[i];
    }
    return newdest;
}

int main() {

    const char* testcontent = "Hello world!";
    char teststr[100] = "";

    printf("move teststring two places forward:\n");
    strcpy(teststr, testcontent);
    size_t length = strlen(teststr);
    printf("teststr before mem_copy: %s\n", teststr);
    mem_copy(teststr+2, teststr, length+1);
    printf("teststr after mem_copy: %s\n", teststr);

    printf("\nmove teststring two places backward:\n");
    strcpy(teststr, testcontent);
    length = strlen(teststr);
    printf("teststr before mem_copy: %s\n", teststr);
    mem_copy(teststr, teststr+2, length+1);
    printf("teststr after mem_copy: %s\n", teststr);

    printf("move teststring two places forward using copy_from_backward:\n");
    strcpy(teststr, testcontent);
    length = strlen(teststr);
    printf("teststr before mem_copy: %s\n", teststr);
    mem_copy_from_backward(teststr+2, teststr, length+1);
    printf("teststr after mem_copy: %s\n", teststr);
}

Output: 输出:

move teststring two places forward:
teststr before mem_copy: Hello world!
teststr after mem_copy: HeHeHeHeHeHeHeH

move teststring two places backward:
teststr before mem_copy: Hello world!
teststr after mem_copy: llo world!

move teststring two places forward using copy_from_backward:
teststr before mem_copy: Hello world!
teststr after mem_copy: HeHello world!

So one could write one function, which decides whether to start copying from index 0 or from index n depending on whether the caller wants to copy forward or backward. 因此,可以编写一个函数,该函数决定是从索引0还是从索引n开始复制,具体取决于调用者是要向前还是向后复制。 The tricky thing is to find out whether the caller will copy forward or backward, since a pointer arithmetic on src and dest like if (src < dest) copy_from_backward(...) is actually not permitted in every case (cf. the standard, eg this draft ): 棘手的问题是找出调用者是向前还是向后复制,因为在srcdest上的指针算法if (src < dest) copy_from_backward(...)在每种情况下实际上都是不允许的(参见标准,例如这个草案 ):

6.5.9 Equality operators 6.5.9平等运营商

When two pointers are compared, the result depends on the relative locations in the address space of the objects pointed to. 比较两个指针时,结果取决于指向的对象的地址空间中的相对位置。 If two pointers to object or incomplete types both point to the same object, or both point one past the last element of the same array object, they compare equal. 如果指向对象或不完整类型的两个指针都指向同一个对象,或者两个指针都指向同一个数组对象的最后一个元素,则它们相等。 If the objects pointed to are members of the same aggregate object, pointers to structure members declared later compare greater than pointers to members declared earlier in the structure, and pointers to array elements with larger subscript values compare greater than pointers to elements of the same array with lower subscript values. 如果指向的对象是同一聚合对象的成员,则指向稍后声明的结构成员的指针比指向结构中先前声明的成员的指针大,指向具有较大下标值的数组元素的指针比指向同一数组的元素的指针大。具有较低的下标值。 All pointers to members of the same union object compare equal. 指向同一个union对象的成员的所有指针都比较相等。 If the expression P points to an element of an array object and the expression Q points to the last element of the same array object, the pointer expression Q+1 compares greater than P. In all other cases, the behavior is undefined . 如果表达式P指向数组对象的元素并且表达式Q指向同一数组对象的最后一个元素,则指针表达式Q + 1将比较大于P. 在所有其他情况下,行为未定义

Though I've never been in a situation where src < dest did not give me the desired results, comparing two pointers this way is actually undefined behaviour if they do not belong to the same array. 虽然我从来没有遇到src < dest没有给我想要的结果的情况,但是如果它们不属于同一个数组,那么比较两个指针实际上是未定义的行为。

Hence, if you ask "how to prevent it?", I think that the only correct answer must be: "It's subject to the caller, because function mem_copy cannot decide whether it may compare src and dest correctly." 因此,如果你问“如何防止它?”,我认为唯一正确的答案必须是:“它受调用者的影响,因为函数mem_copy无法决定它是否可以正确地比较srcdest 。”

There are some issues in your re-implementation of memcpy() : 重新实现memcpy()存在一些问题:

  • The size argument n should have type size_t . size参数n类型应为size_t The index variable i should have the same type as the size argument. 索引变量i应该与size参数具有相同的类型。

  • It is OK to pass a count of 0 . 传递0的数量是可以的。 Indeed your code would behave correctly in this case, remove the test from the assert() . 实际上,在这种情况下,您的代码将正常运行,从assert()删除测试。

  • Avoid casting away the const qualifier unless absolutely necessary. 除非绝对必要,否则请避免丢弃const限定符。

  • Do not tack a '\\0' at the end of the destination, it is incorrect and will cause buffer overruns. 不要在目的地的末尾添加'\\0' ,它是不正确的并且会导致缓冲区溢出。

Here is a corrected version: 这是一个更正版本:

void *mem_copy(void *dest, const void *src, size_t n) {
    assert(n == 0 || (src != NULL && dest != NULL));  
    size_t i = 0;
    const char *newsrc = (const char *)src;
    char *newdest = (char *)dest;
    while (i < n) {
        newdest[i] = newsrc[i];
        i++;
    }
    return dest;
}

Regarding the potential overlap between the source area and the destination area, your code's behavior will be surprising if the destination pointer is greater than the source, but within the source area: 关于源区域和目标区域之间的潜在重叠,如果目标指针大于源,但在源区域​​内,则代码的行为将会令人惊讶:

char buffer[10] = "12345";
printf("before: %s\n", buffer);
mem_copy(buffer + 1, buffer, 5);
printf("after: %s\n", buffer);

Will output: 将输出:

before: 12345
after: 111111

There is no completely portable way to test for such overlap, but it is quite easy on non exotic architectures at some small cost in execution time and code size. 没有完全可移植的方法来测试这种重叠,但是在非常规体系结构上很容易实现时间和代码大小。 The semantics of memcpy() is that no such test is assumed to be performed by the library, so the programmer should only call this function if there is no possibility for source and destination areas to overlap. memcpy()的语义是假定库不执行此类测试,因此如果源和目标区域不可能重叠,程序员应该只调用此函数。 When in doubt, use memmove() that handles overlapping areas correctly. 如有疑问,请使用正确处理重叠区域的memmove()

If you wish to add an assert for this, here is a mostly portable one: 如果你想为此添加一个assert ,这里有一个大多数可移植的assert

assert(n == 0 || newdest + n <= newsrc || newdest >= newsrc + n);

Here is a simple rewrite of memmove() , albeit not completely portable: 这是memmove()的简单重写,虽然不是完全可移植的:

void *mem_move(void *dest, const void *src, size_t n) {
    assert(n == 0 || (src != NULL && dest != NULL));  
    const char *newsrc = (const char *)src;
    char *newdest = (char *)dest;
    if (newdest <= newsrc || newdest >= newsrc + n) {
        /* Copying forward */
        for (size_t i = 0; i < n; i++) {
            newdest[i] = newsrc[i];
        }
    } else {
        /* Copying backwards */
        for (size_t i = n; i-- > 0;) {
            newdest[i] = newsrc[i];
        }
    }
    return dest;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM