Понимание реализации 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не будет правильно выровнено.