Я написал функцию, которая копирует память из одного места назначения в другое. Я бы хотел, чтобы вы посмотрели мой код и сказали, нужно ли мне что-то исправить. Это код:
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 ответа
Ваш код не компилируется чисто с GCC:
move.c: In function 'memCopy':
move.c:16:15: warning: comparison of distinct pointer types lacks a cast
while (a2 < end)
^
Также 2 кратких момента:
- Зачем изобретать велосипед? Большинство реализаций memcpy, memmove и т. Д. В стандартных библиотеках будут правильно и эффективно реализованы, во многих случаях без использования чистого C.
- Ваш код предполагает, что и источник, и данные правильно выровнены для доступа как double. Если это не так, то в зависимости от платформы код может а) работать плохо, б) давать неверные результаты или в) просто давать сбой, если я не ошибаюсь. Он работает (очевидно, правильно) на моем ПК, но это не гарантия переносимости.
Реализация FreeBSD показывает, как это можно сделать, предполагая, что отображение указателей на uintptr_t
дает значения, которые можно соответствующим образом замаскировать — это верно для соответствующих целей кода, но не может быть универсальным.
В дополнение к хорошему ответу @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)
правильно обрабатывается. Браво! — здесь удалось избежать распространенной ошибки.
Да, точно. Я предполагаю, что Иван тестировал некоторую форму 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, когда этот код был разработан — он довольно древний. Я подозреваю, что пока он не выйдет из строя, все останется как есть. Как упоминается в вашем ответе, он действительно имеет дело с перекрытиями путем соответствующего направленного копирования. Я не стал комментировать использование двойного слова, как вы, поскольку я не был полностью уверен в своей правоте, но я согласен с вашими опасениями.
— Марк Блюмел
@MarkBluemel Моя рекомендация не вводит
uintptr_t
использование, поскольку связанный код уже используетuintptr_t
вif ((t | (uintptr_t)dst) & wmask) {
. К сожалению, он не используетuintptr_t
равномерно.— chux — Восстановить Монику