Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active October 28, 2024 13:03
Show Gist options
  • Save x-yuri/91df2a5be018a7529ff8297306967971 to your computer and use it in GitHub Desktop.
Save x-yuri/91df2a5be018a7529ff8297306967971 to your computer and use it in GitHub Desktop.
c/c++: jumping over declarations

c/c++: jumping over declarations

In C one can't have a labeled variable declaration. This quirk probably appeared in C99. Before that variable declarations had to be at the beginnings of blocks, so there could never be a variable declaration after a label. C99 allowed intermixing variable declarations and statements, but the grammar remained the same, allowing only statements after labels.

cases
$ gcc --version
gcc (Alpine 13.2.1_git20240309) 13.2.1 20240309
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

a.c:

#include <stdlib.h>

int main(void)
{
    switch (42) {
        case 1: int a; (void) a; break;
        case 2: break;
    }
    return EXIT_SUCCESS;
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O \
    a.c
a.c: In function 'main':
a.c:6:17: warning: a label can only be part of a statement and a declaration is not a statement [-Wpedantic]
    6 |         case 1: int a; (void) a; break;
      |                 ^~~

b.c:

#include <stdlib.h>

int main(void)
{
    switch (42) {
        case 1: { int a; (void) a; break; }
        case 2: break;
    }
    return EXIT_SUCCESS;
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O \
    b.c

c.c:

#include <stdlib.h>

int main(void)
{
    switch (42) {
        case 1:; int a; (void) a; break;
        case 2: break;
    }
    return EXIT_SUCCESS;
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O \
    c.c

d.c:

#include <stdlib.h>

int main(void)
{
    goto a;
a:
    int a;
    (void) a;
    return EXIT_SUCCESS;
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O \
    d.c
d.c: In function 'main':
d.c:7:5: warning: a label can only be part of a statement and a declaration is not a statement [-Wpedantic]
    7 |     int a;
      |     ^~~

e.c:

#include <stdlib.h>

int main(void)
{
    goto a;
a:
    { int a;
    (void) a; }
    return EXIT_SUCCESS;
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O \
    e.c

f.c:

#include <stdlib.h>

int main(void)
{
    goto a;
a:;
    int a;
    (void) a;
    return EXIT_SUCCESS;
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O \
    f.c

In C++ jumping into a scope of a variable that was declared with an initializer is not allowed (ISO C++ '03 6.7/3, page 100, i.e. 128). Non-POD variables are initialized implicitly (if there's no explicit initializer), as such one can't ever jump over non-POD declarations. But one can jump over POD declarations without an initializer:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps 77) from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer (8.5).

Supposedly they wanted to keep some compatibility with C (in C one can jump over variabile declarations without initialization), but didn't want to let one have uninitialized objects (without their constructor being called). Especially considering that constructors can be called implicitly. Probably because that's an extra guarantee and it's somewhat easier if you know that an object is always initialized (if you have access to an object, its constructor was called).

More quotes here;

cases

a.cpp:

int main()
{
    switch (42) {
        case 1: int a = 1; (void) a; break;
        case 2: break;
    }
}
$ gcc -Wall -Wextra -pedantic -O a.cpp
a.cpp: In function 'int main()':
a.cpp:5:14: error: jump to case label
    5 |         case 2: break;
      |              ^
a.cpp:4:21: note:   crosses initialization of 'int a'
    4 |         case 1: int a = 1; (void) a; break;
      |                     ^

b.cpp:

int main()
{
    switch (42) {
        case 1: { int a = 1; (void) a; break; }
        case 2: break;
    }
}
$ gcc -Wall -Wextra -pedantic -O b.cpp

c.cpp:

int main()
{
    switch (42) {
        case 1: int a; a = 1; (void) a; break;
        case 2: break;
    }
}
$ gcc -Wall -Wextra -pedantic -O c.cpp

d.cpp:

int main()
{
    goto a;
    int a = 1;
    (void) a;
a:
    return 0;
}
$ gcc -Wall -Wextra -pedantic -O d.cpp
d.cpp: In function 'int main()':
d.cpp:6:1: error: jump to label 'a'
    6 | a:
      | ^
d.cpp:3:10: note:   from here
    3 |     goto a;
      |          ^
d.cpp:4:9: note:   crosses initialization of 'int a'
    4 |     int a = 1;
      |         ^

e.cpp:

int main()
{
    goto a;
    { int a = 1; (void) a; }
a:
    return 0;
}
$ gcc -Wall -Wextra -pedantic -O e.cpp

f.cpp:

int main()
{
    goto a;
    int a;
    a = 1;
    (void) a;
a:
    return 0;
}
$ gcc -Wall -Wextra -pedantic -O f.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment