I always knew I had signed values when I used int
or long
, char
, short
, etc. in C and C++ development. By the way, depending on the compiler or OS, what happens when the default signedness of these types becomes unsigned? I'm inquiring because I've never seen a reference to this in a paper or a book, and I don't know it.
-
3What is the "default value"? It is unclear what you are asking. – Vlad from Moscow Jun 23 '23 at 15:56
-
3@MarcoBonelli char is not a signed type. – Vlad from Moscow Jun 23 '23 at 15:58
-
3Integer types are signed by default, only the character types are unspecified https://en.cppreference.com/w/cpp/language/types – Alan Birtles Jun 23 '23 at 15:58
-
8`char` can be either `signed` or `unsigned` (implementation defined) and is a distinct type from `unsigned char` and `signed char`; see [Character types](https://en.cppreference.com/w/cpp/language/types#Character_types) – Richard Critten Jun 23 '23 at 16:00
-
3`char` can be unsigned on some platforms, but usually it is signed. Everything else is signed. – HolyBlackCat Jun 23 '23 at 16:00
-
`char` variables can be signed or unsigned: it's implementation dependent. (Microsoft's C compiler lets you control this with a compiler option). The original ANSI C required that character constants be non-negative, but I can't find any such restriction in the current standard. – Peter Raynham Jun 23 '23 at 16:19
-
@chux: Re “C offers no definition to create a negative constant”: Try `printf("%d\n", '\xff');` and `enum { foo = -37 }; printf("%d\n", foo);`. – Eric Postpischil Jun 23 '23 at 17:34
-
@EricPostpischil 2 fine counter examples. – chux - Reinstate Monica Jun 23 '23 at 18:16
-
Related: [Is char signed or unsigned by default?](https://stackoverflow.com/q/2054939/12149471) – Andreas Wenzel Jun 23 '23 at 19:09
1 Answers
Types short
, int
, long
and long long
are all signed unless explicitly qualified as unsigned
. Note that short
and long
are actually qualifiers themselves and the type int
is implicit if omitted.
Type char
is special: it is different from type signed char
, which obviously is signed and from type unsigned char
, which is not. Depending on the platform and compiler settings, type char
may be signed or unsigned. You can test this by comparing the value of the macro CHAR_MIN
defined in <limits.h>
to 0
or by casting -1
as (char)
and testing if it stays negative.
#include <limits.h>
#include <stdio.h>
int main(void) {
if ((char)-1 < 0) {
/* most common case */
printf("char is signed, range is %d..%d\n", CHAR_MIN, CHAR_MAX);
} else
if (sizeof(char) == sizeof(int)) {
/* corner case, for some DSP systems */
/* char type is the same as unsigned int */
printf("char is unsigned, range is %u..%u\n", CHAR_MIN, CHAR_MAX);
} else {
/* common case, enabled with -funsigned-char for gcc and clang */
/* char values and constants will promote to int */
printf("char is unsigned, range is %d..%d\n", CHAR_MIN, CHAR_MAX);
}
return 0;
}
Note that you cannot use the above tests for preprocessing, but the constants defined in <limits.h>
can be used in preprocessor expressions:
#include <limits.h>
#include <stdio.h>
int main(void) {
#if CHAR_MIN < 0
printf("char is signed, range is %d..%d\n", CHAR_MIN, CHAR_MAX);
#elif CHAR_MAX == UINT_MAX
printf("char is unsigned, range is %u..%u\n", CHAR_MIN, CHAR_MAX);
#else
printf("char is unsigned, range is %d..%d\n", CHAR_MIN, CHAR_MAX);
#endif
return 0;
}
This also works in C++, but there is a more idiomatic way to test it using std::is_signed_v<char>
.

- 131,814
- 10
- 121
- 189
-
-
-
-
@chqrlie Corner case: for those pesky `char` as wide as `int/unsigned`, `printf(", range is %d..%d\n", CHAR_MIN, CHAR_MAX);` needs `printf(", range is %u..%u\n", CHAR_MIN, CHAR_MAX);` when `char` is _unsigned_. – chux - Reinstate Monica Jun 23 '23 at 16:51
-
@chux-ReinstateMonica `printf(", range is %d..%u\n", (int)CHAR_MIN, (unsigned)CHAR_MAX);` will work. – Ian Abbott Jun 23 '23 at 17:39
-
-
@chux-ReinstateMonica: corner case tested and explained, without casts – chqrlie Jun 24 '23 at 09:55
-
-
@IanAbbott: interesting approach, but I prefer to use as few casts as possible. – chqrlie Jun 24 '23 at 09:56
-
I'd expect to print `CHAR_MIN` using `"%d"` is always OK. – chux - Reinstate Monica Jun 24 '23 at 13:07
-
At the risk of language lawyering, I'd also assert `printf("%u\n", CHAR_MAX);` is also always OK given "value is representable in both" C23dr § 7.16.1.1 2. – chux - Reinstate Monica Jun 24 '23 at 13:07
-
IOWs, I assert `printf("char is unsigned, range is %d..%u\n", CHAR_MIN, CHAR_MAX);` is always good. – chux - Reinstate Monica Jun 24 '23 at 13:08
-
1Warning: language-lawyer speaking. `CHAR_MIN` is representable as an `int`, but its type may be `unsigned int` in the pathological case where `char` is unsigned and `sizeof(unsigned) == 1`. Passing an `unsigned int` for `%d` is undefined behavior until C17 and defined if the value is representable in both types only as of C23. This change is rooted in common sense, but comes a bit late. The case of `%u` for `CHAR_MAX` is different yet even more compelling as the behavior is undefined under C17 and before for all other architectures. – chqrlie Jun 24 '23 at 16:08
-
The change in C23dr is only possible because it gets rid of non 2's complement representations of signed integer types. – Ian Abbott Jun 26 '23 at 09:15