检查并对齐缓冲区

Check and align buffer

我正在尝试了解如何检查指针是否对齐并最终对齐它。

为了理解它我采用了这个函数:

#define PJ_POOL_ALIGNMENT 8

PJ_DEF(pj_pool_t*) pj_pool_create_on_buf(const char *name,
                 void *buf,
                 pj_size_t size)
{
#if PJ_HAS_POOL_ALT_API == 0
struct creation_param param;
pj_size_t align_diff;

PJ_ASSERT_RETURN(buf && size, NULL);

if (!is_initialized) {
if (pool_buf_initialize() != PJ_SUCCESS)
    return NULL;
is_initialized = 1;
}

/* Check and align buffer */
align_diff = (pj_size_t)buf;
if (align_diff & (PJ_POOL_ALIGNMENT-1)) {
    align_diff &= (PJ_POOL_ALIGNMENT-1);
    buf = (void*) (((char*)buf) + align_diff);
    size -= align_diff;
}

param.stack_buf = buf;
param.size = size;
pj_thread_local_set(tls, &param);

return pj_pool_create_int(&stack_based_factory, name, size, 0, 
              pj_pool_factory_default_policy.callback);
#else
PJ_UNUSED_ARG(buf);
return pj_pool_create(NULL, name, size, size, NULL);
#endif
}

显然我感兴趣的部分是/*检查并对齐缓冲区*/ 我认为我唯一理解的是:

让我们关注一下if。 这想要验证缓冲区是否与 8 字节多地址对齐。如果if的条件不对齐,一个0以外的数returns,然后进行对齐,否则,也只有一个bit为1,跳过if .为了获得这个结果,他们将变量 PJ_POOL_ALIGNMENT 设置为 7 (0111) 并以此与分配缓冲区的地址进行 AND 运算。考虑到如果地址不对齐,我想得到一个非0的数字,操作如下。

0000... 0111 和

xxxx。 . . x100


0000... 0100 未对齐

如果最后 3 位中的任何一位有 1(或更多 1),因此我知道它没有与 8 字节块对齐:x AND 1 = 0,则 if 将为真。然后进入修正块

但是 if 块对我来说是晦涩难懂的。 有人可以确认我的推理是否正确并让我理解这个块。

当前对齐代码不正确。它确定与下对齐边界的对齐差异,并错误地将其添加到指针值以达到上对齐边界:

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)将取反值转换为使用 2 的补码算法的无符号整数类型。 & (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);void * 值不允许指针运算(至少在标准 C 中),因此 (char*)buf 将其转换为指向 char 的指针。由于根据定义 sizeof(char) 是 1 个字节,因此 + align_diff 根据需要将指针前进 align_diff 个字节。 (void*) 然后将其转换回 void *,然后再将其分配回 buf。这个 (void*) 在 C 中可以省略(但在 C++ 中不能),因此该语句可以重写为:

buf = (char*)buf + align_diff;

这可以说更具可读性。