How is a label in between `else-if` allowed?

Viewed 93

Here is a simple example (not tied to specific platform):

#include <stdio.h>
int main()
{
    for(int i = 0; i < 3; ++i)
        if(i == 0) printf("1\n");
        else if (i == 1) goto checkfor2;
        else checkfor2: if(i == 2 || i == 1) printf("2\n");
}

Live wandbox.

My original Windows code from which this question raised:

#include <Windows.h>
#include <stdbool.h>
main() //sample with Win32 API
{
    for (;;) //infinite loop for my main logic
        if (WaitForSingleObject(hEvent, 1) != WAIT_TIMEOUT, true) //#1 check for some event
            //oops set comma true to test this event code
        {
            //do something
            //but here it will loop infinte and wouldn't check for #3
            //so if something goes wrong I can't exit

            //so clever me did the following:

            goto checkforexit; //see label on #3 below
        }
        else if (WaitForSingleObject(hEvent2, 1) != WAIT_TIMEOUT) //#2 check for another event
        {
            //do something else
        }
        else checkforexit: /*wow in between else and if*/ if (GetAsyncKeyState(VK_BACK) & 0x8000 && GetAsyncKeyState(VK_CONTROL) & 0x8000) //#3 check for keyboard shortcut for exit
            exit(0); //termination code
}

I was surprised that this worked - it seems both the original else if and the label to the if are working as intended - how is this possible?

I would appreciate if you add a quote to the C standard.

It's working both on gcc (as tested on wandbox) and MSVC.

2 Answers

The label is actually part of the if statement; the if statement is labeled.

C 2018 6.8 defines a statement (a token in the formal grammar for the C language) to be one of:

  • labeled-statement
  • compound-statement
  • expression-statement
  • selection-statement
  • iteration-statement
  • jump-statement

An if … else statement is defined in 6.8.4 to be one of the options for a selection-statement:

  • if ( expression ) statement else statement

As you can see, what comes after the else is a statement, and a statement can be a labeled-statement, one form of which is “identifier : statement”. (The other forms use the case and default labels for a switch statement.)

This is not an impediment to the compiler because it simply includes the transfer of control from a goto statement to the label as part of its control-flow graph, which already includes jumps such as from the if to the else (since the first statement is skipped if the expression is false), from the end of a while loops to the beginning, from a break statement to outside a for statement, and even from goto statements to labels inside while or for loops. By constructing a general control-flow graph, the compiler is able to handle arbitrary control flow and does not care about “structured programming.” Structured programming is a tool to aid humans, but software can handle arbitrary cases without that tool.

You can largely use labels to jump around arbitrarily inside a function, but a problem occurs when you jump into a scope in which a variable length array or other variably modified type is declared. The C standard does not define the behavior if you do this, such as:

if (flag)
    goto label;
while (foo)
{
    int bar[n];
    label: // Error, delaration of `bar` is skipped.
    …
}

The formal syntax of a label can be found at C11 6.8.1:

labeled-statement:
identifier : statement

Where statement can be one of:

statement:
labeled-statement
compound-statement
expression-statement
selection-statement
iteration-statement
jump-statement

if is a selection statement, so code like my_label : if(something) ; is perfectly fine syntax-wise.

As for "else if", it actually isn't a statement syntax item of its own (this is a common misunderstanding) - it is just an else containing a nested if written on the same line. But we like to write them together style-wise to improve readability. To clarify the indention, your code could also have been written like this:

#include <stdio.h>
int main()
{
    for(int i = 0; i < 3; ++i)
        if(i == 0) 
            printf("1\n");
        else 
            if (i == 1) 
                goto checkfor2;
        else 
            checkfor2: if(i == 2 || i == 1) 
                printf("2\n");
}

Where checkfor2: if is valid, as per the previously quoted syntax.

Related