使编译器假定所有情况都在默认情况下在switch中处理

时间:2019-01-11 14:48:54

标签: c++ switch-statement g++

让我们从一些代码开始。这是我程序的极为简化的版本。

#include <stdint.h>

volatile uint16_t dummyColorRecepient;

void updateColor(const uint8_t iteration)
{
    uint16_t colorData;
    switch(iteration)
    {
    case 0:
        colorData = 123;
        break;
    case 1:
        colorData = 234;
        break;
    case 2:
        colorData = 345;
        break;
    }
    dummyColorRecepient = colorData;
}

// dummy main function
int main()
{
    uint8_t iteration = 0;
    while (true)
    {
        updateColor(iteration);
        if (++iteration == 3)
            iteration = 0;
    }
}

程序编译时显示警告:

./test.cpp: In function ‘void updateColor(uint8_t)’:
./test.cpp:20:25: warning: ‘colorData’ may be used uninitialized in this function [-Wmaybe-uninitialized]
     dummyColorRecepient = colorData;
     ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~

如您所见,绝对可以确定变量iteration始终为012。但是,编译器不知道这一点,并且假定开关可能未初始化colorData。 (编译期间进行的大量静态分析都无济于事,因为实际程序分散在多个文件中。)

当然,我可以只添加一个默认语句,例如default: colorData = 0;,但这会为程序增加24个字节。 这是一个用于微控制器的程序,我对其大小有严格的限制。

我想通知编译器,保证此开关可以覆盖iteration的所有可能值。

5 个答案:

答案 0 :(得分:10)

  

如您所见,可以绝对确定变量迭代始终为0、1或2。

从工具链的角度来看,这是不正确的。您可以从其他地方调用此函数,甚至可以从另一个翻译单元调用。强制执行约束的唯一位置是在main中,即使执行约束的方式也使得编译器难以推断可能

不过,出于我们的目的,让我们看一下您不打算链接任何其他翻译单元,并且我们想告诉工具链有关此的信息。好吧,幸运的是,我们可以!

如果您不介意携带不便,那么GCC's __builtin_unreachable built-in会通知您,default案件预计不会发生,应视为不可到达。我的海湾合作委员会很聪明,知道这意味着colorData永远都不会被未初始化,除非所有的赌注都被取消了。

#include <stdint.h>

volatile uint16_t dummyColorRecepient;

void updateColor(const uint8_t iteration)
{
    uint16_t colorData;
    switch(iteration)
    {
    case 0:
        colorData = 123;
        break;
    case 1:
        colorData = 234;
        break;
    case 2:
        colorData = 345;
        break;

    // Comment out this default case to get the warnings back!
    default:
        __builtin_unreachable();
    }
    dummyColorRecepient = colorData;
}

// dummy main function
int main()
{
    uint8_t iteration = 0;
    while (true)
    {
        updateColor(iteration);
        if (++iteration == 3)
            iteration = 0;
    }
}

live demo

这不会添加实际的default分支,因为其中没有“代码”。实际上,当我使用带有-O2的x86_64 GCC将其插入Godbolt时,该程序比没有该程序的小-从逻辑上讲,您刚刚添加了一个 major < / em>优化提示。

实际上有a proposal to make this a standard attribute in C++,因此将来可能会成为更具吸引力的解决方案。

答案 1 :(得分:5)

使用“立即调用的lambda表达式”习惯用法和assert

void updateColor(const uint8_t iteration)
{
    const auto colorData = [&]() -> uint16_t
    {
        switch(iteration)
        {
            case 0: return 123;
            case 1: return 234;
        }

        assert(iteration == 2);
        return 345;
    }();

    dummyColorRecepient = colorData;
}
  • lambda表达式使您可以将colorData标记为constconst变量必须始终被初始化。

  • assert + return语句的组合使您可以避免警告并处理所有可能的情况。

  • assert不在发布模式下进行编译,从而避免了开销。


您还可以分解该功能:

uint16_t getColorData(const uint8_t iteration)
{
    switch(iteration)
    {
        case 0: return 123;
        case 1: return 234;
    }

    assert(iteration == 2);
    return 345;
}

void updateColor(const uint8_t iteration)
{
    const uint16_t colorData = getColorData(iteration);
    dummyColorRecepient = colorData;
}

答案 2 :(得分:3)

只需在其中一种情况下添加default标签,就可以在没有警告的情况下进行编译:

switch(iteration)
{
case 0:
    colorData = 123;
    break;
case 1:
    colorData = 234;
    break;
case 2: default:
    colorData = 345;
    break;
}

或者:

uint16_t colorData = 345;
switch(iteration)
{
case 0:
    colorData = 123;
    break;
case 1:
    colorData = 234;
    break;
}

尝试两者,并使用两者中较短的一个。

答案 3 :(得分:1)

我知道有一些很好的解决方案,但是可以选择If在编译时知道您的值,而不是switch语句,您可以将constexprstatic function template一起使用和几个enumerators;在单个类中看起来像这样:

#include <iostream>

class ColorInfo {
public:
    enum ColorRecipient {
        CR_0 = 0,
        CR_1,
        CR_2
    };

    enum ColorType {
        CT_0 = 123,
        CT_1 = 234,
        CT_2 = 345
    };

    template<const uint8_t Iter>
    static constexpr uint16_t updateColor() {

        if constexpr (Iter == CR_0) {
            std::cout << "ColorData updated to: " << CT_0 << '\n';
            return CT_0;
        }

        if constexpr (Iter == CR_1) {
            std::cout << "ColorData updated to: " << CT_1 << '\n';
            return CT_1;
        }

        if constexpr (Iter == CR_2) {
            std::cout << "ColorData updated to: " << CT_2 << '\n';
            return CT_2;
        }
    }
};

int main() {
    const uint16_t colorRecipient0 = ColorInfo::updateColor<ColorInfo::CR_0>();
    const uint16_t colorRecipient1 = ColorInfo::updateColor<ColorInfo::CR_1>();
    const uint16_t colorRecipient2 = ColorInfo::updateColor<ColorInfo::CR_2>();

    std::cout << "\n--------------------------------\n";
    std::cout << "Recipient0: " << colorRecipient0 << '\n'
              << "Recipient1: " << colorRecipient1 << '\n'
              << "Recipient2: " << colorRecipient2 << '\n';

    return 0;
}

cout中的if constexpr语句仅出于测试目的而添加,但这应说明另一种可能的方法,而不必使用switch语句,前提是您的值将在编译时知道。如果这些值是在运行时生成的,则不能完全确定是否有一种方法可以使用constexpr来实现这种代码结构,但是如果有其他方面的经验,我将不胜感激可以详细说明如何使用constexpr值对runtime进行此操作。但是,由于没有magic numbers,因此该代码可读性强。


-更新-

在阅读了有关constexpr的更多信息后,我注意到它们可以用于生成compile time constants。我还了解到它们无法生成runtime constants,但可以在runtime function中使用它们。通过将static function添加到类中,我们可以采用上述类结构并将其在运行时函数中使用:

static uint16_t colorUpdater(const uint8_t input) {
    // Don't forget to offset input due to std::cin with ASCII value.
    if ( (input - '0') == CR_0)
        return updateColor<CR_0>();
    if ( (input - '0') == CR_1)
        return updateColor<CR_1>();
    if ( (input - '0') == CR_2)
        return updateColor<CR_2>();

    return updateColor<CR_2>(); // Return the default type
}

但是,我想更改两个函数的命名约定。我将第一个函数命名为colorUpdater(),上面刚刚显示的这个新函数将命名为updateColor(),因为这样看起来更直观。因此,更新的类现在将如下所示:

class ColorInfo {
public:
    enum ColorRecipient {
        CR_0 = 0,
        CR_1,
        CR_2
    };

    enum ColorType {
        CT_0 = 123,
        CT_1 = 234,
        CT_2 = 345
    };

    static uint16_t updateColor(uint8_t input) {
        if ( (input - '0') == CR_0 ) {
            return colorUpdater<CR_0>();
        }
        if ( (input - '0') == CR_1 ) {
            return colorUpdater<CR_1>();
        }
        if ( (input - '0') == CR_2 ) {
            return colorUpdater<CR_2>();
        }

        return colorUpdater<CR_0>(); // Return the default type
    }

    template<const uint8_t Iter>
    static constexpr uint16_t colorUpdater() {

        if constexpr (Iter == CR_0) {
            std::cout << "ColorData updated to: " << CT_0 << '\n';
            return CT_0;
        }

        if constexpr (Iter == CR_1) {
            std::cout << "ColorData updated to: " << CT_1 << '\n';
            return CT_1;
        }

        if constexpr (Iter == CR_2) {
            std::cout << "ColorData updated to: " << CT_2 << '\n';
            return CT_2;
        }
    }
};

如果只想将其与编译时间常数一起使用,则可以像以前一样使用它,但是要使用函数的更新名称。

#include <iostream>

int main() {
    auto output0 = ColorInfo::colorUpdater<ColorInfo::CR_0>();
    auto output1 = ColorInfo::colorUpdater<ColorInfo::CR_1>();
    auto output2 = ColorInfo::colorUpdater<ColorInfo::CR_2>();

    std::cout << "\n--------------------------------\n";
    std::cout << "Recipient0: " << output0 << '\n'
              << "Recipient1: " << output1 << '\n'
              << "Recipient2: " << output2 << '\n';
    return 0;
}

如果您想将此机制与runtime值一起使用,则可以执行以下操作:

int main() {
    uint8_t input;
    std::cout << "Please enter input value [0,2]\n";
    std::cin >> input;

    auto output = ColorInfo::updateColor(input);

    std::cout << "Output: " << output << '\n';

    return 0;
}

这将与运行时值一起工作。

答案 4 :(得分:0)

好吧,如果您确定不必处理其他可能的值,则可以使用算术。摆脱了分支和负担。

void updateColor(const uint8_t iteration)
{
    dummyColorRecepient = 123 + 111 * iteration;
}