5

Is it possible to rewrite following so I only have to change in one place if the string changes?

#define MY_STRING "Foo bar"
#define MY_STRING_FIRST_CHAR 'F'

The following is not acceptable since it refers to a char in a memory location, so it can't be used as a case in a switch statement:

#define MY_STRING_FIRST_CHAR MY_STRING[0]

switch (something) {
    case MY_STRING_FIRST_CHAR:
        break;
}

The purpose is to efficiently parse a received string by looking at one character. In my case all strings have one unique character. The following is not my actual code, but a very simple example to show the principle:

#define COMMAND_LIST              "list"
#define COMMAND_LIST_FIRST_CHAR   'l'
#define COMMAND_CHANGE            "change"
#define COMMAND_CHANGE_FIRST_CHAR 'c'
#define COMMAND_EXIT              "exit"
#define COMMAND_EXIT_FIRST_CHAR   'e'

switch(received_command_string[0]){
  case COMMAND_LIST_FIRST_CHAR:
    // Do the "list" stuff
    break;
  case COMMAND_CHANGE_FIRST_CHAR:
    // Do the "change" stuff
    break;
  case COMMAND_EXIT_FIRST_CHAR:
    // Do the "exit" stuff
    break;
}

User "pmg" found this in the gcc documentation: "There is no way to convert a macro argument into a character constant."

I wanted the definitions to be in an include file that can be shared by several source files. This is as close as I can get while only have every character defined in one place:

#include <stdio.h>
#define CH0 'F'
#define CH1 'o'
#define CH2 'o'
#define CH3 ' '
#define CH4 'b'
#define CH5 'a'
#define CH6 'r'
static char MY_STRING[] = { CH0, CH1, CH2, CH3, CH4, CH5, CH6, '\0'};
#define MY_STRING_FIRST_CHAR CH0

void main(void){
  printf("The string is %s, the first char is %c\n", MY_STRING, MY_STRING_FIRST_CHAR);
}

I will not do it that way. The original question was if it is possible to share one definition to get both a string constant and a character constant. By wasting clock cycles at run-time there are several solutions to my problem.

Unihedron
  • 10,902
  • 13
  • 62
  • 72
Hans
  • 61
  • 5
  • 3
    Interesting question. I doubt it is possible. But smells like XY-problem to me. – Eugene Sh. Jul 27 '16 at 13:48
  • 1
    If you want to use the character in a case label, you'll need an [integer constant expression](http://port70.net/~nsz/c/c99/n1256.html#6.6). – nwellnhof Jul 27 '16 at 13:57
  • Possible duplicate of [switch case: error: case label does not reduce to an integer constant](http://stackoverflow.com/questions/14069737/switch-case-error-case-label-does-not-reduce-to-an-integer-constant) – jweyrich Jul 27 '16 at 14:01
  • Just use `if-else if-else` way. You can write a fancy macro to wrap it into a `switch`-like structure if you really want. – Eugene Sh. Jul 27 '16 at 14:02
  • 1
    @jweyrich This question is not a duplicate of that one. OP is aware of the problem discussed there and seeks for a workaround in the particular case described in this question. – Leon Jul 27 '16 at 14:05
  • @Hans: Why would you want to use a `switch-case` when it's always going to match the same case? Can you show us an example? – jweyrich Jul 27 '16 at 14:15
  • 1
    @jweyrich Why is that? He might have `MY_STRING` to contain an alphabet or whatever, and test the expression against different letters of it. But yeah, the intended use would be interesting to see. – Eugene Sh. Jul 27 '16 at 14:18
  • 2
    According to [gcc 3.0.2 documentation](https://gcc.gnu.org/onlinedocs/gcc-3.0.2/cpp_3.html#SEC17): *"There is no way to convert a macro argument into a character constant."* – pmg Jul 27 '16 at 14:56
  • 1
    According to [gcc 6.1.0 documentation](https://gcc.gnu.org/onlinedocs/gcc-6.1.0/cpp/Stringification.html#Stringification): *"There is no way to convert a macro argument into a character constant."* – pmg Jul 27 '16 at 15:44
  • @pmg I think you have the final answer, It is impossible to extract a character constant from a constant string. – Hans Jul 27 '16 at 19:57
  • seems like you are trying to do micro/premature optimisations, keep in mind you wont even be able to see the difference (with most tools) on such "optimisations" – Arnaud Aliès Aug 16 '16 at 07:53

4 Answers4

3

You can do that with writing each symbol once ... but on different definitions

#include <stdio.h>

#define COMMAND_LIST_FIRST_CHAR   'l'
#define COMMAND_LIST              (char[]){ COMMAND_LIST_FIRST_CHAR, 'i', 's', 't', 0 }

int main(void) {
    char received_command_string[] = "list";
    switch (received_command_string[0]) {
        case COMMAND_LIST_FIRST_CHAR:
            printf("Doing the \"list\" stuff for '%s'\n", COMMAND_LIST);
            break;
        default:
            break;
    }
    return 0;
}
pmg
  • 106,608
  • 13
  • 126
  • 198
  • Oooops, I saw this after I added my similar but uglier example to the original post. Your solution is more elegant. – Hans Jul 27 '16 at 20:49
  • 2
    My ugly solution didn't specify the string length explicitly, but it seems that the constant '5' can be removed also from your code. – Hans Jul 27 '16 at 21:07
1

Why do you absolutely want to use a switch case?

Instead you could use a mapping table that match your string and the handling function. Then you simply have to iterate over the table.

typedef struct {
    char * key;
    void (func*)(void);
} MAP_ENTRY;

MAP_ENTRY map [] = {
    {"list", listHandler},
    {"change", changeHandler},
    {"exit", exitHandler},
};

for (i = 0; i < sizeof(map)/sizeof(map[0]); i++) {
    if (map[i].key[0] == received_command_string[0]) {
        map[i].func();
        break;
    }
}

Then you just have to move the processing code from your switch/case into the corresponding handler function

greydet
  • 5,509
  • 3
  • 31
  • 51
  • 1
    This code lacks the sought efficiency compared to the `switch`-based approach which would work in O(1) time. Whether the performance of that code really matters that much, only OP can tell. – Leon Jul 27 '16 at 18:06
  • @Leon I don't agree with your statement. From a performance point of view, you have to take compiler optimizations into account. With the above code, any modern compiler configured for performance optimizations would perform loop unrolling that will make the resulting binary code almost the same as a simple switch/case. It is very often a bad idea to sacrifice code readability to make early optimizations that the compiler could do better than you. – greydet Jul 28 '16 at 09:19
  • Loop unrolling can't achieve performance of constant time lookup through a table. What about readability vs performance, I concur with you, and my first comment contains an indication of that. – Leon Jul 28 '16 at 09:23
  • @greydet I think you assume that the switch-case construct will compile to the same code as a series of if-else statements. The compiler is allowed to generate such code, but the intention with the switch-case construct is that it should be possible for the compiler to use a lookup table, which is much faster if there are many cases. This is why every case expression must calculate into an "integer constant expression" at compile time. It seems impossible to extract such a constant from a character in a constant string. I see it as a limitation of the preprocessor. – Hans Jul 29 '16 at 15:35
0

Definition of constants on one line:

#define SKIP            's', "skip"
#define PAUSE           'p', "pause"
#define RESUME          'r', "run"

#define _FIRST_CHAR(C, V) C
#define FIRST_CHAR(V) _FIRST_CHAR(V)
#define _STRING(C, V) V
#define STRING(V) _STRING(V)

And switch statement:

switch(*buffer)
{
    case FIRST_CHAR(SKIP): 
        break;
    case FIRST_CHAR(PAUSE):
        break;
    case FIRST_CHAR(RESUME):
        break;
}

Extracting string from macro:

const char* str = STRING(SKIP);
Ission
  • 1
  • 2
-4
#define MY_STRING "Hello"

const char var[] = MY_STRING;

switch(var[0]) {
    case 'H':
    break;
    case 'A':
    break;
}

This should solve it.