【问题标题】:What objective circumstances would preclude using a return value also as a diagnostic?哪些客观情况会排除使用返回值作为诊断的可能性?
【发布时间】:2016-01-26 16:00:34
【问题描述】:

我正在浏览 Linaro ODP 框架,看到这段代码 sn-p,

static int find_block(const char *name, uint32_t *index)
{
    uint32_t i;

    for (i = 0; i < ODP_CONFIG_SHM_BLOCKS; i++) {
        if (strcmp(name, odp_shm_tbl->block[i].name) == 0) {
            /* found it */
            if (index != NULL)                                                                                                                         
                *index = i;

            return 1;
        }
    }

    return 0;
}

在这里,我们可以返回i 的值,而不是更新*index,以实现与下面相同的功能。

static int find_block(const char *name)
{
    uint32_t i;

    for (i = 0; i < ODP_CONFIG_SHM_BLOCKS; i++) {
        if (strcmp(name, odp_shm_tbl->block[i].name) == 0) {
            /* found it */
            return i;
        }
    }

    return -1;
}

除了品味、风格和编码指南之外,还有什么客观原因可以说明为什么必须选择其中一个而不能选择另一个?

【问题讨论】:

  • 该函数已经返回一个布尔结果。除了通过指针参数之外,您还能将另一个值传递给调用者吗?
  • 您可能会返回索引值,或类似 -1 的“未找到”。然后,您需要根据返回的值在代码中进行分支。这就是许多 UNIX 系统调用的作用。
  • 这显然是一个只有内部链接的函数(参见static)。看看调用它的代码,这可能解释了这个选择。
  • 合理的问题,并不完全基于意见,所以近距离投票是错误的——在我看来;-)。显然,正如在 cmets 中发生的那样,人们可以讨论(目标!)赞成和反对的原因。我今天看到了很多更糟糕的问题,我很高兴看到并讨论这个问题。
  • 在第一个函数中,返回的索引是一个无符号整数。没有办法让它返回一个负值来表示失败。第二个函数只是将用于 int 的类型更改为 int,因此如果要查找的索引优于 INT_MAX,它的行为将无法预测。

标签: c return-value diagnostics


【解决方案1】:

标题中问题的简短回答:

  • 如果返回类型范围内的所有值都被视为有效(在非错误、非诊断意义上)返回值,则不能将返回值也用作诊断。
  • 使用魔术值而不是布尔标准值作为错误报告(如果这些标准值可能是函数的有效结果,则需要这样做)会创建(略微)泄漏的抽象。

当一个人想要有多个返回值,或者更有可能有条件地有多个返回值时,就会使用你所看到的。

你的假设...

在这里,我们可以返回i 的值,而不是更新*index,以实现与下面相同的功能。

...错了:

*index 是一个无符号整数,因此有效值范围为 0 到 2^(32-1)。 这些值中的每一个似乎都是有效的索引值。因此,您有 2^32 个有效结果。

为了指示失败,不使用具有多个返回值的“技巧”,您需要返回一个没有有效含义的值,因此能够携带特殊含义作为失败的指示。假设只有失败的指示(并且没有详细的错误值),您将有 1 个 invalid 结果值。

这会留下 2^32 + 1 个可能 个结果值,它不适合 32 位无符号整数(也不适合您的示例使用的 int) .

一个可能的解决方案是扩展返回值的范围,例如通过使用 64 位无符号整数。然后您可以使用值 2^32(超出有效值范围)来指示失败:

#define MAGIC_FAILURE (((uint64_t) 1) << 32)
static uint64_t find_block(const char *name)
{
    uint32_t i;
    for (i = 0; i < ODP_CONFIG_SHM_BLOCKS; i++) {
        if (strcmp(name, odp_shm_tbl->block[i].name) == 0) {
            /* found it */
            return i;
        }
    }
    // return magic value indicating failure
    return MAGIC_FAILURE;
}

我认为这是一种不好的做法,因为要理解该功能,您还需要在脑海中拥有神奇的价值。还要考虑调试这个函数(或一些调用者):检查原始函数的返回值(通过指针返回数据)并查看布尔值(使用哪个)要容易得多,而不必对返回值进行分解上面的函数来判断它是否有效。

最后一点:不过,在这种情况下,我会采用不同的方法:

static uint32_t find_block(const char *name)
{
    uint32_t i;
    for (i = 0; i < ODP_CONFIG_SHM_BLOCKS; i++) {
        if (strcmp(name, odp_shm_tbl->block[i].name) == 0) {
            /* found it */
            return i;
        }
    }
    return ODP_CONFIG_SHM_BLOCKS;
}

可以使用它,因为正如循环的实现所表明的那样,并非每个 32 位无符号整数都是有效索引(这为指示失败的幻数留出了空间)。使用一个明显相关的常数是比一些随机魔法值更好的选择。

虽然也考虑调用站点:

uint32_t index;
if (! find_block("foo", &index)) {
  // OMG
}
// all nice

这里你只有函数名,当你想通过它的名字找到一个块时,你只需要它。

int32_t index = find_block("foo");
if (index == ODP_CONFIG_SHM_BLOCKS) {
  // OMG
}
// all nice

另一方面,您需要两个“事物”才能通过名称查找块:函数名称和魔法值。这可以被认为是抽象中的泄漏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-09-25
    • 2020-10-10
    • 1970-01-01
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多