【问题标题】:"missing return statement", but I know it is there“缺少退货声明”,但我知道它在那里
【发布时间】:2012-03-23 04:06:45
【问题描述】:

假设我有以下功能:

// Precondition:  foo is '0' or 'MAGIC_NUMBER_4711'
// Returns:       -1 if foo is '0'
//                 1 if foo is 'MAGIC_NUMBER_4711'
int transmogrify(int foo) {
    if (foo == 0) {
        return -1;
    } else if (foo == MAGIC_NUMBER_4711) {
        return 1;
    }
}

编译器抱怨“缺少返回语句”,但我知道foo 永远不会有与0MAGIC_NUMBER_4711 不同的值,否则我的函数将没有定义的语义。

对此有什么更好的解决方案? 这真的是一个问题吗,即标准是怎么说的?

【问题讨论】:

  • 尽管我自己回答了这个问题,但我仍会坚持更多解决方案和强制性标准参考。
  • ".. 我知道 foo 的值永远不会不同于 0 或 MAGIC_NUMBER_4711。"我无法从您发布的代码中看出这一点。您为什么希望编译器能够做到?
  • @Fred:他不希望编译器能够做到这一点,他只是不希望它发出正确代码的警告。
  • @SteveJessop:我认为它是“符合标准的”意义上的“正确代码”,但如果输入与您预期的不同,则会给出未定义的行为。”
  • @Fred:确切地说,它是“正确的”,就像在 glibc 中实现 strlen 是“正确的”:某些输入会导致未定义的行为,而这些输入对于程序员来说是已知的(通过文档)。取决于可能或可能不“足够正确”的项目,但通常是。

标签: c++


【解决方案1】:

有时,您的编译器无法推断出您的函数实际上有 no 缺失返回。在这种情况下,有几种解决方案:

假设以下简化代码(尽管现代编译器会发现没有路径泄漏,只是示例性的):

if (foo == 0) {
    return bar;
} else {
    return frob;
}

重构你的代码

if (foo == 0) {
    return bar;
}
return frob;

如果您可以将 if 语句解释为一种防火墙或先决条件,这将非常有效。

中止()

if (foo == 0) {
    return bar;
} else {
    return frob;
}
abort(); return -1; // unreachable

相应地返回其他内容。该评论告诉其他程序员和您自己为什么会出现这种情况。

投掷

#include <stdexcept>

if (foo == 0) {
    return bar;
} else {
    return frob;
}

throw std::runtime_error ("impossible");

单一功能退出点的缺点

控制流程

有些人会退回到one-return-per-function a.k.a. single-function-exit-point 作为解决方法。这在 C++ 中可能会被视为过时的,因为您几乎永远不知道函数将真正退出的位置:

void foo(int&);

int bar () {
    int ret = -1;
    foo (ret);
    return ret;
}

看起来不错,看起来像 SFEP,但对第 3 方专有 libfoo 进行逆向工程显示:

void foo (int &) {
    if (rand()%2) throw ":P";
}

如果bar()nothrow,则此参数不成立,因此只能调用nothrow 函数。

复杂性

每个可变变量都会增加代码的复杂性,并对代码维护者的脑容量造成更大的负担。这意味着更多的代码和更多的状态需要测试和验证,反过来意味着你从维护者的大脑中吸取了更多的状态,反过来又意味着维护者的大脑用于重要事情的容量更少。

缺少默认构造函数

有些类没有默认构造,如果可能的话,您将不得不编写真正虚假的代码:

File mogrify() {
    File f ("/dev/random"); // need bogus init because it requires readable stream
    ...
}

仅仅为了宣布它,这真是一个黑客行为。

【讨论】:

  • "有时,您的编译器无法推断出您的函数实际上没有丢失返回" ...那您使用的是哪个编译器?对我来说,最好的解决方案是更改(和更新)编译器。
  • 明显的return (foo == 0) ? -1 : 1; 是否违反了您的“从不使用 SFEP”政策?无论如何,当transmogrify 本身记录了一个 nothrow 函数(并且返回类型构造起来很便宜)时,您描述的问题不适用,因此“C++ 中已过时”似乎是错误的。
  • 发出虚假和不正确警告的损坏编译器虽然相当普遍,但并不是使代码复杂化并使其更难阅读的好理由。
  • @Steve:不。我会尽量完善我的答案。
  • “这可能意味着不必要的性能损失”和“在没有测量的情况下永远不要优化”有些矛盾。如果不测量就优化是不正确的,那么仅仅因为它可能更慢而排除特定的代码编写方式是不正确的。因此,关于性能损失的评论充其量与手头的决定无关,最坏的情况是具有误导性。
【解决方案2】:

在 C89 和 C99 中,从不需要 return 语句。即使它是一个返回不同于void 的函数。

C99 只说:

(C99, 6.9.1p12 "如果到达终止函数的},并且调用者使用了函数调用的值,则行为未定义。"

在 C++11 中,标准说:

(C++11, 6.6.3p2) “从函数末尾流出相当于没有值的返回;这会导致返回值的函数出现未定义的行为”

【讨论】:

  • 非常有趣的笔记,谢谢。但我恐怕这个问题是关于 C++ 的。
  • @phresnel 它有 C 标签,但后来被删除了。
  • @phresnel:C++也是一样,函数的“脱落”就是UB。
  • @PlasmaHH 如果您阅读 C++ 引用,实际上会有细微差别:在 C 中,您还必须使用该值来调用 UB,而在 C++ 中,只需到达 } 就是 UB。
  • @ouah:对不起,它没有 C 标记。但在编辑之后,让我++投票。
【解决方案3】:

仅仅因为您可以告诉输入将只有两个值之一并不意味着编译器可以,因此预计它会生成这样的警告。

您有几个选项可以帮助编译器解决这个问题。

  • 您可以使用两个值是唯一有效的枚举值的枚举类型。然后编译器可以立即告诉两个分支之一必须执行并且没有丢失的返回。

  • 你可以在函数结束时abort

  • 您可以在函数末尾throw 一个适当的异常。

请注意,后两个选项比消除警告要好,因为它可以预见地向您显示什么时候违反了先决条件,而不是允许未定义的行为。由于该函数采用int 而不是类或枚举类型,因此有人使用两个允许值以外的值调用它只是时间问题,并且您希望在开发周期的早期捕获它们,而不是而不是因为它违反了函数的要求而将它们视为未定义的行为。

【讨论】:

  • 通常我也会努力争取难以出错的代码。问题代码只是示例性的,但在前提条件方面确实很好,+1。
【解决方案4】:

实际上编译器正在做它应该做的事情。

int transmogrify(int foo) {
    if (foo == 0) {
        return -1;
    } else if (foo == MAGIC_NUMBER_4711) {
        return 1;
    }
    // you know you shouldn't get here, but the compiler has
    // NO WAY of knowing that.  In addition, you are putting
    // great potential for the caller to create a nice bug.
    // Why don't you catch the error using an ELSE clause?
    else {
        error( "transmorgify had invalid value %d", foo ) ;
        return 0 ;
    } 
}

【讨论】:

  • 这当然是对的,但恐怕你的答案代码中的非代码部分以及注释部分不是对问题的答案(这是“你做什么在这种情况下?”,而不是“为什么编译器会警告我?”或“如何让编译器闭嘴”)。我个人的观点是,如果代码应该是适用于一般类型的一般解决方案(如果您返回系统资源怎么办?那么您移动您的调用者站点的错误)。还是您的错误函数是程序端点?
猜你喜欢
  • 2012-10-04
  • 2013-07-11
  • 1970-01-01
  • 1970-01-01
  • 2016-06-23
  • 1970-01-01
  • 2011-03-30
  • 2020-09-15
  • 1970-01-01
相关资源
最近更新 更多