gcc:切换后如何最好地处理有关(无法访问)功能结束的警告?

gcc: how best to handle warning about (unreachable) end of function after switch?

当我以 c++11 标准编译以下代码时,它在 clang 和 gcc 上运行良好,但 gcc(我测试的所有版本 4.8.2、4.9.2、5.1.0)给出一个警告:

#include <iostream>

enum class FOO { A, B, C };

const char * bar(FOO f) {
  switch (f) {
    case FOO::A:
      return "A";
    case FOO::B:
      return "B";
    case FOO::C:
      return "C";
  }
}

int main() {
  unsigned int x;
  std::cin >> x;

  FOO f = static_cast<FOO>(x % 3);
  std::cout << bar(f) << std::endl;
}

警告是-Wreturn-type:

main.cpp: In function ‘const char* bar(FOO)’:
main.cpp:14:1: error: control reaches end of non-void function [-Werror=return-type]
 }
 ^
cc1plus: all warnings being treated as errors

即使使用 -O2-O3 优化,我仍然会收到警告——这是否意味着即使在高优化级别,gcc 也无法通过死代码消除 'end'功能?

值得注意的是,它没有给我有关未处理的开关案例的警告。

编辑:从 godbolt 的实验来看,似乎即使在高级别,它 也不会 死代码消除它。我不确定它是否 可以 或者 clang 是否可以。

在这样的函数中是否有一种在本地抑制此警告的好方法,或者抑制此警告以通常禁用警告的唯一方法?

编辑:我猜这个问题提出了一个自然语言律师问题,从答案来看:

Can a conforming compiler dead-code eliminate the "end" of the bar function in my listing? (Or 101010's version with return nullptr; appended?) Or does conforming to the standard require that it generate code to handle enum values that aren't part of the enum definition?

我认为它可以通过死代码消除这一点,但欢迎您证明我错了。

为了抑制警告,将您的代码更改为:

const char * bar(FOO f) {
  switch (f) {
    case FOO::A:
      return "A";
    case FOO::B:
      return "B";
    case FOO::C:
      return "C";
  }

  return nullptr;
  ^^^^^^^^^^^^^^^
}

或者:

const char * bar(FOO f) {
  switch (f) {
    case FOO::A:
      return "A";
    case FOO::B:
      return "B";
    case FOO::C:
      return "C";
    default:
      return nullptr;
      ^^^^^^^^^^^^^^^
  }
}

如果 f 不属于 switch 中提供的情况之一,编译器会警告您不会返回任何内容。非 void 函数的末尾下降而不返回会引入未定义的行为。

您的 bar 函数的结尾并非无法到达。如果我将代码更改为:

#include <iostream>

enum class FOO { A, B, C };

const char * bar(FOO f) {
  switch (f) {
    case FOO::A:
      return "A";
    case FOO::B:
      return "B";
    case FOO::C:
      return "C";
  }

  std::cout << "End Reached" << std::endl;
  //return nullptr;
}

int main() {
  unsigned int x = 10;
  FOO f = static_cast<FOO>(x);
  std::cout << bar(f) << std::endl;
}

输出:

End Reached

Live Demo

我会在函数末尾添加一个assert(false)

#include <cassert>

const char * bar(FOO f) {
  switch (f) {
    case FOO::A:
      return "A";
    case FOO::B:
      return "B";
    case FOO::C:
      return "C";
  }

  assert(false);
}

(请注意,将它添加为 default 情况不会那么好,因为这会在例如添加新枚举值时消除所有未处理的 switch 情况警告。)

这也有利于调试,因为当 bar 由于某种原因收到无效参数时,它会让您立即知道。

does this mean that even at high optimization levels, gcc cannot dead-code eliminate the 'end' of the function?

是的,因为它不是死代码。

标准允许您使用 static_cast<FOO>(static_cast<int>(FOO::B) | static_cast<int>(FOO::C)) 作为参数调用您的函数。您的交换机无法处理此问题。

你没有收到关于你的开关没有处理的警告的原因是警告会 way 太多误报。

肯定不止一个"best way"。这个答案通过消除 switch 来解决问题。

...并且这种特殊方式可能不是您所期望的方式。我冒着被否决的风险,但我仍然发布这个。底线是:有时 "paradigm shift" 解决方案在给定的应用程序中比任何解决方法和警告禁用效果更好。

switch是众所周知的"code smell"。我并不是说它总是不好,但有时你可以做得比切换更好,值得考虑替代方案。

在我们的例子中,bar 函数执行 translation(在语言意义上)。它在字典中查找一些键和 returns 一个翻译的词。 您可以使用标准容器来明确您的代码。经典的例子是std::map(在其他编程语言中叫做"dictionary"):

#include <map>

enum class FOO {
    A, B, C
};

// Might be a vector or even static array if your enumeration is contiguous
std::map<FOO, std::string> dict = {
    { FOO::A, "A" }, // maps ints to strings
    { FOO::B, "B" },
    { FOO::C, "C" },
};

int main() {
    auto it = dict.find(FOO::A);
    if (it == dict.cend()) {
        // Handle "no such key" case
    }
    auto a = it->second; // "A"
}

无论选择哪种解决方案,如果字典中没有某些键,就会出现问题。无论如何你应该处理这个案子。