Моя собственная функция для копирования памяти на C

Я написал функцию, которая копирует память из одного места назначения в другое. Я бы хотел, чтобы вы посмотрели мой код и сказали, нужно ли мне что-то исправить. Это код:

void* memCopy(void* destination, const void* source, size_t size)
{
    double *a = (double*)source, *b = (double*)destination;
    void* end = a + (size / sizeof(double));
    while ((void*)a < end)
    {
        *b = *a;
        ++a;
        ++b;
    }
    char* a2 = (char*)a;
    char* b2 = (char*)b;
    end = a2 + (size % sizeof(double));
    while (a2 < end)
    {
        *b2 = *a2;
        ++a2;
        ++b2;
    }
    return destination;
}

я использовал double* чтобы сделать функцию быстрее.

2 ответа
2

Ваш код не компилируется чисто с GCC:

move.c: In function 'memCopy':
move.c:16:15: warning: comparison of distinct pointer types lacks a cast
     while (a2 < end)
               ^

Также 2 кратких момента:

  1. Зачем изобретать велосипед? Большинство реализаций memcpy, memmove и т. Д. В стандартных библиотеках будут правильно и эффективно реализованы, во многих случаях без использования чистого C.
  2. Ваш код предполагает, что и источник, и данные правильно выровнены для доступа как double. Если это не так, то в зависимости от платформы код может а) работать плохо, б) давать неверные результаты или в) просто давать сбой, если я не ошибаюсь. Он работает (очевидно, правильно) на моем ПК, но это не гарантия переносимости.

Реализация FreeBSD показывает, как это можно сделать, предполагая, что отображение указателей на uintptr_t дает значения, которые можно соответствующим образом замаскировать — это верно для соответствующих целей кода, но не может быть универсальным.

  • 1

    Да, точно. Я предполагаю, что Иван тестировал некоторую форму x86, которая очень снисходительна к невыровненному доступу. Большинство процессоров в лучшем случае будут отказываться от шины.

    — Тоби Спейт

  • Марк, хм, FreeBSD использует if ((unsigned long)dst < (unsigned long)src) {. Я ожидал if ((uintptr_t)dst < (uintptr_t)src) {, что это ошибка, когда uintptr_t шире, чем unsigned long.

    — chux — Восстановить Монику

  • @ chux-ReinstateMonica Я сомневаюсь, что uintptr_t был даже блеском в глазах разработчика C, когда этот код был разработан — он довольно древний. Я подозреваю, что пока он не выйдет из строя, все останется как есть. Как упоминается в вашем ответе, он действительно имеет дело с перекрытиями путем соответствующего направленного копирования. Я не стал комментировать использование двойного слова, как вы, поскольку я не был полностью уверен в своей правоте, но я согласен с вашими опасениями.

    — Марк Блюмел

  • 1

    @MarkBluemel Моя рекомендация не вводит uintptr_t использование, поскольку связанный код уже использует uintptr_t в if ((t | (uintptr_t)dst) & wmask) {. К сожалению, он не использует uintptr_t равномерно.

    — chux — Восстановить Монику


В дополнение к хорошему ответу @Mark Bluemel:

Перекрывать

Разница между memcpy() а также memmove() это возможность обрабатывать перекрывающиеся буферы. Обратите внимание на ключевое слово restrict.

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);

restrict примерно означает, что доступ к буферу не зависит от других действий.

memCopy() код не копирует хорошо перекрывающиеся буферы, когда source предшествует destination. Это больше похоже на

void* memCopy(void* restrict destination, const void* restrict source, size_t size)

Используя restrict, компилятор может вызывать дополнительные оптимизации.

Решение действовать как memmove() включает зацикливание вверх или вниз в зависимости от того, что больше source или же destination. Хитрость в том, что это сравнение величия указателя, безусловно, невозможно в C. memmove(), как библиотечная функция, имеет доступ к информации, которой нет у C. Тем не менее, сравнение указателей, преобразованных в целое число, обычно достаточный.

Типы

double является нет путь идти. *b = *a; не гарантируется копирование битовой комбинации или потенциальное инициирование исключения сигнализации не числа. Лучше избегать проблем с плавающей запятой. Вместо этого используйте широкий беззнаковый целочисленный тип, возможно uintmax_t, unsigned long long, или же uint64_t. (Плюсы и минусы каждого. Я бы выбрал uint64_t когда возможно.)

На древних машинах, *b2 = *a2; может вызвать ловушка. Вместо этого используйте unsigned char *a2; unsigned char *b2;

Нулевой размер

memCopy(..., ..., 0) правильно обрабатывается. Браво! — здесь удалось избежать распространенной ошибки.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *