Понимание реализации memcpy()
Я искал реализацию memcpy.c, я нашел другой код memcpy. Я не мог понять, почему они делают (((адрес) s) | ((адрес) d) / c) & (sizeof (UINT) - 1)
#if !defined(__MACHDEP_MEMFUNC)
#ifdef _MSC_VER
#pragma function(memcpy)
#undef __MEMFUNC_ARE_INLINED
#endif
#if !defined(__MEMFUNC_ARE_INLINED)
/* Copy C bytes from S to D.
* Only works if non-overlapping, or if D < S.
*/
EXTERN_C void * __cdecl memcpy(void *d, const void *s, size_t c)
{
if ((((ADDRESS) s) | ((ADDRESS) d) | c) & (sizeof(UINT) - 1)) {
BYTE *pS = (BYTE *) s;
BYTE *pD = (BYTE *) d;
BYTE *pE = (BYTE *) (((ADDRESS) s) + c);
while (pS != pE)
*(pD++) = *(pS++);
}
else {
UINT *pS = (UINT *) s;
UINT *pD = (UINT *) d;
UINT *pE = (UINT *) (BYTE *) (((ADDRESS) s) + c);
while (pS != pE)
*(pD++) = *(pS++);
}
return d;
}
#endif /* ! __MEMFUNC_ARE_INLINED */
#endif /* ! __MACHDEP_MEMFUNC */
2 ответа:
Код проверяет, выровнены ли адреса соответствующим образом для
UINT
. Если это так, то код копируется с использованием объектовUINT
. Если нет, то код копируется с использованием объектовBYTE
.Тест работает, сначала выполняя побитовое или из двух адресов. Любой бит, который включен в любом адресе, будет включен в результате. Затем тест выполняется побитово и с помощью
sizeof(UINT) - 1
. Предполагается, что размер aUINT
является некоторой степенью двух. Тогда размер минус один имеет все младшие биты. Например, если размер равен 4 или 8, тогда на единицу меньше, чем это есть, в двоичном коде 112 или 1112. Если любой адрес не кратен размеруUINT
, то он будет иметь один из этих битов, и тест укажет его. (Обычно наилучшее выравнивание для целого объекта совпадает с его размером. Это не обязательно так. Современная реализация этого кода должна использовать_Alignof(UINT) - 1
вместо размера.)Копирование с объектами
Этот код, конечно, зависит от реализации; он требует поддержки со стороны реализации C, которая не является частью базового стандарта C, и зависит от специфических особенностей процессора, на котором он выполняется.UINT
происходит быстрее, поскольку на аппаратном уровне одна загрузка или хранилище инструкция загружает или сохраняет все байты aUINT
(вероятно, четыре байта). Процессоры обычно копируют быстрее при использовании этих инструкций, чем при использовании в четыре раза большего количества однобайтовых инструкций загрузки или хранения.Более продвинутая реализация
memcpy
может содержать дополнительные возможности, такие как:
- если один из адресов выровнен, а другой нет, используйте специальные инструкции load-unaligned для загрузки нескольких байтов с одного адреса, с обычными инструкциями хранения на другой адрес.
- если процессор имеет одну инструкцию с несколькими инструкциями данных, используйте эти инструкции для загрузки или хранения большого количества байтов (часто 16, возможно больше) в одной инструкции.
Код
((((ADDRESS) s) | ((ADDRESS) d) | c) & (sizeof(UINT) - 1))
Проверяет, Если либо
s
,d
, илиc
не выровнены по размеруUINT
.Например, если
s = 0x7ff30b14
,d = 0x7ffa81d8
, c =256
, иsizeof(UINT) == 4
, то:s = 0b1111111111100110000101100010100 d = 0b1111111111110101000000111011000 c = 0b0000000000000000000000100000000 s | d | c = 0b1111111111110111000101111011100 (s | d | c) & 3 = 0b00
Таким образом, оба указателя выровнены. Проще скопировать память между указателями, которыеи выровнены, и это делает это только с одной ветвью.
На многих архитектурах
*(UINT *) ptr
является намного быстрее, еслиptr
правильно выровнено по ширинеUINT
. На в некоторых архитектурах*(UINT *) ptr
действительно произойдет сбой, Еслиptr
не будет правильно выровнено.