> # elif ('\0'|(1 << (CHAR_BIT-1))) < 0
The whole thing is just going to get promoted all the way up some large type.Originally Posted by c99
> # elif ('\0'|(1 << (CHAR_BIT-1))) < 0
The whole thing is just going to get promoted all the way up some large type.Originally Posted by c99
If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
If at first you don't succeed, try writing your phone number on the exam paper.
Well gave that (char)-1 a try to prove a point but that turned out to be my 1st time not getting a compiler error with that sort of thing, on the other hand it gave erronious results.
Gave this result:Code:#ifdef CHAR_SIGNED alu_puts("CHAR_SIGNED"); #endif #ifdef CHAR_UNSIGNED alu_puts("CHAR_UNSIGNED"); #endif #if (char)-1 < 0 alu_printf("(char)-1 < 0 = %i", 1); #else alu_printf("(char)-1 < 0 = %i", 0); #endif
Although I will note that CHAR_MAX & CHAR_MIN appear to be lining up with the second result which is confusingCode:test.c:264: main() 'CHAR_UNSIGNED' test.c:267: main() (char)-1 < 0 = 1
I see nothing unexpected...
Code:#include <stdio.h> #define CHAR_SIGNED ((char)-1 < 0) #define CHAR_UNSIGNED (!CHAR_SIGNED) int main(void) { #if defined CHAR_SIGNED && defined CHAR_UNSIGNED puts("Everything is ok so far"); printf("CHAR_SIGNED == %d, CHAR_UNSIGNED == %d\n", CHAR_SIGNED, CHAR_UNSIGNED); #endif #if CHAR_SIGNED puts("char is signed"); #endif #if CHAR_UNSIGNED puts("char is unsigned"); #endif #ifdef CHAR_SIGNED puts("CHAR_SIGNED"); #endif #ifdef CHAR_UNSIGNED puts("CHAR_UNSIGNED"); #endif #if (char)-1 < 0 printf("(char)-1 < 0 = %i", 1); #else printf("(char)-1 < 0 = %i", 0); #endif }Code:Everything is ok so far CHAR_SIGNED == 1, CHAR_UNSIGNED == 0 char is signed CHAR_SIGNED CHAR_UNSIGNED (char)-1 < 0 = 1
You can do the same thing in the compiler, which is (IMO) cleaner than using the preprocessor:
But I'll ask my question again: when would you ever care whether char is signed or unsigned in an actual program, and not just in an academic exercise in writing macros?Code:if (CHAR_SIGNED) { alu_puts("CHAR_SIGNED"); } else if (CHAR_UNSIGNED) { alu_puts("CHAR_UNSIGNED"); } else { alu_puts("not CHAR_SIGNED and not CHAR_UNSIGNED??"); } if ((char)-1 < 0) { alu_printf("(char)-1 < 0 = %i", 1); } else { alu_printf("(char)-1 < 0 = %i", 0); } // or: alu_printf("(char)-1 < 0 = %i", (char)-1 < 0);
I needed it for the fallbacks of CHAR_MAX & CHAR_MIN, also I expect the main place to care would be functions like iconv() (if I remembered that rightly that is) where a programmed check should be a last resort since that check can be optimised out by using such macros, it needs to do it that way because it is a heavy use function
which behaves differently under the hood depending on whether char is signed or unsigned and how many bits it has access to.
You should note that based on the code I gave my output should not have reported only CHAR_UNSIGNED (or CHAR_UNSIGNED at all) before the (char)-1 < 0 = # part but should have reported CHAR_SIGNED before both, I afterwords changed how I defined CHAR_SIGNED & CHAR_UNSIGNED & how I checked them when deciding what to report and the conflicting results disapeared afer the change
Eh, you expect that SCHAR_MAX, UCHAR_MAX, and SCHAR_MIN to be available in <limits.h>, but not necessarily CHAR_MIN and CHAR_MAX?! On which implementation have you observed this to be the case? I would expect all of them to be available, or if any of them are not available, it's a non-standard conforming implementation such that it is reasonable to suspect that none of them will be available, and perhaps <limits.h> might not even exist. If you read the C standard, it provides a note saying:Originally Posted by awsdert
That is, it is expected that you'll determine whether char is signed or unsigned by checking if CHAR_MIN is 0 or SCHAR_MIN, not the other way round. Such a check can of course be done at compile-time, so there is no runtime penalty. You should be aware that christop's code in post #19, although it does not use the preprocessor is equally efficient since the values are constants known at compile-time, and hence will be optimised away (likely even at low optimisation levels). It is like writing:CHAR_MIN, defined in <limits.h>, will have one of the values 0 or SCHAR_MIN, and this can be used to distinguish the two options.
The if statement will be optimised away such that the result will be as if one wrote:Code:if (1 == 1) { puts("Hello world!"); } else { puts("Goodbye world!"); }
Code:puts("Hello world!");
Last edited by laserlight; 08-12-2020 at 04:53 PM.
Look up a C++ Reference and learn How To Ask Questions The Smart WayOriginally Posted by Bjarne Stroustrup (2000-10-14)
Try compiling this code:
and I can almost guarantee that you will see only one of the printed strings in the executable. Like I said awhile back, any halfway-decent compiler will optimize that if statement and completely remove the non-taken branch. It'll be equivalent to this code on an implementation where char is signed:Code:if ((char)-1 < 0) { puts("char is signed"); } else { puts("char is unsigned"); }
and to this code on an implementation where char is unsigned:Code:puts("char is signed");
So the code will be just as fast whether you check for signed/unsigned char in the preprocessor or in the compiler proper.Code:puts("char is unsigned");
Same goes for checking the bit size of char. You could have a series of "if ((unsigned char)(1 << 16)) .. if ((unsigned char)(1 << 8)) ... else" statements in your code and it'll be optimized. Here's an example:
Compiler output (gcc -S test-char.c):Code:#include <stdio.h> int main() { if ((unsigned char)(1 << 16)) { puts(">16"); } else if ((unsigned char)(1 << 15)) { puts("16"); } else if ((unsigned char)(1 << 14)) { puts("15"); } else if ((unsigned char)(1 << 13)) { puts("14"); } else if ((unsigned char)(1 << 12)) { puts("13"); } else if ((unsigned char)(1 << 11)) { puts("12"); } else if ((unsigned char)(1 << 10)) { puts("11"); } else if ((unsigned char)(1 << 9)) { puts("10"); } else if ((unsigned char)(1 << 8)) { puts("9"); } else if ((unsigned char)(1 << 7)) { puts("8"); } else { puts("<8"); } return 0; }
Only one string, "8", made it to the executable. And that's with GCC's default optimization level (same results as -O0).Code:.file "test-char.c" .text .section .rodata .LC0: .string "8" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 leaq .LC0(%rip), %rdi call puts@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0" .section .note.GNU-stack,"",@progbits
Besides that, I think it's a fool's errand to try to rewrite limits.h since any compiler that wants to call itself "C" must provide it (and I don't see any portable way to write macros contained in limits.h, which is why we are given limits.h in the first place).
That's a very good point. Here's what C11 has to say:Originally Posted by christop
Notice that both <limits.h> and <stdint.h> are in the list, i.e., even if you are programming for embedded systems etc such that you worry that not all of the standard library might be available, there's absolutely no worry about <limits.h> being unavailable. You might be wary about <stdint.h> since it was introduced in C99 (but that was two decades ago!), and likewise for the constants for long long and unsigned long long, but <limits.h> and the constants for char and the like have been standard since C has been standardised in the late 1980s/1990. If you cannot depend on this, then you can only depend on the documentation for these particular standard non-conforming compilers, so you should be making reference to these compilers otherwise you have nothing to stand on. After all, how do you know that the char type even exists? In a deliberately standard non-conforming implementation, it doesn't have to exist.A conforming freestanding implementation shall accept any strictly conforming program that does not use complex types and in which the use of the features specified in the library clause (clause 7) is confined to the contents of the standard headers <float.h>, <iso646.h>, <limits.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, and <stdint.h>.
Look up a C++ Reference and learn How To Ask Questions The Smart WayOriginally Posted by Bjarne Stroustrup (2000-10-14)
Yeah that's the worst case scenario, I prefer to programme around such scenarios (especially in the preprocessor) to reduce number of errors that occur, it's why I tend to throw the error found back 1st then design catches for deal-able ones after seeing them pop up in the final catch before the program aborts.
Which is precisely why I'm doing as much as I can in the preprocessor to create fallbacks for macros, things that can be optimised out, better to assume a scenario exists where they don't exist then to assume not so.
Yeah I remember reading somewhere there was an implementation that only had int, I will be defining char as int in that scenario once I found out how to catch it but for now I'm going with any portable catch I can find, they'll work as expected once I do catch that scenario for which I only need to define char and CHAR_BIT
If there is/was a C compiler that didn't support char (i.e. "that only had int") truly exists I wouldn't bother supporting it. Even Small-C (the original 1980 version) supported char.
GitHub - trcwm/smallc_v1: Source code for the original Small C compiler published in Dr. Dobbs journal.
As christop pointed out, the code tends to be cleaner when using ordinary C syntax than when using the preprocessor syntax. You're doing premature optimisation such that it is almost certainly not optimisation at all. Instead of assuming, follow good practices and test for when they don't apply.Originally Posted by awsdert
"Modern" does depend on context (my undergrad class on "modern Japanese history" started with the mid-1500s), and in this case we're looking at a particular kind of dead code elimination that likely has been common for a quarter of a century or more. (I cannot cite a source, but it's an educated guess from a book written circa 1995 that explained why for ( ;; ) was considered canonical instead of while (1), but which also explained that the underlying issue, i.e., compilers generating code that evaluated the constant rather than generating the deliberate infinite loop, were old and no longer in use... in 1995.)Originally Posted by awsdert
Look up a C++ Reference and learn How To Ask Questions The Smart WayOriginally Posted by Bjarne Stroustrup (2000-10-14)