This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

%bu vs. %hu in printf()

I am using simultaneously in the 8bit C51 and 16bit compilers for two related projects.

Am I correct that for 8bit integers,
8-bit compiler uses %bu in printf()
16bit compiler uses %hu
?
Shouldn't they work the same, and accept either modifier?

Thank you.

Parents
  • Ah, sorry, now I actually looked it up, I see I was making an incorrect transfer of knowledge from floating-point to integer printing formats. In floats, %hf actually doesn't exist, but in integers, it does ---
    sorry for having caused any uncertainty or confusion.

    There can be a difference in results between %hu and %u for printf() only if unsigned int and unsigned short have different ranges. Which is not the case in either of the compilers discussed here (C51, C166). I.e. in these compilers, you don't actually need %hu instead of %u --- it's better to put it in, though, for the sake of maximum portability of the code.

    Keil's '51 and '166 implementations of printf() could simply ignore the 'h' length modifier, and your experiment suggests that's exactly what they do. But IMHO they really should document this fact. Looking at the documentation, one would actually expect to get an error message from C51 for use of %hu, although that's perfectly valid standard C code.

    The 'b' length modifier is, indeed, a Keil-ism. It's needed in C51 because that compiler explicitly violates the ANSI/ISO C89 standard in one important aspect: arguments to var-args functions that fit into 8 bits aren't automatically expanded to 16 bits, contrary to the language definition. IMHO, this behaviour should be changed by the [NO]INTPROMOTE switch, but apparently it's not. So printf() will actually receive 8-bit values as arguments, which in ANSI C is supposed never to happen, and therefore it needs a special length specifier to tell it about this.

Reply
  • Ah, sorry, now I actually looked it up, I see I was making an incorrect transfer of knowledge from floating-point to integer printing formats. In floats, %hf actually doesn't exist, but in integers, it does ---
    sorry for having caused any uncertainty or confusion.

    There can be a difference in results between %hu and %u for printf() only if unsigned int and unsigned short have different ranges. Which is not the case in either of the compilers discussed here (C51, C166). I.e. in these compilers, you don't actually need %hu instead of %u --- it's better to put it in, though, for the sake of maximum portability of the code.

    Keil's '51 and '166 implementations of printf() could simply ignore the 'h' length modifier, and your experiment suggests that's exactly what they do. But IMHO they really should document this fact. Looking at the documentation, one would actually expect to get an error message from C51 for use of %hu, although that's perfectly valid standard C code.

    The 'b' length modifier is, indeed, a Keil-ism. It's needed in C51 because that compiler explicitly violates the ANSI/ISO C89 standard in one important aspect: arguments to var-args functions that fit into 8 bits aren't automatically expanded to 16 bits, contrary to the language definition. IMHO, this behaviour should be changed by the [NO]INTPROMOTE switch, but apparently it's not. So printf() will actually receive 8-bit values as arguments, which in ANSI C is supposed never to happen, and therefore it needs a special length specifier to tell it about this.

Children
  • "In floats, %hf actually doesn't exist, but in integers, it does"

    You're thinking of the %lf conversion in scanf() for a double which doesn't exist in printf(). I incorrectly used %lf in printf() for many years with many compilers and every single one of them ignored it - which is presumably why I was able to go on doing the wrong thing for so long without noticing.

    "Looking at the documentation, one would actually expect to get an error message from C51 for use of %hu, although that's perfectly valid standard C code."

    Compilers are not required to, and generally do not, parse the format string. It would be impossible for them to do so in all situations. You can stick any old rubbish you like in there without getting a diagnostic.

    "The 'b' length modifier is, indeed, a Keil-ism. It's needed in C51 because that compiler explicitly violates the ANSI/ISO C89 standard in one important aspect: arguments to var-args functions that fit into 8 bits aren't automatically expanded to 16 bits, contrary to the language definition. IMHO, this behaviour should be changed by the [NO]INTPROMOTE switch, but apparently it's not."

    The point here is that the default *integer* promotions and the default *argument* promotions are quite different things, so it would make little sense to have the INTPROMOTE switch affect argument promotions.

    "So printf() will actually receive 8-bit values as arguments, which in ANSI C is supposed never to happen, and therefore it needs a special length specifier to tell it about this."

    Another related gotcha is the %c specifier - the standard states that %c expects an int argument. In an ANSI implementation this means that a char argument will also work as the char will be promoted to int, however Keil's printf() expects (and will only work with) a char.

    An interesting side point: As I understand it Keil is what the standard calls a 'freestanding' implementation (no OS present on the target). It is therefore not required to implement the 'C' library functions at all but can still be a conforming implementation. I cannot determine whether a freestanding implementation is required to ensure that if it does implement a library function that it implements it in strict accordance with the standard or not. Or, for that matter, whether it is required to document any differences from ANSI behaviour in this situation.

  • My simple question seems indeed to have a non-trivial and non-obvious answer wrt various C implementations. Thank you for the answers.

    (Andrew, Hans: pardon, my ref to short should have indeed been char. In my mind, when working with 8bit i/o registers and small loop counters, I don't think of them as characters, but integers--which I typecast to int8u or int8s.)

    Stefan: regarding the %c, will it correctly handle signed 8bit integers as well?
    In general, I wonder what a good strategy is for handling these instances... for example, is it advisable to cast printf() arguments explicitly? Perhaps there is a big efficiency penalty vs. simply using the appropriate modifier.

  • You're thinking of the %lf conversion in scanf() for a double which doesn't exist in printf().

    No. I was thinking of exactly what I wrote: that the 'h' modifier doesn't exist at all, for floating-point, neither in printf() nor in scanf().

    Compilers are not required to, and generally do not, parse the format string.

    Quite a number of compilers do parse it, if they can see it. GCC, e.g., does. And IMHO such checks are even more important to be done by a compiler for embedded systems, like C51, where the runtime library has essentially no sane way of signalling a runtime error.