This question already has answers here:
Can I put code outside of cases in a switch?
(5 answers)
Closed 7 years ago.
How come ANSI C allows extraneous code before any case labels within a switch statement?
#include <stdio.h>
int main(void) {
const int foo = 1;
switch (foo) {
printf("wut\n"); /* no label, doesn't output */
case 1:
printf("1\n");
break;
default:
printf("other\n");
break;
}
return 0;
}
compiled with
$ gcc -pedantic -Wall -Werror -Wextra -ansi test.c
It compiles without warnings and executes fine - sans "wut".
It is allowed to put statements in switch without any label. Standard says about that:
C11: 6.8.4.2 The switch statement (p7):
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.
Related
Take a look at the following code:
typedef enum {
A, B, C
} Foo;
int foo(Foo x) {
switch(x) {
case A: return 1;
case B: return 2;
case C: return 3;
}
}
GCC 10.2 outputs
<source>:11:1: warning: control reaches end of non-void function [-Wreturn-type]
11 | }
| ^
This is because I can pass something like 42 to foo, not only A, B, or C. So the question is: how to tell GCC that only A, B, or C can be handled by the switch statement, otherwise the behavior is undefined? Compiler-specific functionality is acceptable.
Let me point some solutions that don't satisfy me. First, I could just insert default: __builtin_unreachable(); but this would penetrate case analysis: imagine that apparently I'm adding the D variant and the compiler would not tell me that D is unhandled.
Second, I could insert if (x > C) { __builtin_unreachable(); } before the switch statement, but this is too impossible because switch(x) is actually generated by a macro which doesn't know about the variants of Foo, it knows nothing but some variable x.
Third, I could insert #pragma GCC diagnostic ignored "-Wreturn-type", but again, switch(x) is generated by a macro and this is why I cannot revert the diagnostics to the previous state by #pragma GCC diagnostic pop.
Fourth, I could use an array instead of switch but the returned expressions are not constant and are provided by a user of the macro generating switch(x).
And the last one: I could write return 42; after the switch statement but again I want to disable the warning automatically inside the macro generating switch(x) because it's used extensively in my codebase.
Godbolt
If you feel like engaging in some light masochism, you could crudely implement exception-like behavior using setjmp and longjmp:
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
typedef enum Foo {
A, B, C
} Foo;
#define ENUM_OUT_OF_RANGE -1
jmp_buf env;
int foo( Foo x )
{
switch ( x )
{
case A: return 1;
case B: return 2;
case C: return 3;
default: longjmp( env, ENUM_OUT_OF_RANGE ); // "throw" ENUM_OUT_OF_RANGE
}
}
int main( void )
{
int ex;
if ( (ex = setjmp( env )) == 0 ) // "try" block
{
Foo arr[] = {A, B, C, 42};
for ( size_t i = 0; i < 4; i++ )
{
printf( "value of %d = %d\n", arr[i], foo( arr[i] ) );
}
}
else // "catch" block
{
if ( ex == ENUM_OUT_OF_RANGE )
{
fprintf( stderr, "foo was called with a value outside the range of Foo\n" );
}
}
return 0;
}
Builds with no warnings as follows (at least on my system):
gcc -o exceptions -std=c11 -pedantic -Wall -Werror exceptions.c
Output:
$ ./exceptions
value of 0 = 1
value of 1 = 2
value of 2 = 3
foo was called with a value outside the range of Foo
Code sample:
int main(int argc, char **argv)
{
switch(argc)
{
case 0:
argc = 5;
__attribute__((fallthrough));
case 1:
break;
}
}
Using gcc 6.3.0, with -std=c11 only, this code gives a warning:
<source>: In function 'main':
7 : <source>:7:3: warning: empty declaration
__attribute__((fallthrough));
^~~~~~~~~~~~~
What is the correct way to use this without eliciting a warning?
As previously answered, __attribute__ ((fallthrough)) was introduced in GCC 7.
To maintain backward compatibility and clear the fall through warning for both Clang and GCC, you can use the /* fall through */ marker comment.
Applied to your code sample:
int main(int argc, char **argv)
{
switch(argc)
{
case 0:
argc = 5;
/* fall through */
case 1:
break;
}
return 0;
}
Tried to comment previous, but did not have 50 reputation.
So, my experiences:
1) the feature is since gcc 7, so using attribute on older
compilers will give warning. therefore I currently use:
#if defined(__GNUC__) && __GNUC__ >= 7
#define FALL_THROUGH __attribute__ ((fallthrough))
#else
#define FALL_THROUGH ((void)0)
#endif /* __GNUC__ >= 7 */
and then I use FALL_THROUGH; in code
(Some day I figure out what is needed for clang, but not today)
2) I spent considerable time to try to get the gcc marker comment to work, but nothing I tried worked! Some comment somewere suggested that in order for that to work one has to add -C to
gcc arguments (meaning comments will be passed to cc1). Sure gcc 7 documentation doesn't mention anything about this requirement...
You can also try the code below, because only this one works for me on Android:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
// your code
#pragma GCC diagnostic pop
Say I declare the following variable:
int num;
num = 0;
int main(void)
{
/* ... */
exit(EXIT_SUCCESS);
}
The compiler will complain about num being undeclared and that it will default to type int. This does not happen when I do it all in one step:
int num = 0;
or if I move the assignment into main():
int main(void)
{
num = 0;
/* ... */
exit(EXIT_SUCCESS);
}
I once read an explanation for this behavior but I cannot find it anymore. Could someone update me again.
I'm compiling with
gcc -std=c11 -O2 -g -pedantic -Wall -c -fmessage-length=0 -v
num = 0; is a statement that can exist only inside a function. It cannot exist in a global scope.
If you put a statement outside a function, it's wrong and not allowed. Simply think this like, if you have a statement outside all the functions, in a global scope, when and how that statement can be executed? So, that's wrong.
A special case, initialization while defining is allowed in a form of int num = 0;
I have this code:
#include<stdio.h>
int main()
{
int a=10;
switch(a)
{
case '1':
printf("ONE\n");
break;
case '2':
printf("TWO\n");
break;
defalut:
printf("NONE\n");
}
return 0;
}
The program doesn't print anything, not even NONE. I figured out that default had a typo defalut!
I want to know why this syntax error is not detected by the compiler.
defalut is just a label in your program that you can jump to with goto. Having an editor that highlights keywords could have made this error easier to spot.
I should also note that your program may have some logic errors. The character '1' is not the same as 1, and the same with '2' and 2.
That's not a syntax error. defalut is a valid label, and it could be the target of a goto.
tip: if you are using gcc, add the option -pedantic. it will warn you for unused labels:
$ gcc -ansi -Wall -pedantic test.c -o test
test.c: In function ‘main’:
test.c:14:10: warning: label ‘defalut’ defined but not used
Today I found one interesting thing. I didn't know that one can't declare a variable after a goto label.
Compiling the following code
#include <stdio.h>
int main() {
int x = 5;
goto JUMP;
printf("x is : %d\n",x);
JUMP:
int a = 0; <=== giving me all sorts of error..
printf("%d",a);
}
gives errors like
temp.c: In function ‘main’:
temp.c:7: error: expected expression before ‘int’
temp.c:8: error: ‘a’ undeclared (first use in this function)
temp.c:8: error: (Each undeclared identifier is reported only once
temp.c:8: error: for each function it appears in.)
Now what is the logic behind that? I heard that one cannot create variables inside the case statements of switch. Since JUMP is inside the same scope (the scope of main function, in my case) of the goto statement, I believe that scope is not an issue here. But then, why am I getting this error?
The syntax simply doesn't allow it. §6.8.1 Labeled Statements:
labeled-statement:
identifier : statement
case constant-expression : statement
default : statement
Note that there is no clause that allows for a "labeled declaration". It's just not part of the language.
You can trivially work around this, of course, with an empty statement.
JUMP:;
int a = 0;
You want a semi-colon after the label like this:
#include <stdio.h>
int main() {
int x = 5;
goto JUMP;
printf("x is : %d\n",x);
JUMP: ; /// semicolon for empty statement
int a = 0;
printf("%d",a);
}
Then your code compiles correctly for the C99 standard, with gcc -Wall -std=c99 -c krishna.c (I'm using GCC 4.6 on Debian/Sid/AMD64).
Simple explanation, other than the spec says not, is that the compiler is exepecting the code after the goto to be something that compiles into an operation which it can then calculate the offset of, and is kicking because your variable declaration isn't a statement/block that it can compile into such an offset.
My gcc version (4.4) is giving this compile error:
t.c:7: error: a label can only be part of a statement and a declaration is not a statement
. This error-message says it all.
If you know why you can't create variables inside case statement of switch, basically its the same reason why you cant do this too. As a fix, you can try this,
#include <stdio.h>
int main() {
int x = 5;
goto JUMP;
printf("x is : %d\n",x);
JUMP:
{ //Note this
int a = 0; // <=== no more error..
printf("%d",a);
} //Note this
}
Well, first you should be consistent. It's either LABEL or label. Second, label is a part of the statement and the declaration doesn't answer the description enough.
You can replace LABEL: with label: ; and then it is likelier to compile.
EDIT: Now that you edited your code all over, it should be JUMP: replaced with JUMP: ; ;-)
#include <stdio.h>
int main() {
int x = 5;
goto JUMP;
printf("x is : %d\n",x);
JUMP:
printf("Do anything after label but dont declare
anything. even empty statement will also work
because label can only be part of a statement");
int a = 0;
printf("%d",a);
}