Понимание реализации 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 10

2 ответа:

Код проверяет, выровнены ли адреса соответствующим образом для UINT. Если это так, то код копируется с использованием объектов UINT. Если нет, то код копируется с использованием объектов BYTE.

Тест работает, сначала выполняя побитовое или из двух адресов. Любой бит, который включен в любом адресе, будет включен в результате. Затем тест выполняется побитово и с помощью sizeof(UINT) - 1. Предполагается, что размер a UINT является некоторой степенью двух. Тогда размер минус один имеет все младшие биты. Например, если размер равен 4 или 8, тогда на единицу меньше, чем это есть, в двоичном коде 112 или 1112. Если любой адрес не кратен размеру UINT, то он будет иметь один из этих битов, и тест укажет его. (Обычно наилучшее выравнивание для целого объекта совпадает с его размером. Это не обязательно так. Современная реализация этого кода должна использовать _Alignof(UINT) - 1 вместо размера.)

Копирование с объектами UINT происходит быстрее, поскольку на аппаратном уровне одна загрузка или хранилище инструкция загружает или сохраняет все байты a UINT (вероятно, четыре байта). Процессоры обычно копируют быстрее при использовании этих инструкций, чем при использовании в четыре раза большего количества однобайтовых инструкций загрузки или хранения.

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

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