views:

680

answers:

9

For instance:

#include <stdio.h>

void why_cant_we_switch_him(void *ptr)
{
    switch (ptr) {
        case NULL:
            printf("NULL!\n");
            break;
        default:
            printf("%p!\n", ptr);
            break;
    }
}

int main(void)
{
    void *foo = "toast";
    why_cant_we_switch_him(foo);
    return 0;
}

gcc test.c -o test
test.c: In function 'why_cant_we_switch_him':
test.c:5: error: switch quantity not an integer
test.c:6: error: pointers are not permitted as case values

Just curious. Is this a technical limitation?

EDIT

People seem to think there is only one constant pointer expression. Is that is really true, though? For instance, here is a common paradigm in Objective-C (it is really only C aside from NSString, id and nil, which are merely a pointers, so it is still relevant — I just wanted to point out that there is, in fact, a common use for it, despite this being only a technical question):

#include <stdio.h>
#include <Foundation/Foundation.h>

static NSString * const kMyConstantObject = @"Foo";

void why_cant_we_switch_him(id ptr)
{
    switch (ptr) {
        case kMyConstantObject: // (Note that we are comparing pointers, not string values.)
            printf("We found him!\n");
            break;
        case nil:
            printf("He appears to be nil (or NULL, whichever you prefer).\n");
            break;
        default:
            printf("%p!\n", ptr);
            break;
    }
}

int main(void)
{
    NSString *foo = @"toast";
    why_cant_we_switch_him(foo);
    foo = kMyConstantObject;
    why_cant_we_switch_him(foo);

    return 0;
}

gcc test.c -o test -framework Foundation
test.c: In function 'why_cant_we_switch_him':
test.c:5: error: switch quantity not an integer
test.c:6: error: pointers are not permitted as case values

It appears that the reason is that switch only allows integral values (as the compiler warning said). So I suppose a better question would be to ask why this is the case? (though it is probably too late now.)

+2  A: 

Switch statements operate on integral values only. That's why the error message is "switch quantity not an integer." I don't think it's a technical limitation so much as it's outside the language syntax.

Jeff Kelley
It is more semantic than syntactic - syntactically, you can't tell the difference between '`int ptr; ... switch (ptr)`' and '`void *ptr; ... switch (ptr)`', but semantically, there is a difference. But you are right; the language does not allow 'switch on non-integral type'.
Jonathan Leffler
peachykeen
+4  A: 

Cast ptr to an int and try again:

switch( (int)ptr )

or to be more correct:

switch( (intptr_t)ptr ) // C99 integer type to hold a pointer
Justicle
The appropriate workaround for compiler not supporting the one legitimate use of switch on pointer. I like that.
Joshua
This only works assuming `sizeof(int) == sizeof(ptr)` on the target platform. That's not always the case.
Billy ONeal
You have no guarantee that an `int` is large enough to hold a pointer.
Brian Campbell
So you'd use intptr then, but since the only comparison you can really make is against 0, there's no point to using a switch instead of an if/else.
Dan Olson
@Dan: no, the only comparison *you* can think of is against zero.
Justicle
Using pointers to global/static objects would make sense to me, but they're not constant expressions: their addresses are assigned by the linker, and possibly relocated by the loader. Stack/heap are obviously right out. What does that leave, comparisons against `(intptr_t)-1` and the like?
bk1e
Fine... the only platform-independent comparison you can make against it is 0. I don't tend to assume that people asking basic questions like this will be comparing pointers against lists of memory mapped hardware registers, or something like that.
Dan Olson
Although is an interesting workaround, I wasn't actually looking for a workaround; just an explanation. If I used this cast in real code it would signal a WTF (to me, at least).
meeselet
@ Dan, Well, I guess that's the problem with passing judgment on the question and not addressing the question as asked. The question isn't "is it a good or bad idea to do this?".
Justicle
As meeselet points out, while this is a good example of a workaround, it doesn't answer the question that was posed.
Dan Moulding
Fair enough...i think Jeff's answer is the best anyway.
Justicle
+1  A: 

It can be related to how switch is implemented - it seems to expect at most an integer so it can use a certain CPU register which might not be possible with a pointer.

Otávio Décio
+3  A: 

switch statements operate on integral expressions only. A pointer is not an integral expression.

You can explicitly convert a pointer to an integral type if you wanted to, but the proposed code is a little strange and unnatural.

So to answer your question exactly: Because there is no implicit conversion between a pointer and an integral type.

Brian R. Bondy
A: 

Doh! why even use a switch statement? switch statements should only be used if you have 3 or more options to choose from if you have 2 options then use an if(){} else {} statement.

joshua this is not a legitimate use of a pointer in a switch.

If you really must use a switch statement then cast it to an _int64 or long long or some integral type guaranteed to be as big as or bigger than a pointer (depends on compiler).

Also some compilers may limit the maximum size of a switch to an int or some other arbitrary size. in this case you can't use a switch statement at all.

DC

DeveloperChris
+4  A: 

Because there is only one constant pointer expression

Given that only a single constant pointer expression exists, the switch statement has little to offer pointer expressions. You have cited essentially the only possible construction.

DigitalRoss
Bam. This seems to be the reason. I'm not sure why it escaped nearly everyone else that switch cases operated off of constants.
San Jacinto
You could have a other constant pointer expressions like "char * vgabuffer = (char *)(0xB800)"
Tarydon
The addresses of variables that have static storage duration are also constant.
caf
@caf: no, C99 calls those *address constants* .. see 6.6 (9). You cannot, for example, get away with `int y; int x[`
DigitalRoss
@Tarydon: perhaps I should have said *"there is only one portable constant ..."* :-)
DigitalRoss
Well, of course not - using a pointer value as an array size is nonsensical (`int x[NULL];` is also wrong!). However, it seems to me that if, hypothetically, `switch` *were* extended to deal with pointers, then logically *address constants* would be needed as the `case` expressions.
caf
Since when was C for *portable* code. :-D
Justicle
+1  A: 

A switch compares the variable with a set of compile-time constants. Other than null, I can't see any valid compile time constants that you might compare a pointer with. For example:

switch (ptr) { 
   case &var1: printf ("Pointing to var1"); break;
   case &var2: printf ("Pointing to var2"); break;
}

var1 and var2 are likely different in each run of the program, and would not be compile time constants. One possibility might be that they are addresses of memory-mapped ports that are always fixed, but otherwise I don't see how you could easily expand this from your two cases (null / not-null).

Tarydon
+2  A: 

case labels expect a constant-expression, usually an integer, and pointers tend not to compare well against these except in the case of NULL. You could cast to intptr_t, but it's still nonsensical when you only have one thing you can compare against.

switch statements exist because the compiler can often turn them into a jump table, which is a concept that works best if your case labels are consecutive integers. But in the case of a pointer casted to integral type, you gain nothing over an if / else by using switch except a more cumbersome syntax.

Dan Olson
+1  A: 

You can (if you really must). Simply cast the pointer to an appropriately sized integer. For this intptr_t should be used. That is not to say I'd recommend it, but you may have your reasons.

#include <stdint.h>
#include <stdio.h>

void we_can_switch_him(void *ptr)
{
    switch ((intptr_t)ptr) {
        case (intptr_t)NULL:
            printf("NULL!\n");
            break;
        default:
            printf("%p!\n", ptr);
            break;
    }
}

int main(void)
{
    void *foo = "toast";
    we_can_switch_him(foo);
    return 0;
}
Clifford