8

Linux 内核中,我找到了以下代码:

static inline loff_t pos_from_hilo(unsigned long high, unsigned long low)
{
#define HALF_LONG_BITS (BITS_PER_LONG / 2)
    return (((loff_t)high << HALF_LONG_BITS) << HALF_LONG_BITS) | low;
}

该代码用于将系统调用参数组合成一个更广泛的变量,例如在 ia32 上,偏移量pwritev在两个 32 位寄存器中指定。

在 x64 上,loff_t并且unsigned long都是 64 位宽。在这种情况下,该high变量被忽略并且仅low被使用。在 ia32 上,loff_t是 64 位宽和unsigned long32 位宽。在这种情况下,两个参数highlow结合在一起。

我想知道为什么代码位移两次而不是一次。在提交消息和 LWN 文章中有更多关于此代码的信息:系统调用和 64 位架构,但没有对双位移位的解释。

4

1 回答 1

4

测试应用程序中的以下警告帮助我解决了这个问题:

test.c:8:27: warning: left shift count >= width of type [-Wshift-count-overflow]
    8 |     return (((loff_t)high << (2*HALF_LONG_BITS))) | low;

双位移位可防止未定义的行为。从C 规范

6.5.7 3) ... 如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义。

在 64 位机器上,两者loff_t都是long64 位宽。如果我们立即进行移位,我们将移位high64 位,根据上面的陈述,这是未定义的行为。分两步做就等于high0。


PS:我编写了一个测试程序来调查这个问题,令我惊讶的是,当我用一个移位替换两个移位时,我得到了不同的结果。

于 2021-08-06T15:30:04.957 回答