switch statement - ANSI-C

A

aaron80v

Hi,

A simple question: In ANSI-C, how to specify a string as the expression
for switch statement.

eg

switch (mystr) {
case "ABC" : bleh...
break;
case "DEF" : bleh
break'
default: bleh;
break;
}

AFAIK, the expression must produce an integer value. In the case of a
string, what is the work-around?

Aaron
 
T

typedef

Q: How do I switch on strings?

The simple answers are:
a) don't use switch. use if/else with strcmp
b) make an array of possible strings and use a lookup function to
return the index of your string (or -1) and switch on that.

The most common time I face this sort of thing is parsing config files
for tokens. The approach I take is an enhancement to the simple answer
b) above, using a structure consisting of an enumeration of index
values associated with the string. The enum makes it handy to add new
keys without having to worry about index numbers as opposed to having
case 1:, case 2: to go update. Here's an example for a few strings:

typedef enum
{
eKEY_HOST = 0,
eKEY_PORT, /* implicitly = 1 ... etc */
eKEY_PROTO,
eNUM_KEYS, /* number of valid keys */
eKEY_INVALID
} cfg_key_names;

/* array of structures to map the enum (numerical) values to strings */
struct {
cfg_key_names id;
const char *str;
} cfg_keys[] = {
{ eKEY_HOST, "host" },
{ eKEY_PORT, "port" },
{ eKEY_PROTO, "proto" }
};

/* get the number of entries in the above array automatically */
int n_cfg_keys = sizeof(cfg_keys) / sizeof(cfg_keys[0]);

/* lookup function */
int parse_cfg_key(const char *str)
{
int i;
for (i = 0; i < n_cfg_keys; i++)
if (strcmp(str, cfg_keys.str) == 0)
return cfg_keys.id;

return eKEY_INVALID;
}

switch (parse_cfg_key(some_str))
{
case eKEY_HOST:
/* ... */
break;
default:
case eKEY_INVALID:
/* NO STRING FOR YOU! */
break;
}

Hope that helps.
 
R

Richard Heathfield

(e-mail address removed) said:
Hi,

A simple question: In ANSI-C, how to specify a string as the expression
for switch statement.

You can't, but keep reading, because there is a way around this.
eg

switch (mystr) {
case "ABC" : bleh...
break;
case "DEF" : bleh
break'
default: bleh;
break;
}

AFAIK, the expression must produce an integer value. In the case of a
string, what is the work-around?

Associate each string with an integer (a struct works well here), preferably
in a sorted container of some kind (sorted array, or BST, or hash table, or
something of the kind), to make retrieval quicker. Look up the string, find
the associated number, and switch on that instead.
 
R

Richard Bos

typedef said:
Q: How do I switch on strings?

The simple answers are:
a) don't use switch. use if/else with strcmp
b) make an array of possible strings and use a lookup function to
return the index of your string (or -1) and switch on that.

c) Use a hash.

Richard
 
G

Giorgio Silvestri

typedef said:
Q: How do I switch on strings?

The simple answers are:
a) don't use switch. use if/else with strcmp
b) make an array of possible strings and use a lookup function to
return the index of your string (or -1) and switch on that.

The most common time I face this sort of thing is parsing config files
for tokens. The approach I take is an enhancement to the simple answer
b) above, using a structure consisting of an enumeration of index
values associated with the string. The enum makes it handy to add new
keys without having to worry about index numbers as opposed to having
case 1:, case 2: to go update. Here's an example for a few strings:

typedef enum
{
eKEY_HOST = 0,
eKEY_PORT, /* implicitly = 1 ... etc */
eKEY_PROTO,
eNUM_KEYS, /* number of valid keys */
eKEY_INVALID
} cfg_key_names;

/* array of structures to map the enum (numerical) values to strings */
struct {
cfg_key_names id;
const char *str;
} cfg_keys[] = {
{ eKEY_HOST, "host" },
{ eKEY_PORT, "port" },
{ eKEY_PROTO, "proto" }
};

/* get the number of entries in the above array automatically */
int n_cfg_keys = sizeof(cfg_keys) / sizeof(cfg_keys[0]);

And for checking:

char only_for_check[2 * (sizeof(cfg_keys) / sizeof(cfg_keys[0]) ==
eNUM_KEYS) - 1];

/*
* in this case "n_cfg_keys" is not necessary
*/
/* lookup function */
int parse_cfg_key(const char *str)
{
int i;
for (i = 0; i < n_cfg_keys; i++)
if (strcmp(str, cfg_keys.str) == 0)
return cfg_keys.id;

return eKEY_INVALID;
}

switch (parse_cfg_key(some_str))
{
case eKEY_HOST:
/* ... */
break;
default:
case eKEY_INVALID:
/* NO STRING FOR YOU! */
break;
}


If you have found the index, the work is done.
It is possible to add a function field to the
struct to avoid the switch.

x = parse_cfg_key(some_str);
if (x != eKEY_INVALID) {
(*cfg_keys[x].fun)(); /* or with arguments */
} else {
/* ... */
}

The "id" field is not necessary if you respect the order:

struct str_and_fun_only {
const char *str;
void (*fun)(void);
};

"parse_cfg_key" can return directly 'i'.
 
J

John Bode

Hi,

A simple question: In ANSI-C, how to specify a string as the expression
for switch statement.

eg

switch (mystr) {
case "ABC" : bleh...
break;
case "DEF" : bleh
break'
default: bleh;
break;
}

AFAIK, the expression must produce an integer value. In the case of a
string, what is the work-around?

After 16 years, I've become an advocate for simplicity. Any workaround
that allows you to use a switch is just going to bog you down in extra
work and complexity, so I'd advocate ditching the switch statement
entirely and just going with

if (!strcmp(mystr, "ABC"))
{
/* do stuff */
}
else if (!strcmp(mystr, "DEF"))
{
/* do stuff */
}
...
else
{
/* do default */
}
 
R

Richard Heathfield

John Bode said:
After 16 years, I've become an advocate for simplicity. Any workaround
that allows you to use a switch is just going to bog you down in extra
work and complexity, so I'd advocate ditching the switch statement
entirely and just going with

if (!strcmp(mystr, "ABC"))
{
/* do stuff */
}
else if (!strcmp(mystr, "DEF"))
{
/* do stuff */
}
...
else
{
/* do default */
}

That's a perfectly reasonable alternative, but what if you have a great many
cases? It's not a very scalable solution. You can, of course, use the
result of strcmp:

diff = strcmp(mystr, "MNO");
if(diff == 0)
{
/* whatever */
}
else if(diff < 0)
{
/* handle cases "AAA" to "MNN" */
diff = strcmp(mystr, "HIJ");
if(diff == 0) etc etc
}
else
{
/* handle cases "MNP" to "ZZZ" */
diff = strcmp(mystr, "RST");
if(diff == 0) etc etc
}

but really, if you have so many cases as to make this worth doing, you're
better off with a bunch of string/functionptr structs in a sorted array, a
tree, or a hashtable.
 
D

David T. Ashley

Hi,

A simple question: In ANSI-C, how to specify a string as the expression
for switch statement.

eg

switch (mystr) {
case "ABC" : bleh...
break;
case "DEF" : bleh
break'
default: bleh;
break;
}

AFAIK, the expression must produce an integer value. In the case of a
string, what is the work-around?

You can't. Other posters have made great suggestions. I'll make more.

The disadvantage of else-if is of course that the execution time to match
one of the last clauses will be longest of all. Depends on how many you
have. I have a couple suggestions, all of which will get me boo'd on this
group.

#1
---
Make the compiler work for a living:

switch(mystr[0])
{
case 'A' :
{
switch (mystr[1])
{
//And nest arbitrarily deeply.


#2
---
Standard BSEARCH, with the table of strings ordered in the source code. You
can include a function pointer as part of the table. That, at least is
O(log N) rather than O(N).

http://en.wikipedia.org/wiki/Bsearch

---------

The suggestions by the other posters involving hashing were of course very
good ... but BSEARCH seems easier and cleaner. For one thing, no hash
collisions to worrry about.

Dave.
 
G

Guest

David said:
Hi,

A simple question: In ANSI-C, how to specify a string as the expression
for switch statement.

You can't. Other posters have made great suggestions. I'll make more. [...]
The suggestions by the other posters involving hashing were of course very
good ... but BSEARCH seems easier and cleaner. For one thing, no hash
collisions to worrry about.

If you use a hash function generator such as gperf, you have no hash
collisions to worry about either.
 
C

CBFalconer

David T. Ashley said:
.... snip ...

The suggestions by the other posters involving hashing were of
course very good ... but BSEARCH seems easier and cleaner. For
one thing, no hash collisions to worrry about.

If you use my hashlib package (see sig for URL, download section)
you don't have to worry about collisions either. And lookup will
be O(1) rather than O(logN).

Besides, I try to avoid worrrying. Insufficient Scottish ancestry.
 
A

aaron80v

Hi,

Thanks for all the input. I believe in simplicity myself for easy
maintenance. The IF ELSE statement is probably a better solution than a
hash for now. However, has anyone actually tested the performance for
say 100 IF ELSE statements? There must be some threshold we can use
before we decide to go for a hashlib sample provided.

Just a side note, anyone knows of any good ANSI-C IDE other than the
one comes with DJGPP (RHIDE)?


Aaron
 
C

CBFalconer

.... snip ...

Just a side note, anyone knows of any good ANSI-C IDE other than the
one comes with DJGPP (RHIDE)?

No IDE. Just have the editor open in one window, and the DOSbox in
another. ALT-tab switches between them. In the dosbox you can run
make, or gcc, or the output program as you wish. Much faster, no
bugs. And you get to choose your editor (I like textpad).
 
U

user923005

Hi,

Thanks for all the input. I believe in simplicity myself for easy
maintenance. The IF ELSE statement is probably a better solution than a
hash for now. However, has anyone actually tested the performance for
say 100 IF ELSE statements? There must be some threshold we can use
before we decide to go for a hashlib sample provided.

2 strings. Branch mispredictions really stink on modern CPUs. I
almost always agree with John Bode. Not this time. This has hashmap
written all over it.
IMO-YMMV
Just a side note, anyone knows of any good ANSI-C IDE other than the
one comes with DJGPP (RHIDE)?

eclipse
IMO-YMMV
 
R

Richard Bos

CBFalconer said:
No IDE. Just have the editor open in one window, and the DOSbox in
another. ALT-tab switches between them. In the dosbox you can run
make, or gcc, or the output program as you wish. Much faster, no
bugs. And you get to choose your editor (I like textpad).

Many editors, including TextPad, let you configure a limited selection
of hotkeys to call the program of your choice on the edited text. One
can use this to compile and test without even leaving the editor.

Richard
 
R

Randy Howard

Hi,

Thanks for all the input. I believe in simplicity myself for easy
maintenance. The IF ELSE statement is probably a better solution than a
hash for now.

A wise decision.
However, has anyone actually tested the performance for
say 100 IF ELSE statements?

Even if they had, it wouldn't be directly applicable to your case. If
the 100 options are equally likely, then there isn't a lot you can do
about it. However, if a few items are very likely, and the others
happen more rarely, then putting them in order so that the most common
items get tested for first will help.
There must be some threshold we can use
before we decide to go for a hashlib sample provided.

The hash implementation is much more complicated. Unless you have a
discernable performance problem without it, don't even worry about
going that route.
Just a side note, anyone knows of any good ANSI-C IDE other than the
one comes with DJGPP (RHIDE)?

There are a lot of IDEs that are front-ends to gcc, which can be
configured to compile strict ansi (flags to gcc). I know longer spend
time developing on Windows, only compiling code there (from a makefile)
after it's been finished on a real operating system, so I'm not going
to have a current opinion on it.
 
U

user923005

Randy Howard wrote:
[snip]
The hash implementation is much more complicated. Unless you have a
discernable performance problem without it, don't even worry about
going that route.

If you use an existing, debugged hash library, the code needed will be
far simpler and far smaller than one hundred if/else if branches and
will automatically expand for the new options that are sure to come.

I think your advice is wrong.

Now, if the one hundred different options are just menu items and the
performance is therefore irrelevant and the options are literally
mandated to never change then maybe it is a coin toss.

For me, I would add the hash lookup even for the menu, but that's a
personal choice in that instance.

On the other, other hand, if this is a performance critical piece of
code, then the hash table will smoke one hundred if branches.

In all cases I consider the hash table solution as simpler, faster,
more general, easier to code, test, debug and document.

But, as always --
IMO-YMMV
 
R

Randy Howard

Randy Howard wrote:
[snip]
The hash implementation is much more complicated. Unless you have a
discernable performance problem without it, don't even worry about
going that route.

If you use an existing, debugged hash library, the code needed will be
far simpler and far smaller than one hundred if/else if branches and
will automatically expand for the new options that are sure to come.

I think your advice is wrong.

I think your opinion on my advice is wrong. :)

Keep in mind, the OP in question here was originally asking about how
to do a switch on string arguments. Someone that unfamiliar with the
basics of the language at this point is much better off getting
something working, then determining if it is in fact a bottleneck, or a
don't care to overall performance than trying to dive off into
something completely new to them when they have so much other ground to
cover at the same time.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,996
Messages
2,570,237
Members
46,825
Latest member
VernonQuy6

Latest Threads

Top