This source code is switching on a string in C. How does it do that?

10,223

Solution 1

(Only you can answer the "macro trickery" part - unless you paste up more code. But there's not much here for macros to work on - formally you are not allowed to redefine keywords; the behaviour on doing that is undefined.)

In order to achieve program readability, the witty developer is exploiting implementation defined behaviour. 'eax' is not a string, but a multi-character constant. Note very carefully the single quotation characters around eax. Most likely it is giving you an int in your case that's unique to that combination of characters. (Quite often each character occupies 8 bits in a 32 bit int). And everyone knows you can switch on an int!

Finally, a standard reference:

The C99 standard says:

6.4.4.4p10: "The value of an integer character constant containing more than one character (e.g., 'ab'), or containing a character or escape sequence that does not map to a single-byte execution character, is implementation-defined."

Solution 2

According to the C Standard (6.8.4.2 The switch statement)

3 The expression of each case label shall be an integer constant expression...

and (6.6 Constant expressions)

6 An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof operator.

Now what is 'eax'?

The C Standard (6.4.4.4 Character constants)

2 An integer character constant is a sequence of one or more multibyte characters enclosed in single-quotes, as in 'x'...

So 'eax' is an integer character constant according to the paragraph 10 of the same section

  1. ...The value of an integer character constant containing more than one character (e.g., 'ab'), or containing a character or escape sequence that does not map to a single-byte execution character, is implementation-defined.

So according to the first mentioned quote it can be an operand of an integer constant expression that may be used as a case label.

Pay attention to that a character constant (enclosed in single quotes) has type int and is not the same as a string literal (a sequence of characters enclosed in double quotes) that has a type of a character array.

Solution 3

As other have said, this is an int constant and its actual value is implementation-defined.

I assume the rest of the code looks something like

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

You can be sure that 'eax' in the first part has the same value as 'eax' in the second part, so it all works out, right? ... wrong.

In a comment @Davislor lists some possible values for 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, or something else

Notice the first potential value? That is just 'e', ignoring the other two characters. The problem is the program probably uses 'eax', 'ebx', and so on. If all these constants have the same value as 'e' you end up with

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

This doesn't look too good, does it?

The good part about "implementation-defined" is that the programmer can check the documentation of their compiler and see if it does something sensible with these constants. If it does, home free.

The bad part is that some other poor fellow can take the code and try to compile it using some other compiler. Instant compile error. The program is not portable.

As @zwol pointed out in the comments, the situation is not quite as bad as I thought, in the bad case the code doesn't compile. This will at least give you an exact file name and line number for the problem. Still, you will not have a working program.

Solution 4

The code fragment uses an historical oddity called multi-character character constant, also referred to as multi-chars.

'eax' is an integer constant whose value is implementation defined.

Here is an interesting page on multi-chars and how they can be used but should not:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


Looking back further away into the rearview mirror, here is how the original C manual by Dennis Ritchie from the good old days ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) specified character constants.

2.3.2 Character constants

A character constant is 1 or 2 characters enclosed in single quotes ‘‘ ' ’’. Within a character constant a single quote must be preceded by a back-slash ‘‘\’’. Certain non-graphic characters, and ‘‘\’’ itself, may be escaped according to the following table:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

The escape ‘‘\ddd’’ consists of the backslash followed by 1, 2, or 3 octal digits which are taken to specify the value of the desired character. A special case of this construction is ‘‘\0’’ (not followed by a digit) which indicates a null character.

Character constants behave exactly like integers (not, in particular, like objects of character type). In conformity with the addressing structure of the PDP-11, a character constant of length 1 has the code for the given character in the low-order byte and 0 in the high-order byte; a character constant of length 2 has the code for the first character in the low byte and that for the second character in the high-order byte. Character constants with more than one character are inherently machine-dependent and should be avoided.

The last phrase is all you need to remember about this curious construction: Character constants with more than one character are inherently machine-dependent and should be avoided.

Share:
10,223

Related videos on Youtube

Ian Colton
Author by

Ian Colton

Updated on June 05, 2022

Comments

  • Ian Colton
    Ian Colton almost 2 years

    I'm reading through some emulator code and I've countered something truly odd:

    switch (reg){
        case 'eax':
        /* and so on*/
    }
    

    How is this possible? I thought you could only switch on integral types. Is there some macro trickery going on?

    • 0___________
      0___________ over 6 years
      it is not the string 'eax' and it enumerates constant integer value
    • Davislor
      Davislor over 6 years
      Single quotes, not double. A character constant is promoted to int, so it’s legal. However, the value of a multi-character constant is implementation-defined, so the code might not work as expected on another compiler. For example, eax might be 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, or something else.
    • Steve Jessop
      Steve Jessop over 6 years
      @Davislor: given the name of the variable "reg", and the fact that eax is an x86 register, I would guess that the implementation-defined behaviour was intended to be OK, because it's the same everywhere it's used in the code. Just as long as 'eax' != 'ebx', of course, so it only fails one or two of your examples. Although there might be some code somewhere that in effect assumes *(int*)("eax") == 'eax', and therefore fails most of your examples.
    • Davislor
      Davislor over 6 years
      @SteveJessop I don’t disagree with what you say, but there is the real danger that someone could try to compile the code on a different compiler, even for the same architecture, and get different behavior. For example, 'eax' might compare equal to 'ebx' or to 'ax', and the switch statement would not work as intended.
    • ths
      ths over 6 years
      All of that mystery would have quickly been dispelled if you had looked up/shown us the data type of reg.
    • Kaz
      Kaz over 6 years
      By the way, I would tend to consider this code stinky. Why didn't the original designer just define an enumerated constant reg_eax with a nice value, like zero? switch statements encompassing sets of non-consecutive, large values do not compile into nice jump tables.
  • Leushenko
    Leushenko over 6 years
    Just in case anyone sees that and panics, "implementation-defined" is required to work and to be documented by your compiler in some appropriate fashion (the standard doesn't require that the behaviour be intuitive or that the documentation be any good, but...). This is "safe" to use for a coder who fully understands what they're writing, as opposed to "undefined".
  • Zan Lynx
    Zan Lynx over 6 years
    Just a note: the original intent was for multibyte characters like Unicode. One UTF8 "character" on screen may be up to four bytes.
  • Justin
    Justin over 6 years
    Seems to me like a conforming implementation could do something weird like define all multi-character constants to be equal, so just blindly using them doesn't seem like a good idea. Should definitely consult your implementation's documentation before making any assumptions about them
  • Barmar
    Barmar over 6 years
    @Justin While it could, that would be quite perverse. If it doesn't do what the answer suggests is most likely, the next possibility is probably that it just uses the first character and ignores the rest.
  • Spike0xff
    Spike0xff over 6 years
    people who write compilers are not (in general) insane, so they try hard to map implementation-defined constructs in a useful and logical way, and to issue warnings when they can't. Because... engineering. -ex compiler guy.
  • jpmc26
    jpmc26 over 6 years
    @Barmar Having officially "undefined" behavior where the compiler is allowed to make demons fly out of your nose, to the point that you have to constantly guard against accidentally invoking it, is pretty perverse to begin with...
  • Justin
    Justin over 6 years
    @Barmar Your "next possibility" reinforces my main point: before making any assumptions on what multibyte constants mean, you should consult your implementation's documentation
  • Russell Borogove
    Russell Borogove over 6 years
    @ZanLynx I'm not positive, but I believe the feature long predates Unicode and other MBCS standards. "Magic numbers" that look like text in memory dumps and RIFF-style file-format-chunk IDs were the first applications I'm aware of.
  • Barmar
    Barmar over 6 years
    @jpmc26 This is not undefined behavior, it's implementation-defined. So unless the compiler documentation mentions demons, your nose is safe.
  • Admin
    Admin over 6 years
    @jpmc26: Undefined behavior usually lurks in places you have to guard against anyways, so that's not really a fair description.
  • chqrlie
    chqrlie over 6 years
    @ZanLynx: I'm afraid the original intent predates Unicode, UTF-8 and any multibyte character encoding by almost 20 years. multi-character constant were just a handy way to express integers representing groups of 2, 3 or 4 bytes (depending on the byte and int sizes). Inconsistencies across implementations and architectures led the committee to declare this as implementation defined, which means there is no portable way to compute the value of 'ab' from 'a' and 'b'.
  • Dan Is Fiddling By Firelight
    Dan Is Fiddling By Firelight over 6 years
    other than some form of assert('eax' != 'ebx'); //if this fails you can't compile the code because... is there anything the original author could do to prevent other compiler failures without replacing the construct entirely>
  • zwol
    zwol over 6 years
    Two case labels with the same value are a constraint violation (6.8.4.2p3: "...no two of the case constant expressions in the same switch statement shall have the same value after conversion") so, as long as all the code treats the values of these constants as opaque, this is guaranteed either to work or to fail to compile.
  • Zan Lynx
    Zan Lynx over 6 years
    @chqrlie: I am pretty sure that you're wrong about that. It was always about encoding character sets for languages other than English ASCII. The ability to make an integer of 4 bytes is just happy coincidence..
  • chqrlie
    chqrlie over 6 years
    @ZanLynx: here is an interesting page on multi-chars: zipcon.net/~swhite/docs/computers/languages/… and looking back further away into the rearview mirror, here is the original C manual from the good old days: bell-labs.com/usr/dmr/www/cman.pdf .
  • chqrlie
    chqrlie over 6 years
    @ZanLynx: 2.3.2 character constants [...] Character constants behave exactly like integers (not, in particular, like objects of character type). In conformity with the addressing structure of the PDP-11, a character constant of length 1 has the code for the given character in the low-order byte and 0 in the high-order byte; a character constant of length 2 has the code for the first character in the low byte and that for the second character in the high-order byte. Character constants with more than one character are inherently machine-dependent and should be avoided.
  • supercat
    supercat over 6 years
    @ZanLynx: The ability to interpret a sequence of four characters to a 32-bit unsigned value was supported by Macintosh C compilers, probably going back to their 1986 (since it had previously been supported by Macintosh Pascal compilers). Maybe some machines somewhere used multi-byte characters, but they certainly weren't common.
  • jpmc26
    jpmc26 over 6 years
    @Barmar I noticed that. I was saying the language is already perverse, so one more perverse behavior wouldn't change that much.
  • Kaz
    Kaz over 6 years
    Multi-character constants have nothing to do with Unicode. They simply reflect the idea that multiple characters can be packed into a word, and this is useful for creating constants which spell something.
  • Kaz
    Kaz over 6 years
    Apple added that most likely because their platform was chock full of "fourcc" codes for identifying data types and formats. For instance, files types. Fourcc codes were often chosen which spelled something when interpreted as ASCII bytes.
  • Zan Lynx
    Zan Lynx over 6 years
    Since multibyte character literals are in exactly the same place in the standard as wide characters, both are for non-ASCII language support. Otherwise '♂' would look funny.
  • tucuxi
    tucuxi over 6 years
    The worse part is that the poor fellow compiling on another compiler probably will not see any compile-time error (switching on ints is fine); instead, run-time errors will crop up...
  • supercat
    supercat over 6 years
    @Kaz: Yup. Software could copy and compare such things using a single operation on a "LongInt" [Pascal] or "long" [C], but tools to manipulate things like file, application, or resource types could show them in human-readable format. File, application, or resource types with fewer than four characters were blank-padded (so a "snd " resource would be have a type of 0x736e6420), and the first character of every type that was publicly used was less than 0x80, so the lack of an unsigned 32-bit type in Pascal didn't cause any weirdness with those types.
  • Kaz
    Kaz over 6 years
    @supercat Of course, since those character constants are not portable, what you do in portable C is FOURCC('m', 'o', 'o', 'v') via a macro. It's less cumbersome to just be able to use 'moov'.
  • Kaz
    Kaz over 6 years
    "Since multibyte character literals are in exactly the same place in the standard as wide characters, both are for non-ASCII language support." Does not logically follow. They are in the same place because they are syntactically related; they are character constants.
  • supercat
    supercat over 6 years
    @Kaz: In the days when people writing compilers for a platform could be expected to honor that platforms idioms, potability of such constructs was a non-issue. If one was using the Macintosh Resource Manager, File Manager, or Desktop Manager functions, that meant one was writing code for the Macintosh and would thus be using a compiler designed for that platform.
  • Casanova
    Casanova over 6 years
    Just wondering, did you use a fake account to ask this question?