Skip to content

Commit 67e7bbe

Browse files
committed
Fixes mpaland#51: Now supporting MSVC-style integer specifiers: %I8, %I16, %I32, %I64.
1 parent 6ce7a11 commit 67e7bbe

5 files changed

Lines changed: 214 additions & 40 deletions

File tree

CMakeLists.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ option(BUILD_STATIC_LIBRARY "Build the library as static rather than shared" OFF
1515

1616
# Boolean options which go into config.h
1717

18-
option(SUPPORT_DECIMAL_SPECIFIERS "Support decimal notation floating-point conversion specifiers (%f,%F)" ON)
19-
option(SUPPORT_EXPONENTIAL_SPECIFIERS "Support exponential floating point format conversion specifiers (%e,%E,%g,%G)" ON)
20-
option(SUPPORT_WRITEBACK_SPECIFIER "Support the length write-back specifier (%n)" ON)
21-
option(SUPPORT_LONG_LONG "Support long long integral types (allows for the ll length modifier and affects %p)" ON)
22-
option(ALIAS_STANDARD_FUNCTION_NAMES "Alias the standard library function names (printf, sprintf etc.) to the library's functions" ON)
18+
option(SUPPORT_DECIMAL_SPECIFIERS "Support decimal notation floating-point conversion specifiers (%f,%F)" ON)
19+
option(SUPPORT_EXPONENTIAL_SPECIFIERS "Support exponential floating point format conversion specifiers (%e,%E,%g,%G)" ON)
20+
option(SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS "Support the I + bit size integer specifiers (%I8, %I16, %I32, %I64) as in Microsoft Visual C++" ON)
21+
option(SUPPORT_WRITEBACK_SPECIFIER "Support the length write-back specifier (%n)" ON)
22+
option(SUPPORT_LONG_LONG "Support long long integral types (allows for the ll length modifier and affects %p)" ON)
23+
option(ALIAS_STANDARD_FUNCTION_NAMES "Alias the standard library function names (printf, sprintf etc.) to the library's functions" ON)
2324

2425
foreach(opt
2526
SUPPORT_DECIMAL_SPECIFIERS
2627
SUPPORT_EXPONENTIAL_SPECIFIERS
28+
SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
2729
SUPPORT_WRITEBACK_SPECIFIER
2830
SUPPORT_LONG_LONG
2931
ALIAS_STANDARD_FUNCTION_NAMES)

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ Options used both in CMake and in the library source code via a preprocessor def
8383
| PRINTF_DECIMAL_BUFFER_SIZE | 32 | ftoa (float) conversion buffer size. This must be big enough to hold one converted float number _including_ leading zeros, normally 32 is a sufficient value. Created on the stack. |
8484
| PRINTF_DEFAULT_FLOAT_PRECISION | 6 | Define the default floating point precision|
8585
| PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL | 9 | Maximum number of integral-part digits of a floating-point value for which printing with %f uses decimal (non-exponential) notation |
86-
| PRINTF_SUPPORT_DECIMAL_SPECIFIERS | YES | Support decimal notation floating-point conversion specifiers (%f,%F) |
87-
| PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS | YES | Support exponential floating point format conversion specifiers (%e,%E,%g,%G) |
86+
| PRINTF_SUPPORT_DECIMAL_SPECIFIERS | YES | Support decimal notation floating-point conversion specifiers (%f, %F) |
87+
| PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS | YES | Support exponential floating point format conversion specifiers (%e, %E, %g, %G) |
88+
| SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS | YES | Support the 'I' + bit size integer specifiers (%I8, %I16, %I32, %I64) as in Microsoft Visual C++ |
8889
| PRINTF_SUPPORT_WRITEBACK_SPECIFIER | YES | Support the length write-back specifier (%n) |
8990
| PRINTF_SUPPORT_LONG_LONG | YES | Support long long integral types (allows for the ll length modifier and affects %p) |
9091

@@ -175,6 +176,7 @@ Notes:
175176
* The `%a` specifier for hexadecimal floating-point notation (introduced in C99 and C++11) is _not_ currently supported.
176177
* If you want to print the percent sign (`%`, US-ASCII character 37), use "%%" in your format string.
177178
* The C standard library's `printf()`-style functions don't accept `float` arguments, only `double`'s; that is true for this library as well. `float`'s get converted to `double`'s.
179+
* There is no unsigned equivalent of the `I` specifier at the moment.
178180

179181
#### Flags
180182

@@ -207,22 +209,28 @@ Notes:
207209

208210
The length sub-specifier modifies the length of the data type.
209211

210-
| Length | With `d`, `i` | With `u`,`o`,`x`, `X` | Support enabled by... |
211-
|--------|---------------------------|------------------------|---------------------------------|
212-
| (none) | int | unsigned int | |
213-
| hh | signed char | unsigned char | |
214-
| h | short int | unsigned short int | |
215-
| l | long int | unsigned long int | |
216-
| ll | long long int | unsigned long long int | PRINTF_SUPPORT_LONG_LONG |
217-
| j | intmax_t | uintmax_t | |
218-
| z | signed version of size_t | size_t | |
219-
| t | ptrdiff_t | ptrdiff_t | |
212+
| Length | With `d`, `i` | With `u`,`o`,`x`, `X` | Support enabled by... |
213+
|--------|---------------------------|------------------------|---------------------------------------|
214+
| (none) | int | unsigned int | |
215+
| hh | signed char | unsigned char | |
216+
| h | short int | unsigned short int | |
217+
| l | long int | unsigned long int | |
218+
| ll | long long int | unsigned long long int | PRINTF_SUPPORT_LONG_LONG |
219+
| j | intmax_t | uintmax_t | |
220+
| z | signed version of size_t | size_t | |
221+
| t | ptrdiff_t | ptrdiff_t | |
222+
| I8 | int8_t | uint8_t | SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS |
223+
| I16 | int16_t | uint16_t | SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS |
224+
| I32 | int32_t | uint32_t | SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS |
225+
| I64 | int64_t | uint64_t | SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS |
226+
220227

221228
Notes:
222229

223230
* The `L` modifier, for `long double`, is not currently supported.
224231
* A `"%zd"` or `"%zi"` takes a signed integer of the same size as `size_t`.
225232
* The implementation currently assumes each of `intmax_t`, signed `size_t`, and `ptrdiff_t` has the same size as `long int` or as `long long int`. If this is not the case for your platform, please open an issue.
233+
* The `Ixx` length modifiers are not in the C (nor C++) standard, but are somewhat popular, as it makes it easier to handle integer types of specific size. One must specify the argument size in bits immediately after the `I`. The printing is "integer-promotion-safe", i.e. the fact that an `int8_t` may actually be passed in promoted into a larger `int` will not prevent it from being printed using its origina value.
226234

227235
### Return Value
228236

printf_config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS @PRINTF_SUPPORT_DECIMAL_SPECIFIERS@
66
#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS @PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS@
77
#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER @PRINTF_SUPPORT_WRITEBACK_SPECIFIER@
8+
#define PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS @PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS@
89
#define PRINTF_SUPPORT_LONG_LONG @PRINTF_SUPPORT_LONG_LONG@
910
#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES @PRINTF_ALIAS_STANDARD_FUNCTION_NAMES@
1011

src/printf/printf.c

Lines changed: 121 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,16 @@
3838

3939
#ifdef __cplusplus
4040
#include <cstdint>
41+
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
42+
#include <climits>
43+
#endif
4144
extern "C" {
4245
#else
4346
#include <stdbool.h>
4447
#include <stdint.h>
48+
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
49+
#include <limits.h>
50+
#endif
4551
#endif // __cplusplus
4652

4753
// Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
@@ -54,6 +60,13 @@ extern "C" {
5460
#include "printf_config.h"
5561
#endif
5662

63+
#include <stdbool.h>
64+
#include <stdint.h>
65+
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
66+
#include <limits.h>
67+
#endif
68+
69+
5770
#include <printf/printf.h>
5871

5972
#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES
@@ -131,12 +144,61 @@ extern "C" {
131144
#define FLAGS_UPPERCASE (1U << 5U)
132145
#define FLAGS_CHAR (1U << 6U)
133146
#define FLAGS_SHORT (1U << 7U)
134-
#define FLAGS_LONG (1U << 8U)
135-
#define FLAGS_LONG_LONG (1U << 9U)
136-
#define FLAGS_PRECISION (1U << 10U)
137-
#define FLAGS_ADAPT_EXP (1U << 11U)
138-
#define FLAGS_POINTER (1U << 12U)
139-
// Note: Similar, but not identical, effect as FLAGS_HASH
147+
#define FLAGS_INT (1U << 8U)
148+
// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
149+
#define FLAGS_LONG (1U << 9U)
150+
#define FLAGS_LONG_LONG (1U << 10U)
151+
#define FLAGS_PRECISION (1U << 11U)
152+
#define FLAGS_ADAPT_EXP (1U << 12U)
153+
#define FLAGS_POINTER (1U << 13U)
154+
// Note: Similar, but not identical, effect as FLAGS_HASH
155+
#define FLAGS_SIGNED (1U << 14U)
156+
// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
157+
158+
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
159+
160+
#define FLAGS_INT8 FLAGS_CHAR
161+
162+
163+
#if (SHRT_MAX == 32767LL)
164+
#define FLAGS_INT16 FLAGS_SHORT
165+
#elif (INT_MAX == 32767LL)
166+
#define FLAGS_INT16 FLAGS_INT
167+
#elif (LONG_MAX == 32767LL)
168+
#define FLAGS_INT16 FLAGS_LONG
169+
#elif (LLONG_MAX == 32767LL)
170+
#define FLAGS_INT16 FLAGS_LONG_LONG
171+
#else
172+
#error "No basic integer type has a size of 16 bits exactly"
173+
#endif
174+
175+
#if (SHRT_MAX == 2147483647LL)
176+
#define FLAGS_INT32 FLAGS_SHORT
177+
#elif (INT_MAX == 2147483647LL)
178+
#define FLAGS_INT32 FLAGS_INT
179+
#elif (LONG_MAX == 2147483647LL)
180+
#define FLAGS_INT32 FLAGS_LONG
181+
#elif (LLONG_MAX == 2147483647LL)
182+
#define FLAGS_INT32 FLAGS_LONG_LONG
183+
#else
184+
#error "No basic integer type has a size of 32 bits exactly"
185+
#endif
186+
187+
#if (SHRT_MAX == 9223372036854775807LL)
188+
#define FLAGS_INT64 FLAGS_SHORT
189+
#elif (INT_MAX == 9223372036854775807LL)
190+
#define FLAGS_INT64 FLAGS_INT
191+
#elif (LONG_MAX == 9223372036854775807LL)
192+
#define FLAGS_INT64 FLAGS_LONG
193+
#elif (LLONG_MAX == 9223372036854775807LL)
194+
#define FLAGS_INT64 FLAGS_LONG_LONG
195+
#else
196+
#error "No basic integer type has a size of 64 bits exactly"
197+
#endif
198+
199+
#endif // PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
200+
201+
140202
typedef unsigned int printf_flags_t;
141203

142204
#define BASE_BINARY 2
@@ -829,6 +891,7 @@ static size_t print_floating_point(out_fct_type out, char* buffer, size_t idx, s
829891

830892
#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)
831893

894+
832895
// internal vsnprintf
833896
static int _vsnprintf(out_fct_type out, char* buffer, printf_size_t maxlen, const char* format, va_list va)
834897
{
@@ -902,6 +965,31 @@ static int _vsnprintf(out_fct_type out, char* buffer, printf_size_t maxlen, cons
902965

903966
// evaluate length field
904967
switch (*format) {
968+
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
969+
case 'I' : {
970+
format++;
971+
// Greedily parse for size in bits: 8, 16, 32 or 64
972+
switch(*format) {
973+
case '8': flags |= FLAGS_INT8;
974+
format++;
975+
break;
976+
case '1':
977+
format++;
978+
if (*format == '6') { format++; flags |= FLAGS_INT16; }
979+
break;
980+
case '3':
981+
format++;
982+
if (*format == '2') { format++; flags |= FLAGS_INT32; }
983+
break;
984+
case '6':
985+
format++;
986+
if (*format == '4') { format++; flags |= FLAGS_INT64; }
987+
break;
988+
default: break;
989+
}
990+
break;
991+
}
992+
#endif
905993
case 'l' :
906994
flags |= FLAGS_LONG;
907995
format++;
@@ -943,7 +1031,11 @@ static int _vsnprintf(out_fct_type out, char* buffer, printf_size_t maxlen, cons
9431031
case 'X' :
9441032
case 'o' :
9451033
case 'b' : {
946-
// set the base
1034+
1035+
if (*format == 'd' || *format == 'i') {
1036+
flags |= FLAGS_SIGNED;
1037+
}
1038+
9471039
numeric_base_t base;
9481040
if (*format == 'x' || *format == 'X') {
9491041
base = BASE_HEX;
@@ -956,26 +1048,22 @@ static int _vsnprintf(out_fct_type out, char* buffer, printf_size_t maxlen, cons
9561048
}
9571049
else {
9581050
base = BASE_DECIMAL;
959-
flags &= ~FLAGS_HASH; // no hash for dec format
1051+
flags &= ~FLAGS_HASH; // decimal integers have no alternative presentation
9601052
}
961-
// uppercase
1053+
9621054
if (*format == 'X') {
9631055
flags |= FLAGS_UPPERCASE;
9641056
}
9651057

966-
// no plus or space flag for u, x, X, o, b
967-
if ((*format != 'i') && (*format != 'd')) {
968-
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
969-
}
970-
1058+
format++;
9711059
// ignore '0' flag when precision is given
9721060
if (flags & FLAGS_PRECISION) {
9731061
flags &= ~FLAGS_ZEROPAD;
9741062
}
9751063

976-
// convert the integer
977-
if ((*format == 'i') || (*format == 'd')) {
978-
// signed
1064+
if (flags & FLAGS_SIGNED) {
1065+
// A signed specifier: d, i or possibly I + bit size if enabled
1066+
9791067
if (flags & FLAGS_LONG_LONG) {
9801068
#if PRINTF_SUPPORT_LONG_LONG
9811069
const long long value = va_arg(va, long long);
@@ -987,12 +1075,22 @@ static int _vsnprintf(out_fct_type out, char* buffer, printf_size_t maxlen, cons
9871075
idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
9881076
}
9891077
else {
990-
const int value = (flags & FLAGS_CHAR) ? (signed char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
1078+
// We never try to interpret the argument as something potentially-smaller than int,
1079+
// due to integer promotion rules: Even if the user passed a short int, short unsigned
1080+
// etc. - these will come in after promotion, as int's (or unsigned for the case of
1081+
// short unsigned when it has the same size as int)
1082+
const int value =
1083+
(flags & FLAGS_CHAR) ? (signed char) va_arg(va, int) :
1084+
(flags & FLAGS_SHORT) ? (short int) va_arg(va, int) :
1085+
va_arg(va, int);
9911086
idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
9921087
}
9931088
}
9941089
else {
995-
// unsigned
1090+
// An unsigned specifier: u, x, X, o, b
1091+
1092+
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
1093+
9961094
if (flags & FLAGS_LONG_LONG) {
9971095
#if PRINTF_SUPPORT_LONG_LONG
9981096
idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) va_arg(va, unsigned long long), false, base, precision, width, flags);
@@ -1002,11 +1100,13 @@ static int _vsnprintf(out_fct_type out, char* buffer, printf_size_t maxlen, cons
10021100
idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) va_arg(va, unsigned long), false, base, precision, width, flags);
10031101
}
10041102
else {
1005-
const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
1103+
const unsigned int value =
1104+
(flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) :
1105+
(flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) :
1106+
va_arg(va, unsigned int);
10061107
idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) value, false, base, precision, width, flags);
10071108
}
10081109
}
1009-
format++;
10101110
break;
10111111
}
10121112
#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS

0 commit comments

Comments
 (0)