复制没有memcpy()的字节

时间:2017-06-03 20:46:31

标签: c memcpy

这是家庭作业。我想实现#include <stdio.h> #include <string.h> int get_width(char* word) { return 15 - strlen(word); } struct category { char *name; int namelen; int count; }; struct category createCat(char *name) { struct category cat = {name, get_width(name), 0}; return cat; } int main () { int c; int inside_word; int i; int p; struct category words = createCat("words"); struct category lines = createCat("lines"); struct category characters = createCat("characters"); struct category catList[] = {words, lines, characters}; while ((c = getchar()) != EOF) { characters.count++; if (c != '\n' && c != ' ' && c != '\t') { putchar(c); if (!inside_word) { inside_word = 1; words.count++; } } else { if (inside_word) printf("\n"); inside_word = 0; if (c == '\n') lines.count++; } } printf("\n%d words, %d lines, %d characters\n", words.count, lines.count, characters.count); for (i = 0; i < 3; i++) { printf("%s:%*s", catList[i].name, catList[i].namelen, " "); printf("%d", catList[i].count); for ( p = 0; p < catList[p].count; p++) printf("#"); printf("\n"); } return 0; } 。我被告知记忆区域不能重叠。实际上我不明白这意味着什么,因为这段代码工作正常,但存在内存重叠的可能性。怎么预防呢?

memcpy()

3 个答案:

答案 0 :(得分:2)

  

实际上我不明白什么意思是[重叠记忆]

考虑这个例子:

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

从程序的角度来看,srcsrc+n的地址范围不得与destdest+n的范围重叠。

  

如果存在内存重叠的可能性,如何防止它?

如果src的数字地址低于dest,则可以决定从后面复制重叠区域,从而使算法有效或不重叠。

注意:由于您正在执行memcpy,而不是strcpy,因此newdest[i]='\0'强制空终止是不正确的,需要将其删除。

答案 1 :(得分:2)

当源和目标内存块重叠时,如果循环从索引0开始复制一个元素,则它适用于dest < source,但不适用于dest > source(因为你在复制之前覆盖元素),反之亦然。

您的代码从索引0开始复制,因此您可以简单地测试哪些情况有效,哪些无效。请参阅以下测试代码;它显示了如何向前移动测试字符串失败,而向后移动字符串工作正常。此外,它还显示了从后向复制时如何正向移动测试字符串:

#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);
}

输出:

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!

因此,可以编写一个函数,该函数根据调用者是要向前还是向后复制来决定是从索引0还是从索引n开始复制。棘手的是找出调用者是向前还是向后复制,因为src上的指针算术和dest if (src < dest) copy_from_backward(...)实际上不允许在每种情况下使用(参见标准) ,例如draft}:

  

6.5.9平等运营商

     

当比较两个指针时,结果取决于相对值   指向的对象的地址空间中的位置。如果两个   指向对象或不完整类型的指针都指向同一个对象,   或者两者都指向同一个数组对象的最后一个元素,它们   比较平等。如果指向的对象是同一个成员   聚合对象,指向稍后声明的结构成员的指针   大于指向结构中先前声明的成员的指针,   和指向具有较大下标值的数组元素的指针进行比较   大于指向同一数组元素的指针   下标值。指向同一个union对象的成员的所有指针   比较平等。如果表达式P指向数组的元素   对象和表达式Q指向同一个的最后一个元素   数组对象,指针表达式Q + 1比大于P. In   在所有其他情况下,行为未定义

虽然我从来没有遇到src < dest没有给我想要的结果的情况,但是如果它们不属于同一个数组,那么比较两个指针实际上是未定义的行为。

因此,如果你问“如何防止它?”,我认为唯一正确的答案必须是:“它受调用者的影响,因为函数mem_copy无法决定它是否可以比较{{1} }和src正确。“

答案 2 :(得分:1)

重新实施memcpy()时存在一些问题:

  • 大小参数n的类型应为size_t。索引变量i应与size参数具有相同的类型。

  • 可以传递0的计数。实际上,在这种情况下,您的代码会正常运行,从assert()中移除测试。

  • 除非绝对必要,否则请避免丢弃const限定符。

  • 请勿在目的地末尾添加'\0',否则会导致缓冲区溢出。

以下是更正后的版本:

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;
}

关于源区域和目标区域之间的潜在重叠,如果目标指针大于源,但在源区域​​内,则代码的行为会令人惊讶:

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

将输出:

before: 12345
after: 111111

没有完全可移植的方法来测试这种重叠,但是在非常外的架构上,执行时间和代码大小的成本很低。 memcpy()的语义是假定库不执行此类测试,因此如果源和目标区域不可能重叠,程序员应该只调用此函数。如有疑问,请使用正确处理重叠区域的memmove()

如果你想为此添加一个assert,这里有一个最便携的:

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

这是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;
}