使用 "|" 而不是 "=" 的具体原因

3

我目前在审查 Linux 内核中的一些代码(热管理)。在某些地方,返回值用于发出错误信号,在函数开始时将其设置为 0。然后,调用可能失败的函数时,使用|=而不是=来将其设置为新值。以下是一个例子:

int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id,
                int *temperature)
{
    u32 temp;
    int ret;

    ret = ti_bandgap_validate(bgp, id);
    if (ret)
        return ret;

    spin_lock(&bgp->lock);
    temp = ti_bandgap_read_temp(bgp, id);
    spin_unlock(&bgp->lock);

    ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
    if (ret)
        return -EIO;

    *temperature = temp;

    return 0;
}
ti_bandgap_validate 的定义如下:
/**
 * ti_bandgap_validate() - helper to check the sanity of a struct ti_bandgap
 * @bgp: struct ti_bandgap pointer
 * @id: bandgap sensor id
 *
 * Checks if the bandgap pointer is valid and if the sensor id is also
 * applicable.
 *
 * Return: 0 if no errors, -EINVAL for invalid @bgp pointer or -ERANGE if
 * @id cannot index @bgp sensors.
 */
static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id)

因此,如果我的推理正确,在调用 ti_bandgap_adc_to_mcelsius() 时,ret 的值必须为 0(否则该函数已经退出)。那么在这里使用 |= 而不是 = 的原因是什么呢?使用一个全零模式进行“或”运算将只返回正常模式。这是某种针对通常情况的优化吗,即函数没有返回失败(即返回值为 0)?还是我漏掉了其他差异?此代码在 ARM 架构上运行,因此可能与该平台的特定优化有关。

3
听起来是个好习惯:这样,阅读代码的人不需要查看周围的上下文就可以确信在任何情况下都不会覆盖非零错误返回。 - Charles Duffy
3
看起来这是一些剩余或不需要的内容。它在一个更新的提交中被删除了https://github.com/torvalds/linux/commit/e34238bf98a2ad9deda9444d69903889eced0519#diff-a50a7076f4668c922ed31669c390eb17R836 - nnn
2
这段代码在ARM上会比较慢,因为需要记住先前的值,而不是只使用返回寄存器。此外,在最终的“return”中,函数甚至没有返回值。我认为这并不更加健壮。根据@nnn的评论,它已被删除,可能是一个废弃的错误处理概念的一部分。 - too honest for this site
3
该提交注释中的特定部分是“避免在错误值上使用|=操作符造成混淆。” - nnn
1
@Charles Duffy:不,这实际上是一种极其糟糕的形式。首先,从抽象的角度来看,它只是一个错误:它基本上试图对标准的 E... 错误常量进行 OR 运算,这是没有意义的。保留一个毫无意义的非零值并不比完全丢失它更好。其次,从具体的角度来看,它强制代码的读者四处搜索并手动推断出 ret 在那一点上保证为零,从而使 |= 等同于 =。强迫读者做出这样不必要的努力从来都不是一个好的形式。 - AnT stands with Russia
@KeithThompson 正如Bugs Bunny可能会说的那样,“我知道我应该在阿尔伯克基左转的”。除了一些我所知道的遗留直接移植的代码库之外,没有Java并行。 - Edwin Buck
2个回答

3
在这种情况下,没有理由使用|=。然而,如果你正在跟踪可能出错并且返回错误代码的多个函数,则模式为:
boolean error = false

error |= doFirstStep(...);
error |= doSecondStep(...);
error |= doThirdStep(...);

if (error) {
  printf("error occurred: %s\n", strerror(errno));
}

在C语言中,这是一种不太常见的模式,偶尔会在与C有某些关联的语言中使用。在C语言中,大量的C库函数返回一个“错误码”,通常情况下,成功操作返回0。
当使用这种模式时,用户依赖于零作为成功条件的返回值。这意味着上述的log_and_recover()可能会从error.h的静态变量中获取错误消息,这在C的 #include <error.h>例程中很常见。
----继续解释为什么经常在int字段中使用该模式----
你还会看到在一个int中保存错误的这种模式。
int error = 0; // or a variable that's defined to zero

error |= doFirstStep(...);
error |= doSecondStep(...);
error |= doThirdStep(...);

if (error != 0) {
  ... some error handling ...
}

当您看到这个时,它与上面的想法相同,但开发者将两种模式结合在一起。 位字段模式通常用于打包配置参数,现在被利用来打包多种错误。 通常,在这种情况下,您可以找到类似以下列表的错误:
#define ERROR_NO_DISK  (1<<1);
#define ERROR_NO_NETWORK (1<<2);
#define ERROR_NO_SANITY (1<<3);

大多数情况下,一个项目出现多个错误不是很明智的做法,应该将其归为一个错误处理;但有时候由于错误抑制非常重要,所以会这样做。例如,如果客户端向主机发送消息失败,可以将各种失败(如“无法打开套接字”,“无法写入套接字”,“无法复制到缓冲区”等)合并成一个通用的“X发送失败”错误。从某个层面上讲,整个顶级工作流程已经失败了,而且如果需要,还可以获得一些失败的详细信息。

使用真正的布尔变量会更好,因为它更能传达忽略实际返回代码的意图。但无论如何,忽略实际返回代码都是属于草图质量代码的事情。在生产级别的代码中,这样做看起来并不好。 - AnT stands with Russia
Java有什么相关性? - Keith Thompson
@AnT 我同意使用布尔变量会更好看,但在遗留的 C 代码中这些变量很短缺。 - Edwin Buck
@KeithThompson 去掉了Java的引用。老实说,我不知道我是如何读到上面的内容并在脑海中将其塞入面向对象的思维模式中的 ;) - Edwin Buck
@Edwin Buck:如果标准的 E... 代码遵循了您帖子第二部分描述的“位域模式”,那么使用 |= 的确是完全合理的。此外,即使已知 ret 为零,我也会坚持使用 |= 作为良好编程实践的一部分。然而,标准的 E... 代码并不符合“位域模式”,这就是为什么您答案第二部分的主要前提与问题无关的原因。 - AnT stands with Russia
@AnT 这就是我提到“你也看到了这个模式”的原因。当然,使用错误代码作为位字段并不是标准的做法,但这样的用法可能会证明在它们被使用时|=是合理的。如果它们被使用了,虽然这有点牵强附会,而且(在我看来)不是最好的方法,但我提供了(站不住脚的)理由。 - Edwin Buck

2
在这种情况下使用|=没有理由。正如您所说,当到达此行时,ret必须为0,否则它将在早期返回。它还引入了一个不必要的额外操作。
正如评论中提到的那样,实际上已经将此运算符更改为=,在此提交中进行了更改。提交注释如下:

避免对错误值使用|=。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接