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:
#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! :wtf:
As a test, if I do this:
#if IS_SIGNED_TYPE(uint16_t)
#warning "Type is signed"
#else
#warning "Type is unsigned"
#endif
My 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?
Remember, macros are just that, macros that are expanded before compilation.
You're actually writing
#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-not
Note 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:
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
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:
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.
Why do you need to test the signed/unsigned type during compile time if YOU are writing the code?
Because 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.
You 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.
// Comment the next 2 lines if using unsigned
typedef int typex;
#define SIGNED 1
// Comment the next line if using signed
//typedef uint16_t typex;
#ifdef SIGNED
...
#else
...
#endif
Then you may want to add a hack to break compilation if the type is not marked as signed by mistake:
#ifndef SIGNED
int dummy[-IS_SIGNED(type)]
#endif
This 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.
As a bonus, a simple example of how you can use _Generic in C11:
int Abs(uint16_t n)
{
return _Generic(n,
uint16_t: n,
int16_t: (n < 0)? -n : n);
}
I don't like doing it but in the past where I've needed a pre-processing step I deal with it in the makefile:
In this case, you need a test program, that when compiled and run, emits the header definitions. I'd use something along the lines of
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 list include/signedness.h as 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 is char, I believe). If the recipe is listed without prerequisites, then providing a include/signedness.h file suitable for the target architecture suffices; make won't try to regenerate it.
⚠ Warning: that code hasn’t been tested in production and should be considered experimental
I 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:// 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)
#endif
Use:#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: <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 _Generic selector, 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:,-------. ,-------. ,-------. ,-------. ,-------.
| Type1 | ,->| Type2 | ,->| Type3 | ,->| Type4 | ,->| Type5 |
| fn | | | fn | | | fn | | | fn | | | fn |
| o-----' | o-----' | o-----' | o-----' | o----> abort
`-------' `-------' `-------' `-------' `-------'
If the type of value doesn’t match the given node, it uses the default association(3), effectively going deeper into the list. The final node is using abort simply because there must be the default association there.(4) That requirement arises from the fact, that even if any “earlier” _Generic selector matches, the “later” ones still need to be valid: if the final one would not have the default association, it would be valid only for a single type, which would break the whole code.
The outer _Generic is 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 like wchar_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 unused static inline functions. If that requirement is not met, it will waste space in the final executable. If the compiler can do that for static functions it is even better.
Alternatively, if the final “(value)” portion of the macro definition is removed, the signedFn and unsignedFn arguments 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 sentinel abort is 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_t signedness can’t be determined.
(3) 6.5.1.1§2
(4) 6.5.1.1§2–3
If you want a runtime check, then I recommend using
#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:
#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.
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 your Makefile (simplified, with build and source directories the same),
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 $@
with generate-signedness.c containing
#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 a DEFINE(type) line for each type the code might be interested in.
Then, in any of the other C source files,
#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 */
#endif
When cross-compiling such code, one can provide the signedness.h file as-is. When it exists, it will not be regenerated/replaced.
Now, looking at the actual code above, I'd probably instead keep pregenerated signedness.h files 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.
Someone posted a solution right here. But removed it!
It 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 like IS_SIGNED(unsigned int) /* Will not work */
However, here is the principle:
#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)