208

Through a little typo, I accidentally found this construct:

int main(void) {
    char foo = 'c';

    switch(foo)
    {
        printf("Cant Touch This\n");   // This line is Unreachable

        case 'a': printf("A\n"); break;
        case 'b': printf("B\n"); break;
        case 'c': printf("C\n"); break;
        case 'd': printf("D\n"); break;
    }

    return 0;
}

It seems that the printf at the top of the switch statement is valid, but also completely unreachable.

I got a clean compile, without even a warning about unreachable code, but this seems pointless.

Should a compiler flag this as unreachable code?
Does this serve any purpose at all?

abelenky
  • 58,532
  • 22
  • 99
  • 149
  • Doesn't your compiler already warn you about dead code in general? – Kerrek SB Jan 18 '17 at 19:05
  • 1
    Perhaps you didn't read the question: **YES** the compiler should warn against unreachable code. **NO** this bit was *NOT* flagged as unreachable. – abelenky Jan 18 '17 at 19:06
  • 3
    @KerrekSB tried to reproduce: My GCC 5.3.1 doesn't. – Marcus Müller Jan 18 '17 at 19:06
  • 1
    @MarcusMüller: My [GCC 7 does](http://melpon.org/wandbox/permlink/fokam99wkp5KgwEH) :-) – Kerrek SB Jan 18 '17 at 19:07
  • 52
    GCC has a special flag for this. It's `-Wswitch-unreachable` – Eli Sadoff Jan 18 '17 at 19:08
  • @EliSadoff well, GCC 7 does seem to have that, but 5.3.1 doesn't know that flag :) – Marcus Müller Jan 18 '17 at 19:08
  • 58
    *"Does this serve any purpose at all?"* Well, you can `goto` in and out of the otherwise unreachable part, which may be useful for various hacks. – HolyBlackCat Jan 18 '17 at 19:09
  • 13
    @HolyBlackCat Wouldn't that be such for all unreachable code? – Eli Sadoff Jan 18 '17 at 19:10
  • 28
    @EliSadoff Indeed. I guess it doesn't serve any *special* purpose. I bet it is allowed just because there is no reason to forbid it. After all, `switch` is just a conditional `goto` with multiple labels. There are more or less same restrictions on it's body as you would have on a regular block of code filled with goto labels. – HolyBlackCat Jan 18 '17 at 19:14
  • 5
    @HolyBlackCat The body doesn't even have to be a block. Any statement will do. – PSkocik Jan 18 '17 at 19:18
  • @PSkocik Huh, who would have thought. – HolyBlackCat Jan 18 '17 at 19:27
  • 4
    @HolyBlackCat: http://ideone.com/8XKE7 look where the `do {` is. – Mooing Duck Jan 18 '17 at 23:48
  • 16
    Worth pointing out that @MooingDuck s example is a variant on Duff's device (https://en.wikipedia.org/wiki/Duff's_device) – Michael Anderson Jan 19 '17 at 02:14
  • what I don't get is what's the difference in behavior when we look at duff's device vs. the `printf` in OP. why is one reached, and the other never? is it just because of the fall-through in the last case? could a for loop be used instead of the while? i.e., are "both ends of the loop a goto target"? – Cee McSharpface Jan 19 '17 at 15:32
  • @HolyBlackCat Re "you can goto in and out of the otherwise unreachable part": If you can do that, the code is obviously not unreachable. Duh. The `printf` in the OP cannot be reached, period. – Peter - Reinstate Monica Jan 20 '17 at 07:42
  • @dlatikayYes, one can think of both ends of a loop as labels (just imagine how a loop would be coded without loop constructs). While a `for` and a `while` loop technically only need a label at the beginning, a `do..while` loop has a virtual label at the end as well which is jumped to with a `continue`, in order to check the end condition. – Peter - Reinstate Monica Jan 20 '17 at 09:14
  • @PeterA.Schneider Please read my comment closely. I know that, and that why I said "*otherwise* unreachable". – HolyBlackCat Jan 20 '17 at 11:15
  • Another reason I don't touch C with a ten-foot stick; the compiler is just fine with stuff like this :) – Ben Leggiero Jan 24 '17 at 19:18

8 Answers8

227

Perhaps not the most useful, but not completely worthless. You may use it to declare a local variable available within switch scope.

switch (foo)
{
    int i;
case 0:
    i = 0;
    //....
case 1:
    i = 1;
    //....
}

The standard (N1579 6.8.4.2/7) has the following sample:

EXAMPLE    In the artificial program fragment

switch (expr)
{
    int i = 4;
    f(i);
case 0:
    i = 17;
    /* falls through into default code */
default:
    printf("%d\n", i);
}

the object whose identifier is i exists with automatic storage duration (within the block) but is never initialized, and thus if the controlling expression has a nonzero value, the call to the printf function will access an indeterminate value. Similarly, the call to the function f cannot be reached.

P.S. BTW, the sample is not valid C++ code. In that case (N4140 6.7/3, emphasis mine):

A program that jumps90 from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer (8.5).


90) The transfer from the condition of a switch statement to a case label is considered a jump in this respect.

So replacing int i = 4; with int i; makes it a valid C++.

AlexD
  • 30,405
  • 3
  • 66
  • 62
  • 3
    "... but is never initialized ..." Looks like `i` is initialized to 4, what am I missing? – yano Jan 18 '17 at 19:24
  • 7
    Note that if the variable is `static`, it will be initialized to zero, so there's a safe usage for this as well. – Leushenko Jan 18 '17 at 19:29
  • 23
    @yano We always jump over the `i = 4;`initialization, so it never takes place. – AlexD Jan 18 '17 at 19:32
  • 12
    Hah of course! ... whole point of the question ... geez. The desire is strong to delete this stupidity – yano Jan 18 '17 at 19:35
  • 1
    Nice! Sometimes I needed a temp variable inside a `case`, and always had to use different names in each `case` or define it outside the switch. – SJuan76 Jan 19 '17 at 13:08
98

Does this serve any purpose at all?

Yes. If instead of a statement, you put a declaration before the first label, this can make perfect sense:

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

The rules for declarations and statements are shared for blocks in general, so it's the same rule that allows that that also allows statements there.


Worth mentioning as well is also that if the first statement is a loop construct, case labels may appear in the loop body:

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

Please don't write code like this if there is a more readable way of writing it, but it's perfectly valid, and the f() call is reachable.

  • @MatthieuM Duff's Device does have case labels inside a loop, but starts with a case label before the loop. –  Jan 19 '17 at 15:38
  • 2
    I'm not sure if I should upvote for the interesting example or downvote for the utter madness of writing this in a real program :). Congrats for diving into the abyss and returning back in one piece. – Liviu T. Jan 19 '17 at 23:44
  • @ChemicalEngineer: If the code is part of a loop, as it is in Duff's Device, `{ /*code*/ switch(x) { } }` may look cleaner but it is also *wrong*. – Ben Voigt Jan 20 '17 at 16:56
40

There is a famous use of this called Duff's Device.

int n = (count+3)/4;
switch (count % 4) {
  do {
    case 0: *to = *from++;
    case 3: *to = *from++;
    case 2: *to = *from++;
    case 1: *to = *from++;
  } while (--n > 0);
}

Here we copy a buffer pointed to by from to a buffer pointed to by to. We copy count instances of data.

The do{}while() statement starts before the first case label, and the case labels are embedded within the do{}while().

This reduces the number of conditional branches at the end of the do{}while() loop encountered by roughly a factor of 4 (in this example; the constant can be tweaked to whatever value you want).

Now, optimizers can sometimes do this for you (especially if they are optimizing streaming/vectorized instructions), but without profile guided optimization they cannot know if you expect the loop to be large or not.

In general, variable declarations can occur there and be used in every case, but be out of scope after the switch ends. (note any initialization will be skipped)

In addition, control flow that isn't switch-specific can get you into that section of the switch block, as illustrated above, or with a goto.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • 2
    Of course, this would still be possible without allowing statements above the first case, as the order of `do {` and `case 0:` don't matter, both serve to place a jump target on the first `*to = *from++;`. – Ben Voigt Jan 20 '17 at 16:58
  • 1
    @BenVoigt I'd argue that putting the `do {` is more readable. Yes, arguing about readability for Duff's Device is stupid and pointless and likely a simple way to go mad. – Fund Monica's Lawsuit Jan 20 '17 at 20:31
  • 1
    @QPaysTaxes You should check out Simon Tatham’s [Coroutines in C](http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html). Or maybe not. – Jonas Schäfer Jan 23 '17 at 06:33
  • @JonasSchäfer Amusingly, that is basically what C++20 coroutines are going to do for you. – Yakk - Adam Nevraumont Apr 28 '19 at 15:46
15

Assuming you are using gcc on Linux, it would have given you a warning if you're using 4.4 or earlier version.

The -Wunreachable-code option was removed in gcc 4.4 onward.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
16tons
  • 605
  • 4
  • 14
  • Having experienced the issue first hand always helps! – 16tons Jan 18 '17 at 19:13
  • @JonathanLeffler: The general issue of gcc warnings being susceptible to the particular set of optimization passes selected is still true, unfortunately, and makes for a poor user experience. It's really annoying to have a clean Debug build followed by a failing Release build :/ – Matthieu M. Jan 19 '17 at 19:38
  • @MatthieuM.: It would seem that such a warning would be extremely easy to detect in cases involving grammatical rather than semantic analysis [e.g. code follows an "if" which has returns in both branches], and make it easier to stifle "annoyance" warnings. On the other hand, there are some cases where it's useful to have errors or warning in release builds that aren't in debug builds (if nothing else, in places where a hack which is put in for debugging is supposed to be cleaned up for release). – supercat Jan 20 '17 at 15:18
  • @supercat: Well, `#ifndef NDEBUG` allows debug only code sections and `#pragma warning` (I think) lets you write your own warning. However, my problem is more than having `-Wuninitialized` only work for maybe 30% of its potential reach in Debug and 100% in Release mean that you have to compile in both modes when developing, which slows down the edit-compile-test cycle (or you put it off 'till the end, and then discover that your approach has a flaw)... Clang manages to have such warnings have 100% coverage in Debug, I don't see why gcc couldn't. – Matthieu M. Jan 20 '17 at 15:28
  • 1
    @MatthieuM.: If significant semantic analysis would be required to discover that a certain variable will always be false at some spot in the code, code which is conditional upon that variable being true would be found to be unreachable only if such analysis were performed. On the other hand, I would consider a notice that such code was unreachable rather differently from a warning about syntactically-unreachable code, since it should be entirely *normal* to have various conditions be possible with some project configurations but not others. It may at times be helpful for programmers... – supercat Jan 20 '17 at 15:50
  • 1
    ...to know why some configurations generate larger code than others [e.g. because a compiler could regard some condition as impossible in one configuration but not another] but that doesn't mean that there's anything "wrong" with code which could be optimized in that fashion with some configurations. – supercat Jan 20 '17 at 15:57
11

Not only for variable declaration but advanced jumping as well. You can utilize it well if and only if you're not prone to spaghetti code.

int main()
{
    int i = 1;
    switch(i)
    {
        nocase:
        printf("no case\n");

        case 0: printf("0\n"); break;
        case 1: printf("1\n"); goto nocase;
    }
    return 0;
}

Prints

1
no case
0 /* Notice how "0" prints even though i = 1 */

It should be noted that switch-case is one of the fastest control flow clauses. So it must be very flexible to the programmer, which sometimes involves cases like this.

Dellowar
  • 2,764
  • 11
  • 30
11

It should be noted, that there are virtually no structural restrictions on the code within the switch statement, or on where the case *: labels are placed within this code*. This makes programming tricks like duff's device possible, one possible implementation of which looks like this:

int n = ...;
int iterations = n/8;
switch(n%8) {
    while(iterations--) {
        sum += *ptr++;
        case 7: sum += *ptr++;
        case 6: sum += *ptr++;
        case 5: sum += *ptr++;
        case 4: sum += *ptr++;
        case 3: sum += *ptr++;
        case 2: sum += *ptr++;
        case 1: sum += *ptr++;
        case 0: ;
    }
}

You see, the code between the switch(n%8) { and the case 7: label is definitely reachable...


* As supercat thankfully pointed out in a comment: Since C99, neither a goto nor a label (be it a case *: label or not) may appear within the scope of a declaration that contains a VLA declaration. So it's not correct to say that there are no structural restrictions on the placement of the case *: labels. However, duff's device predates the C99 standard, and it does not depend on VLA's anyway. Nevertheless, I felt compelled to insert a "virtually" into my first sentence due to this.

Community
  • 1
  • 1
cmaster - reinstate monica
  • 33,875
  • 7
  • 50
  • 100
  • The addition of variable-length arrays led to the imposition of structural restrictions related to them. – supercat Jan 20 '17 at 00:09
  • @supercat What kind of restrictions? – cmaster - reinstate monica Jan 20 '17 at 09:51
  • 1
    Neither a `goto` nor `switch/case/default` label may appear within the scope of any variably-declared object or type. This effectively means that if a block contains any declarations of variable-length-array objects or types, any labels must precede those declarations. There's a confusing bit of verbiage in the Standard which would suggest that in some cases the scope of a VLA declaration extends to the entirety of a switch statement; see http://stackoverflow.com/questions/41752072/meaning-of-this-rule-about-variable-length-arrays-and-switch-statements for my question on that. – supercat Jan 20 '17 at 14:54
  • @supercat: You just misunderstood that verbiage (which I presume is why you deleted your question). It imposes a requirement on the scope in which a VLA may be defined. It doesn't extend that scope, it just makes certain VLA definitions invalid. – Keith Thompson Feb 01 '17 at 18:53
  • @KeithThompson: Yeah, I had misunderstood it. The weird use of the present tense in the footnote made things confusing, and I think the concept could have been better expressed as a prohibition: "A switch statement whose body contains a VLA declaration within it shall not include any switch or case labels within the scope of that VLA declaration". – supercat Feb 01 '17 at 19:26
10

You got your answer related to the required gcc option -Wswitch-unreachable to generate the warning, this answer is to elaborate on the usability / worthyness part.

Quoting straight out of C11, chapter §6.8.4.2, (emphasis mine)

switch (expr)
{
int i = 4;
f(i);
case 0:
i = 17;
/* falls through into default code */
default:
printf("%d\n", i);
}

the object whose identifier is i exists with automatic storage duration (within the block) but is never initialized, and thus if the controlling expression has a nonzero value, the call to the printf function will access an indeterminate value. Similarly, the call to the function f cannot be reached.

Which is very self-explanatory. You can use this to define a locally scoped variable which is available only within the switch statement scope.

Sourav Ghosh
  • 127,934
  • 16
  • 167
  • 234
9

It is possible to implement a "loop and a half" with it, although it might not be the best way to do it:

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));
celtschk
  • 18,046
  • 2
  • 34
  • 61