C macro to determine if a type is signed - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 12, 2019, 11:24:52 pm
content: I need to know at compile-time whether a given type is signed, so I can enable or disable a block of code with an#if.My first thought - and after some searching around, it turns out also the most common way - was to do the following:Code:#define IS_SIGNED_TYPE(type) (((type)-1) < 0)But, at least in the scenario I'm attempting to use it in,it doesn't work!As a test, if I do this:Code:#if IS_SIGNED_TYPE(uint16_t)#warning "Type is signed"#else#warning "Type is unsigned"#endifMy compiler (AVR-GCC) spits out the "Type is signed" message. I'm at a loss to come up with a reason why this fails. What's going wrong here?

author: ataradov
author_rank: Super Contributor
timestamp: November 12, 2019, 11:52:07 pm
content: This macro is for the code. You are trying to use it in a preprocessor. And all the preprocessor stuff is dome with 'int's and there is no typecasting. The preprocessor also does not know what uint16_t is and it is just being substituted whit 0.You can replace uint16_t with whatever you want. You still essentially get "(((0)-1) < 0)", which is true in plain integers.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 13, 2019, 12:00:43 am
content: Ah, I see, thanks. I was starting to suspect it must be something to do with the preprocessor evaluating the expression differently.Is there any other way of doing such a thing at compile-time?I can make it a run-time check if I really have to, but ideally I would like to make it compile-time so I can avoid including a whole block of code altogether.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 13, 2019, 12:02:27 am
content: Remember, macros are just that, macros that are expanded before compilation.You're actually writingCode:#if (((uint16_t)-1) < 0)Which doesn't mean very much -- uint16_t isn't a number. Does it not emit a syntax error? Perhaps the preprocessor doesn't emit syntax errors on expressions (should be easy enough to look up, I don't know offhand). I would guess it's evaluating "0 - 1 < 0", which is true for any int type.Also, it may be evaluating the expression as an int (in which case those are signed zeroes), though again I don't know if the preprocessor uses types or is more basic than that.This explains a bit more:https://stackoverflow.com/questions/7469915/value-vs-type-code-to-determine-if-a-variable-is-signed-or-notNote that something that may seem obvious on familiar machines (e.g., the big-flip version, which assumes twos-complement) need not be supported by the language as such. Notably, C supports ones-complement and other more obscure number systems, more of historical interest but they remain considerations in the standard.P.S. It occurs to me you may've meant:Code:uint16_t var;...#if IS_SIGNED_TYPE(var)...which shouldn't result in a syntax error, but it seems just as wrong to test run-time variables at compile-time. Again, I'm not sure what error this would be.As an expression, evaluated at run time, the function would work fine though. e.g. if(IS_SIGNED_TYPE(var)) { ...}Tim

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 13, 2019, 12:12:59 am
content: Quote from: T3sl4co1l on November 13, 2019, 12:02:27 amNo, it doesn't.I only got an error when I tried it as "#if IS_SIGNED_TYPE(unsigned int)". I forget right now what the error was - something regarding a right-parenthesis.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 13, 2019, 12:13:10 am
content: It doesn't work indeed. I get the same result with GCC for other targets.I don't think it can really work actually. Just pass anything as the argument of the IS_SIGNED_TYPE() macro, and you'll get the same result. The cast gets simply ignored. Big surprise.It appears the cast can't be evaluated properly at compile time inside a macro unfortunately: when the macros are processed, you are at the preprocessing stage. The preprocessor has no knowledge of C types. At the preprocessing stage, the compiler itself is not yet doing anything.The context in which the above macro would work is if you use it in the run-time part of your code, and not in a conditional compilation (so maybe that's exactly what the people who suggest it were doing).Like inside a function:Code:typedef uint16_t MyType_t;int SomeFunction(){if (IS_SIGNED_TYPE(MyType_t)){... Do this ...}else{... Do that ...}}Note that even at low optimization levels, it should be just as efficient as using a conditional compilation: any test that can only be true or false at compile-time will only retain the corresponding part of the code.That said, also note that what you wanted to do is thus not possible: you can't have a warning issued during compilation (#warning) depending on the case. Try putting #warning statements in the then and else clauses. Both will appear: the preprocessor again has no way of processing the code itself and will just spit out both warnings.I can't think of a portable and standard way of doing this in conditional compilation directives. May be doable with the additions in C11, I'll check that and will report back if so.

author: ataradov
author_rank: Super Contributor
timestamp: November 13, 2019, 12:23:57 am
content: Quote from: T3sl4co1l on November 13, 2019, 12:02:27 amC preprocessor does expression evaluation on its own. All types are ints and all unknown symbols are turned into 0.And yes, just use a normal "if". Any sane compiler will optimize that stuff anyway.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 13, 2019, 12:26:00 am
content: Quote from: SiliconWizard on November 13, 2019, 12:13:10 amAh, I hadn't thought about a run-time check in that way - that the compiler might optimise-away the block in question. I will have to do it and look at the generated assembly.Edit:It does indeed optimise away the block of code if the type is unsigned. Great!

author: TK
author_rank: Super Contributor
timestamp: November 13, 2019, 12:33:49 am
content: Why do you need to test the signed/unsigned type during compile time if YOU are writing the code?

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 13, 2019, 12:41:00 am
content: Quote from: TK on November 13, 2019, 12:33:49 amBecause I want to make it so that the only thing I have to change is a typedef to enable certain parts of the code. So basically, convenience.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 13, 2019, 12:53:15 am
content: Quote from: HwAoRrDk on November 13, 2019, 12:41:00 amOk, so this should work fine for your needs. The thing that won't, as I said, is the #warning part... but apparently, if it's just for enabling certain parts of the code, it's ok. You can do without the warning.Anyway, I tried something with the new _Generic() construct in C11. Unfortunately, _Generic statements are NOT expanded in #if directives, so that still doesn't work for that case.If interested for other uses, you can take a look at what you could do with _Generic though, it can be handy - dunno if AVR-GCC supports C11, but if it's based on a reasonably recent version of GCC, it should.

author: TK
author_rank: Super Contributor
timestamp: November 13, 2019, 12:57:30 am
content: Quote from: HwAoRrDk on November 13, 2019, 12:41:00 amYou can do something like this as you already know if it is signed or unsigned (assume uint* and unsigned* are unsigned, all the others signed). I think this is 100% compiler independent.I use unsigned when programming microcontrollers.Code:// Comment the next 2 lines if using unsignedtypedef int typex;#define SIGNED 1// Comment the next line if using signed//typedef uint16_t typex;#ifdef SIGNED...#else...#endif

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 13, 2019, 10:50:39 am
content: Yeah, setting a flag is probably the better way. You don't have to rely on optimizations to do it -- #if-blocks are the traditional way of enabling pieces of code.Tim

author: magic
author_rank: Super Contributor
timestamp: November 13, 2019, 12:31:02 pm
content: Then you may want to add a hack to break compilation if the type is not marked as signed by mistake:Code:#ifndef SIGNEDint dummy[-IS_SIGNED(type)]#endifThis forces the compiler to honestly evaluate IS_SIGNED(type) and create an array of 0 or -1 elements. The former is OK, the latter is an error.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 13, 2019, 04:25:27 pm
content: As a bonus, a simple example of how you can use _Generic in C11:Code:int Abs(uint16_t n){return _Generic(n,uint16_t: n,int16_t: (n < 0)? -n : n);}

author: Brutte
author_rank: Frequent Contributor
timestamp: November 13, 2019, 05:57:48 pm
content: You can use gcc extension__builtin_types_compatible_pwhich returns 1 if arguments are compatible.

author: andersm
author_rank: Super Contributor
timestamp: November 13, 2019, 07:32:51 pm
content: Man, just go C++ already.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 13, 2019, 08:13:21 pm
content: Signed overflow/undeflow handling in GCC is well defined, and all unsigned types (on architectures GCC supports) are the exact same size as their signed variants, so I'm puzzled as to what is OP's use case.The only way I know offhand of how to handle this kind of situation, is to write a prebuild checker, that outputs the definitions for type-specific signedness macros to a generated header file; sayCHAR_IS_SIGNED,MYTYPE_IS_SIGNED, and so on. The actual source can then do#if CHAR_IS_SIGNED==1et cetera. However, this precludes cross compilation, although that can be worked around by providing handwritten variants of the generated header files.So, what's the actual use case? I bet there is a better solution...

author: blacksheeplogic
author_rank: Frequent Contributor
timestamp: November 18, 2019, 06:58:07 am
content: Quote from: HwAoRrDk on November 13, 2019, 12:41:00 amPass it though as a compiler option using -Dfor example:EXTRA_CFLAGS = -DXXXX#ifdef XXXX<whatever>#endif

author: blacksheeplogic
author_rank: Frequent Contributor
timestamp: November 18, 2019, 07:06:05 am
content: Quote from: Nominal Animal on November 13, 2019, 08:13:21 pmI don't like doing it but in the past where I've needed a pre-processing step I deal with it in the makefile:.SUFFIXES .cx.cx.c:<pre-processing step>And before you point it out, yes, that is the old school way, but I'm old and so are many of my makefiles.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 18, 2019, 08:41:45 am
content: Quote from: blacksheeplogic on November 18, 2019, 07:06:05 amIn this case, you need a test program, that when compiled and run, emits the header definitions. I'd use something along the lines ofCode:include/signedness.h:$(CC) $(CFLAGS) $(SRCDIR)/generate-signedness.c $(LDFLAGS) -o host-bin/generate-signedness$(EXESUFFIX)host-bin/generate-signedness$(EXESUFFIX) > $@i.e. compile a test program with the current configuration, then run it; and have it emit the C header file with the signedness definitions.You'll then need to listinclude/signedness.has a prerequisite to the sources that refer to it.Like I said, the downside is that when cross-compiling to another architecture, the second step should be run on the target architecture and not locally (although the only base type whose signedness varies from architecture to architecture ischar, I believe). If the recipe is listed without prerequisites, then providing ainclude/signedness.hfile suitable for the target architecture suffices; make won't try to regenerate it.

author: golden_labels
author_rank: Super Contributor
timestamp: November 29, 2019, 07:19:37 pm
content: ⚠ Warning: that code hasn’t been tested in production and should be considered experimentalI wonder if that would work, if you’re caring only about required(1)built-in types for which signedness is defined or can be determined from the preprocessor(2), or about any typedef of any of those types:Code:// Copyright © 2019 mpan; <https://mpan.pl/>; CC0 1.0 (THIS CODE!)#include <limits.h>#include <stddef.h>#include <stdint.h>#include <stdlib.h>#include <wchar.h>#define OVERLOAD_BY_SIGN(value, signedFn, unsignedFn) _Generic((value),\unsigned char: (unsignedFn),\unsigned short: (unsignedFn),\unsigned int: (unsignedFn),\unsigned long: (unsignedFn),\unsigned long long: (unsignedFn),\signed char: (signedFn),\short: (signedFn),\int: (signedFn),\long: (signedFn),\long long: (signedFn),\char: OVERLOAD_BY_SIGN_CHAR((unsignedFn), (signedFn)),\default: _Generic((value),\_Bool: (unsignedFn), default: _Generic((value),\size_t: (unsignedFn), default: _Generic((value),\ptrdiff_t: (signedFn), default: _Generic((value),\uintmax_t: (unsignedFn), default: _Generic((value),\intmax_t: (signedFn), default: _Generic((value),\uint_least16_t: (unsignedFn), default: _Generic((value),\uint_least32_t: (unsignedFn), default: _Generic((value),\uint_least64_t: (unsignedFn), default: _Generic((value),\int_least16_t: (signedFn), default: _Generic((value),\int_least32_t: (signedFn), default: _Generic((value),\int_least64_t: (signedFn), default: _Generic((value),\uint_fast8_t: (unsignedFn), default: _Generic((value),\uint_fast16_t: (unsignedFn), default: _Generic((value),\uint_fast32_t: (unsignedFn), default: _Generic((value),\uint_fast64_t: (unsignedFn), default: _Generic((value),\int_fast8_t: (signedFn), default: _Generic((value),\int_fast16_t: (signedFn), default: _Generic((value),\int_fast32_t: (signedFn), default: _Generic((value),\int_fast64_t: (signedFn), default: _Generic((value),\wchar_t: OVERLOAD_BY_SIGN_WCHAR((unsignedFn), (signedFn)),\default: _Generic((value),\default: (abort)\)))))))))))))))))))))\)(value);#if CHAR_MIN < 0#define OVERLOAD_BY_SIGN_CHAR(unignedFn, signedFn) (signedFn)#else#define OVERLOAD_BY_SIGN_CHAR(unignedFn, signedFn) (unsignedFn)#endif#if WCHAR_MIN < 0#define OVERLOAD_BY_SIGN_WCHAR(unignedFn, signedFn) (signedFn)#else#define OVERLOAD_BY_SIGN_WCHAR(unignedFn, signedFn) (unsignedFn)#endifUse:Code:#include <stdio.h>static inline void signedCall(int const x) {printf("signed: %d\n", x);}static inline void unsignedCall(unsigned int const x) {printf("unsigned: %u\n", x);}int main(void) {Type x = 1;OVERLOAD_BY_SIGN(x, signedCall, unsignedCall);return 0;}That definition can be extended as you wish by adding yet another line to the list:Code:<Type>: (<unsignedFn|signedFn>), default: _Generic((value),\A matching parenthesis must be added to the parens in the penultimate line. Unlike with plain_Generic, types can be duplicated in this structure.The way this code works is as follows. It has two parts. The outer_Genericselector, which is really just an optimization for types that surely aren’t compatible with each other, and the inner_Generic·s sequence that performs the job for all other types. The inner_Generic·s are really a form of a linked list:Code:,-------. ,-------. ,-------. ,-------. ,-------.| Type1 | ,->| Type2 | ,->| Type3 | ,->| Type4 | ,->| Type5 || fn | | | fn | | | fn | | | fn | | | fn || o-----' | o-----' | o-----' | o-----' | o----> abort`-------' `-------' `-------' `-------' `-------'If the type ofvaluedoesn’t match the given node, it uses thedefaultassociation(3), effectively going deeper into the list. The final node is usingabortsimply because there must be thedefaultassociation there.(4)That requirement arises from the fact, that even if any “earlier”_Genericselector matches, the “later” ones still need to be valid: if the final one would not have thedefaultassociation, it would be valid only for a single type, which would break the whole code.The outer_Genericis just a plain old selector, which uses the inner one iff the type isn’t found on the list already.Some platforms may lack things likewchar_t, so this is just a template to be adjusted as needed. It is also not completely eliminating supplying informtion from external sources during build, as described in the posts of other people here, but merely limits the amount of work to be done and streamlines access to it.This code needs a compiler that eliminates unusedstatic inlinefunctions. If that requirement is not met, it will waste space in the final executable. If the compiler can do that forstaticfunctions it is even better.Alternatively, if the final “(value)” portion of the macro definition is removed, thesignedFnandunsignedFnarguments can actually be arbitrary expressions — as long as they can be used as arguments to a macro (consider putting them in parens if they contain the comma operator). The benefit is that the unused code is surely eliminated on any sane comiler and less code has to be written. The drawback is that if no type matches, the sentinelabortis a no-effect code that on some platforms may even be unreported. Consider replacing it with a call that will stop the code.____(1)Some compiler-specific built-in types have to be added manually, perhaps using conditional inclusion.(2)E.g.time_tsignedness can’t be determined.(3)6.5.1.1§2(4)6.5.1.1§2–3

author: Jeroen3
author_rank: Super Contributor
timestamp: November 29, 2019, 08:42:42 pm
content: Someone posted a solution right here. But removed it!Anyway, it doesn't use the preprocessor, but it seems to works compile time just fine. On all GNU derived compilers.It doesn't work in msvc.See here:https://godbolt.org/z/k3JC65It only generates mov and call assembly stating.Due to it not working in msvc should be an indicator that you're entering terrain that will need to carry ifdefs and erroring out on unknown compilers. Not very useful.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 29, 2019, 09:38:04 pm
content: If you want a runtime check, then I recommend usingCode:#define IS_SIGNED_TYPE(type) ( (type)(-1) < (type)(0) )which relies on C99 or later (and thus C++) casting rules: a cast to any numeric type restricts the value to the precision and range of the type. This should work fine even on MSVC. If -O1 or better optimizations are enabled, gcc and other compilers should optimize the code to no-op.Here's an example program:Code:#include <stdlib.h>#include <stdio.h>#define IS_SIGNED_TYPE(type) ( (type)(-1) < (type)(0) )int main(void){printf("char is %s\n", IS_SIGNED_TYPE(char) ? "signed" : "unsigned");printf("unsigned char is %s\n", IS_SIGNED_TYPE(unsigned char) ? "signed" : "unsigned");printf("signed char is %s\n", IS_SIGNED_TYPE(signed char) ? "signed" : "unsigned");printf("float is %s\n", IS_SIGNED_TYPE(float) ? "signed" : "unsigned");printf("long long int is %s\n", IS_SIGNED_TYPE(long long int) ? "signed" : "unsigned");printf("unsigned long long int is %s\n", IS_SIGNED_TYPE(unsigned long long int) ? "signed" : "unsigned");return EXIT_SUCCESS;}However, this is not a solution that can be utilized in the preprocessor, and that's what OP stated they needed.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 29, 2019, 10:21:04 pm
content: As I explained before, it is possible to compile and run at build time a program that generates the necessary macros for the types needed.The downside is that this won't work when cross-compiling to a different architecture or OS.Let's say you have in yourMakefile(simplified, with build and source directories the same),Code:signedness.h: generate-signedness$(CC) $(CFLAGS) -o generate-signedness generate-signedness.c./generate-signedness > $@generate-signedness: generate-signedness.c$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@%.o: %.c signedness.h$(CC) $(CFLAGS) -c $< -o $@yourprog: list of object files$(LD) $(LDFLAGS) $^ -o $@withgenerate-signedness.ccontainingCode:#include <stdlib.h>#include <inttypes.h>#include <stdio.h>#define IS_SIGNED_TYPE(type) ( (type)(-1) < (type)(0) )#define DEFINE(type) do { printf("#define type_" #type "_is_signed %d\n", IS_SIGNED_TYPE(type)); } while (0)int main(void){DEFINE(char);DEFINE(size_t);DEFINE(time_t);DEFINE(off_t);printf("#define JOIN3(a,b,c) a##b##c\n");printf("#define is_signed_type(type) JOIN3(type_, type, is_signed)\n");return EXIT_SUCCESS;}with aDEFINE(type)line for each type the code might be interested in.Then, in any of the other C source files,Code:#include "signedness.h"#if is_signed_type(time_t)/* time_t is a signed type */#else/* time_t is an unsigned type */#endif#if is_signed_type(char)/* char is a signed type */#else/* char is an unsigned type */#endifWhen cross-compiling such code, one can provide thesignedness.hfile as-is. When it exists, it will not be regenerated/replaced.Now, looking at the actual code above, I'd probably instead keep pregeneratedsignedness.hfiles in a subdirectory, with a special target to regenerate one for the current host system, and instead detect the current OS using$(OS)(and$(shell uname -s)on non-Windows systems), and architecture using$(PROCESSOR_ARCHITECTURE)and$(PROCESSOR_ARCHITEW6432)on Windows and$(shell uname -p)on all other systems, copying the correct header file to the build directory.

author: golden_labels
author_rank: Super Contributor
timestamp: November 29, 2019, 10:46:17 pm
content: Quote from: Jeroen3 on November 29, 2019, 08:42:42 pmC is not really supported by MSVC, so it is not expected that any C code will work there. Nowadays MSVC is a Microsoft C++/CLI compiler, which can also handle C++(1). C is “supported” only as long as it more or less falls under what C++ covers.____(1)But I heard it may fail to reject invalid C++ code despite required to do so.

author: Jeroen3
author_rank: Super Contributor
timestamp: November 29, 2019, 10:59:29 pm
content: Quote from: Nominal Animal on November 29, 2019, 10:21:04 pmI have written a code generator in python for some C project. It fills a linked-list from snippets defined in many files. You could go this route.There is even a C parser (compiled C) for python if you want.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 30, 2019, 01:47:03 am
content: Quote from: Jeroen3 on November 29, 2019, 10:59:29 pmNo, the issue is that the C code must be compiled and run on thetargetOS and architecture, not on thehost, to determine the signedness. You cannot determine the signedness of arbitrary types on the target by doing stuff on the host. Having a code generator does not help at all there.As to the types whose signedness is needed, that is trivial to maintain, and does not need a code generator: consider the practical use cases.

author: Kalvin
author_rank: Super Contributor
timestamp: November 30, 2019, 12:48:04 pm
content: Quote from: Jeroen3 on November 29, 2019, 08:42:42 pmIt was probably me. I didn't like the solution as it was *really* an ugly hack, and requires manual maintenance etc. Also, it won't work with something likeCode:IS_SIGNED(unsigned int) /* Will not work */However, here is the principle:Code:#define char_is_signed 1#define int_is_signed 1#define int8_t_is_signed 1#define uint8_t_is_signed 0#define int16_t_is_signed 1#define uint16_t_is_signed 0#define int32_t_is_signed 1#define uint32_t_is_signed 0#define IS_SIGNED(T) (T ## _is_signed)