Портативно, вы должны копировать на основе выравнивания, что не обязательно uint64_t
. Теоретически, вы должны использовать uint_fast8_t
, но на практике это, по-видимому, 1 байт большого размера, 1 байт выровнен в большинстве систем. Если мобильность не требуется, вы можете придерживаться uint64_t
.
Следующая проблема состоит в том, что указатели, переданные в memcpy
, не обязательно указывают на выровненный адрес, в соответствии с требованием стандартной функции работать независимо от выравнивания. Так что вам придется сделать что-то вроде этого:
size_t prealign = (uintptr_t)src % _Alignof(uint64_t);
if(prealign != 0)
{
// copy bytes up to next aligned address
}
То же самое для пункта назначения и то же самое для конца данных.
что, насколько я понимаю (поправьте меня, если я ошибаюсь), может нарушить строгое предположение о псевдонимах и привести к неопределенному поведению.
Правильно. Таким образом, чтобы скопировать чанки uint64_t
, вы должны либо написать код на встроенном ассемблере, либо отключить строгий псевдоним нестандартным способом при компиляции, например gcc -fno-strict-aliasing
.
.
"Настоящая" библиотека memcpy рассматривается компилятором как особый случай, как и многие другие подобные библиотечные функции. memcpy(&foo, &bar, sizeof(int));
будет, например, переведен в одну mov
инструкцию, встроенную в код вызывающего абонента, без вызова memcpy
вообще.
Еще одно замечание по поводу псевдонимов указателей заключается в том, что вы должны restrict
квалифицировать указатели, как это было сделано с реальной memcpy. Это говорит компилятору, что он может предполагать, что указатели dest
и src
не совпадают или что они перекрываются, что означает, что компилятору не нужно добавлять проверки или служебный код для этого сценария.
Забавно, когда я пишу следующую наивную функцию копирования:
#include <stdint.h>
#include <stddef.h>
void foocpy (void* dst, const void* src, size_t n)
{
uint8_t* u8_dst = dst;
const uint8_t* u8_src = src;
for(size_t i=0; i<n; i++)
{
u8_dst[i] = u8_src[i];
}
}
Затем компилятор дает мне тонну довольно неэффективного машинного кода. Но если я просто добавлю restrict
к обоим указателям, все функции будут заменены следующим:
foocpy:
test rdx, rdx
je .L1
jmp memcpy
.L1:
ret
Это еще раз показывает, что встроенный memcpy
рассматривается компилятором как особая снежинка.