Reputation: 1405
Recently I found out that the body of a switch can be any statement (C99 6.8.4). The idea was first suggested to me by this: https://stackoverflow.com/a/9220598/515212
So one can have switch statements like
void f(int n)
{
switch (n)
case 0:
printf("zero\n");
}
or even put if
s, while
s, etc.
void f(int n)
{
switch (n)
if (1)
{
case 0:
printf("zero\n");
}
else
while (--n)
{
default:
printf("non-zero\n");
}
}
Just out of interest, I was wondering whether this syntax has some usage or is just an artifact of how the switch statement is defined in the standard?
Upvotes: 3
Views: 213
Reputation: 17613
Here are a few unusual usages of switch()
and case
that I was playing around with this evening just to see how far it would stretch when I came across this question. Most of these come from various SO postings and answers.
The most famous unusual example is Duff's Device which is already covered in other answers to this question.
The basics of switch()
The switch()
statement has the basic form of:
switch (<expression>) <statement>;
where
<expression>
is an expression that evaluates to an integral value<statement>
is a statement or compound statement with labels<statement>
is either a single statement terminated by a semicolon or a series of statements enclosed in curly braces, a compound statement. Optional case
labels which specify a constant value can be used to act as jump targets if the constant value equals the value of <expression>
specified in the switch()
.
The case
labels are similar to goto
labels with a similar syntax and the switch()
statement can be thought of as a computed goto
statement in which the value of <expression>
determines to which label within the scope of the switch()
execution will jump.
The case
labels must specify an integral constant value or an expression from which the compiler can create an integral constant value. Each value specified in a case
label must be unique within the scope of the switch()
or the special label of default
can be used to specify any value other than the specified case
label values. The order of the case
labels does not matter.
The case
and default
labels do not need to be specified however switch(i) return 0;
will not do a return
since there are no labels to jump to. On the other hand switch(i) default: return 0;
will always return no matter what the value of the variable i
.
So the following are good switch()
statements:
switch (i) case 1: case 2: return i;
switch (i) {
case 3:
i++;
break;
case 1:
// FALL THROUGH
case 2:
return i;
default:
i -= ((i < 10) ? 2 : 5);
break;
}
// an enum is an integral type so its possible to use them with switch()
typedef enum { TYPE_1 = 0, TYPE_2, TYPE_3 } MyTypes;
MyTypes jjkk = TYPE_1;
switch (jjkk) {
case TYPE_1: doType1(22); break;
case TYPE_2: doType2(33); break;
}
// you can use #define constants so long as they evaluate to an integral valued constant
// the Preprocessor will do the text substitution for you.
#define JKJK1 1
#define JKJK2 2
switch (i) case JKJK1: case JKJK2: printf("JKJK1 or JKJK2\n");
// following use of logical express should transform logical true/false result
// into an integral value of 1 (true) or 0 (false). Expect a warning about
// this usage though.
switch (i < 4) case 1: i *= 2; // logical true evaluated as integral value of 1
Examples of switch()
usage
The first example uses a do {} while(0)
so that several of the cases use the same printf()
statement but the cases prepare case specific data to be used in the output.
Assume that we have a data structure in a point of sale which contains a foreign currency tender, e.g. the customer is at an airport shop in Paris where the Euro is the standard currency but the shop also accepts US dollars and the point of sale does a conversion of US dollars to Euros. In the receipt printing we want to print just Euros if the customer is using Euros and if the customer is using US dollars then we want to print the amount of US dollars, the conversion rate, and the amount of Euros.
We have a struct containing the tender data that looks something like:
typedef struct {
unsigned char uchMajorCode;
unsigned char uchMinorCode; // indicates which tender
long lTenderAmt; // amount of tender in Euros
long lRate; // conversion rate
long lForeignCurrency; // amount of the foreign currency
// .... other data
} ItemTender;
In the printing logic we have to determine how to format the receipt line for this tender. And yes I know there are other and more readable ways to do this.
switch (pItem->uchMinorCode) {
char *mnemonic;
case FOREIGN_1: do {
mnemonic = getMnemonic(TRN_FOREIGN_1);
break; // break from do {} while()
case FOREIGN_2:
mnemonic = getMnemonic(TRN_FOREIGN_2);
break; // break from do {} while()
case FOREIGN_3:
mnemonic = getMnemonic(TRN_FOREIGN_3);
break; // break from do {} while()
} while(0);
printf ("%s\t%ld %ld @ %ld\n", mnemonic, pItem->lTenderAmt, pItem->lForeignCurrency, pItem->lRate);
break; // break from switch()
case LOCAL:
// FALL THROUGH
default:
printf ("%s\t%ld\n", getMnemonic(TRN_LOCAL), pItem->lTenderAmt);
break; // break from switch()
}
Note: Be aware that using a switch()
to jump into a loop will bypass any loop initialization expression with some loop constructs such as for()
. The loop conditional check expression will also be bypassed when jumping into the loop body. A similar note applies to an if
statement should a case
label be in the body of the if
.
switch (i) {
default: if (iVal > 3) { // when i is not equal to 5 then iVal is checked
case 5: iVal++; // when i equals 5 then iVal is not checked
}
}
or with a for()
loop should the case
label be inside the loop then the initialization is bypassed.
switch (i) {
default: for (iVal= 0; iVal< 4; iVal++) { // i not equal to 5 so iVal is initialized
case 5: doSomething (iVal); // i equals 5 so iVal is not initialized
}
break;
}
The next unusual usage is to test for a sequence of values and perform some set of steps for those values. This uses the same data struct above. We could also have more than one line if we used curly braces. Without curly braces the first terminating semicolon also terminates the switch()
.
// in the case of a foreign tender perform the currency conversion
switch (pItem->uchMinorCode)
case FOREIGN_1: case FOREIGN_2: case FOREIGN_3:
pItem->lTenderAmt = pItem->lForeignCurrency * pItem->lRate;
A third unusual usage is to generate a set of values within a while()
loop and, depending on the value, do something. This example is somewhat contrived to show syntax possibilities. I haven't seen anything like this in the wild however error check after calling a function and doing a retry based on the error code is a possibility.
while ((iVal = f(iVal))) { // get next value in a sequence and do something with it.
switch (iVal) {
int j; // define temporary variable used below.
case 1:
continue; // continue the while() to get next value
case 2:
break; // break from switch() to do printf() below
case 0: do {
j = 17;
break; // break from do()
default:
j = 12;
break; // break from do()
} while (0); // bottom of the do(). print follows
printf(" do while - iVal = %d, j = %d\n", iVal, j);
break; // break from switch() to do printf() below
}
printf(" while - iVal = %d\n", iVal);
break; // break from while() loop
}
As a fourth example we use a switch()
statement for testing various bits. Since a case
must be an integral value which is computed at compile time we can use constants with bitwise operators if the compiler, and most modern ones will do so, will perform the calculation at compile time.
#define VAL_1 0x0001
#define VAL_2 0x0002
#define VAL_3 0x0004
#define VAL_4 0x0008
switch (iBitMask & 0x000f) {
case VAL_1: // only VAL_1 is set
printf(" only VAL_1 found\n");
break;
case VAL_1 | VAL_2: // both and only both VAL_1 and VAL_2 are set
printf(" both VAL_1 and VAL_2 found\n");
break;
case VAL_1 | VAL_3: // both and only both VAL_1 and VAL_3 are set
printf(" both VAL_1 and VAL_3 found\n");
break;
case 0x000f & ~VAL_1:
printf(" everything except for VAL_1 found\n");
break;
}
References
How does switch statement work?
Does the C standard explicitly indicate truth value as 0 or 1?
Using continue in a switch statement
Upvotes: 1
Reputation: 12243
This is valid C code. It origins from assembly where every conditional statement uses if, goto and label jumps.
Implementation of array copy using this feature is called Duff's Device
void copy(char *from, char *to, int count)
{
int n = (count + 7) / 8;
switch(count % 8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while(--n > 0);
}
}
When you replace while
with if
and goto
void copy(char *from, char *to, int count)
{
int n = (count + 7) / 8;
switch(count % 8) {
case 0:
loop: *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
if(--n > 0) goto loop;
}
}
Then replace switch
with if
and goto
void copy(char *from, char *to, int count)
{
int n = (count + 7) / 8;
if(count%8==7)goto case7;
if(count%8==6)goto case6;
if(count%8==5)goto case5;
if(count%8==4)goto case4;
if(count%8==3)goto case3;
if(count%8==2)goto case2;
if(count%8==1)goto case1;
if(count%8==0)goto case0; // this can be omitted
case0: // this can be omitted
loop: *to++ = *from++;
case7: *to++ = *from++;
case6: *to++ = *from++;
case5: *to++ = *from++;
case4: *to++ = *from++;
case3: *to++ = *from++;
case2: *to++ = *from++;
case1: *to++ = *from++;
if(--n > 0) goto loop;
}
Which is functionally (almost) equivalent to
void copy(char *from, char *to, int count)
{
while(--n > 0) {
*to++ = *from++;
}
}
It's almost equivalent because in the last implementation loop check is performed 8 times more often, what have impact on performance.
Upvotes: 2
Reputation: 513
You can look here for example of unusual usage of switch statement. But don't do this in a real code. Example from link:
int duffs_device(char *from, char *to, int count)
{
{
int n = (count + 7) / 8;
switch(count % 8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while(--n > 0);
}
}
return count;
}
It's called Duff's device. This function copies char array. It uses the trick called "loop unrolling".
Long loops can be slow because every iteration they need to do additional work like comparisons and variable incremention. So one way to speed them up is to duplicate repeating code. Like its done in example.
But modern compilers can do this better, and using code like this is not recommended, because it only confuse people who read it.
Upvotes: 1
Reputation: 311078
You can consider the switch
statement as a code block with labels (case(s) are indeed labels) where the control is passed with a goto statement.
Something like
void f(int n)
{
if ( n == 0 ) goto Label_case_0;
else goto Label_default;
{
if ( 1 )
{
Label_case_0:
printf("zero\n");
}
else
while (--n)
{
Label_default:
printf("non-zero\n");
}
}
}
In my opinion it is not a good idea to place case labels inside some other control structures because this makes the code difficult to read and can lead to errors.
Upvotes: 3