Текущий код выравнивания неверен. Он определяет разницу выравнивания от нижней границы выравнивания и неправильно добавляет ее к значению указателя для достижения верхней границы выравнивания:
xxxxx000 + 000 = xxxxx000 (OK - no change)
xxxxx001 + 001 = xxxxx010 (WRONG)
xxxxx010 + 010 = xxxxx100 (WRONG)
xxxxx011 + 011 = xxxxx110 (WRONG)
xxxxx100 + 100 = xxxxy000 (OK - rounded up)
xxxxx101 + 101 = xxxxy010 (WRONG)
xxxxx110 + 110 = xxxxy100 (WRONG)
xxxxx111 + 111 = xxxxy110 (WRONG)
Разница с верхней границей является дополнением 2 разницы с нижней границей выравнивания, по модулю размера выравнивания:
xxxxx000 + 000 = xxxxx000 (OK - no change)
xxxxx001 + 111 = xxxxy000 (OK - rounded up)
xxxxx010 + 110 = xxxxy000 (OK - rounded up)
xxxxx011 + 101 = xxxxy000 (OK - rounded up)
xxxxx100 + 100 = xxxxy000 (OK - rounded up)
xxxxx101 + 011 = xxxxy000 (OK - rounded up)
xxxxx110 + 010 = xxxxy000 (OK - rounded up)
xxxxx111 + 001 = xxxxy000 (OK - rounded up)
Текущий код выравнивания можно исправить, добавив одну строку, чтобы преобразовать нижнюю разницу выравнивания в верхнюю разницу выравнивания :
/* Check and align buffer */
align_diff = (pj_size_t)buf;
if (align_diff & (PJ_POOL_ALIGNMENT-1)) {
align_diff &= (PJ_POOL_ALIGNMENT-1);
align_diff = PJ_POOL_ALIGNMENT - align_diff; // upper alignment
buf = (void*) (((char*)buf) + align_diff);
size -= align_diff;
}
В качестве альтернативы, верхняя разница выравнивания может быть определена непосредственно перед if
:
/* Check and align buffer */
align_diff = (pj_size_t)-(pj_size_t)buf & (PJ_POOL_ALIGNMENT-1);
if (align_diff != 0) {
buf = (void*) (((char*)buf) + align_diff);
size -= align_diff;
}
Можно утверждать (и было!), Что это менее читабельно чем оригинальная версия.
На самом деле, if
можно было бы опустить, поскольку добавление нуля не имеет значения:
/* Check and align buffer */
align_diff = (pj_size_t)-(pj_size_t)buf & (PJ_POOL_ALIGNMENT-1);
buf = (void*) (((char*)buf) + align_diff);
size -= align_diff;
Относительно align_diff = (pj_size_t)-(pj_size_t)buf & (PJ_POOL_ALIGNMENT-1);
, (pj_size_t)buf
преобразует указатель в целочисленный тип без знака -
отрицает значение, а начальный (pj_size_t)
преобразует отрицательный va lue к целому типу без знака, используя арифметику дополнения до 2 c. & (PJ_POOL_ALIGNMENT-1)
преобразует по модулю PJ_POOL_ALIGNMENT
эквивалентно % PJ_POOL_ALIGNMENT
(поскольку PJ_POOL_ALIGNMENT
является степенью 2).
Строго, чтобы избежать неопределенного поведения , указатель выше целое преобразование должно выполняться с использованием uintptr_t
(определяется #include <stdint.h>
) вместо pj_size_t
:
align_diff = (uintptr_t)-(uintptr_t)buf & (PJ_POOL_ALIGNMENT-1);
Что касается buf = (void*) (((char*)buf) + align_diff);
, арифметика указателя c недопустима для значений void *
(при минимум в стандартном C), поэтому (char*)buf
преобразует его в указатель на char
. Поскольку sizeof(char)
равен 1 байту по определению, + align_diff
продвигает указатель на align_diff
байтов по мере необходимости. (void*)
затем преобразует это обратно в void *
, прежде чем присвоить его обратно buf
. Этот (void*)
может быть опущен в C (но не в C ++), поэтому утверждение можно переписать в виде:
buf = (char*)buf + align_diff;
, что, возможно, более читабельно.