[英]How to print types of unknown size like ino_t?
I often experience situations where I want to print with printf
the value of an integer type of implementation-defined size (like ino_t
or time_t
). 我经常遇到这样的情况:我想用
printf
打印整数类型的实现定义大小的值(比如ino_t
或time_t
)。 Right now, I use a pattern like this for this: 现在,我使用这样的模式:
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
printf("%" PRIuMAX, (uintmax_t)ino);
This approach works so far but it has a couple of disadvantages: 这种方法到目前为止有效,但它有一些缺点:
Is there a better strategy? 有更好的策略吗?
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
/* ... */
printf("%" PRIuMAX, (uintmax_t)ino);
That will certainly work (with some provisos; see below), but I'd use: 这肯定会有效(有一些附带条件;见下文),但我会用:
printf("%ju", (uintmax_t)ino);
The j
length modifier j
长度修饰符
Specifies that a following
d
,i
,o
,u
,x
, orX
conversion specifier applies to anintmax_t
oruintmax_t
argument;指定以下
d
,i
,o
,u
,x
或X
转换说明符适用于intmax_t
或uintmax_t
参数; or that a followingn
conversion specifier applies to a pointer to anintmax_t
argument.或者后面的
n
转换说明符适用于指向intmax_t
参数的指针。
There are also z
and t
modifiers for size_t
and ptrdiff_t
(and their corresponding signed/unsigned types), respectively. 还有
size_t
和ptrdiff_t
(以及它们对应的有符号/无符号类型)的z
和t
修饰符。
And personally, I find the format string macros defined in <inttypes.h>
ugly and difficult to remember, which is why I prefer "%ju"
or "%jd"
. 个人而言,我发现
<inttypes.h>
定义的格式字符串宏难看并且难以记住,这就是为什么我更喜欢"%ju"
或"%jd"
。
As you mentioned, it's helpful to know whether the type ( ino_t
in this case) is signed or unsigned. 正如您所提到的,知道类型(在本例中为
ino_t
)是有符号还是无符号是有帮助的。 If you don't happen to know that, it's possible to figure it out: 如果你碰巧不知道这一点,就有可能搞清楚:
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#define IS_SIGNED(type) ((type)-1 < (type)0)
#define DECIMAL_FORMAT(type) (IS_SIGNED(type) ? "%jd" : "%ju")
#define CONVERT_TO_MAX(type, value) \
(IS_SIGNED(type) ? (intmax_t)(value) : (uintmax_t)(value))
#define PRINT_VALUE(type, value) \
(printf(DECIMAL_FORMAT(type), CONVERT_TO_MAX(type, (value))))
int main(void) {
ino_t ino = 42;
PRINT_VALUE(ino_t, ino);
putchar('\n');
}
though that may be overkill. 虽然这可能有点矫枉过正。 If you're sure the type is narrower than 64 bits, you can convert the value to
intmax_t
, and the value will be preserved. 如果您确定类型的值小于64位,则可以将该值转换为
intmax_t
,并保留该值。 Or you can use uintmax_t
and get well-defined results for all values, though printing -1
as 18446744073709551615
(2 64 -1) may be a bit confusing. 或者您可以使用
uintmax_t
并为所有值获得明确定义的结果,但打印-1
为18446744073709551615
(2 64 -1)可能会有点混乱。
All of this works only if your C implementation supports <stdint.h>
and the j
length modifier for printf
-- ie, if it supports C99. 所有这些只有在您的C实现支持
<stdint.h>
和printf
的j
长度修饰符时才有效 - 即,如果它支持C99。 Not all compilers do so ( cough Microsoft cough ). 并非所有编译器都这样做( 咳嗽微软咳嗽 )。 For C90, the widest integer types are
long
and unsigned long
, and you can convert to those and use "%ld"
and/or "%lu"
. 对于C90,最宽的整数类型是
long
和unsigned long
,您可以转换为那些并使用"%ld"
和/或"%lu"
。 You can theoretically test for C99 compliance using the __STDC_VERSION__
predefined macro -- though some pre-C99 compilers might still support types wider than long
and unsigned long
as an extension. 理论上,您可以使用
__STDC_VERSION__
预定义宏来测试C99合规性 - 尽管某些C99之前的编译器可能仍然支持宽度大于long
且unsigned long
类型作为扩展。
The "size" of an integer type is not relevant here, but its range of values. 整数类型的“大小”在这里不相关,但是它的值范围。
As apparently you tried, yet, it is possible to cast to uintmax_t
and intmax_t
to easily solve any ambiguity in the printf()
call. 显然你已经尝试过了,可以转换为
uintmax_t
和intmax_t
来轻松解决printf()
调用中的任何歧义。
The issue of the signed or unsigned types can be solved in an easy way: 签名或无符号类型的问题可以通过简单的方式解决:
x
has a signed or an unsigned type, it is enough to verify if x
and -x
are both nonnegative values. x
是否具有有符号或无符号类型,只需验证x
和-x
是否都是非负值。 For example: 例如:
if ( (x>=0) && (-x>=0) )
printf("x has unsigned type");
else
printf("x has signed type");
Now, we can write some macros: 现在,我们可以编写一些宏:
(Edited: the name and expression of the macro have changed) (已编辑:宏的名称和表达已更改)
#include <inttypes.h>
#include <limits.h>
#define fits_unsigned_type(N) ( (N >= 0) && ( (-(N) >= 0) || ((N) <= INT_MAX) ) )
#define smartinteger_printf(N) \
(fits_unsigned_type(N)? printf("%ju",(uintmax_t)(N)): printf("%jd",(intmax_t) (N)) )
// ....
ino_t x = -3;
printf("The value is: ");
smartinteger_printf(x);
//.....
Note: The signed or unsigned character of a variable is not well detected by the macro above when the value is 0. But in this case everything works well, because 0 has the same bit representation in signed or unsigned types. 注意:当值为0时,上面的宏没有很好地检测到变量的有符号或无符号字符。但是在这种情况下一切都运行良好,因为0在有符号或无符号类型中具有相同的位表示。
The first macro can be used to detect if the underlying type of an arithmetical object has unsgined type or not. 第一个宏可用于检测算术对象的基础类型是否具有未编号类型。
This result is used in the second macro to choose the way in that the object is printed on screen. 此结果在第二个宏中用于选择在屏幕上打印对象的方式。
1st REEDITION: 第一次罚款:
char
and short
values fiiting in the range of int
. char
和short
值在int
范围内进行。 This is equivalent to ask if the value is in the rango 0 to INT_MAX
. INT_MAX
0到INT_MAX
。 So I have changed the name of the macro to fits_signed_type
. 所以我已经将宏的名称更改为
fits_signed_type
。
Also, I have modified the macro to take in account the positive int
values. 此外,我已修改宏以考虑正
int
值。
The macro fits_unsigned_type
can tell if an object has unsigned integer type or not in most cases. 在大多数情况下,macro
fits_unsigned_type
可以判断对象是否具有无符号整数类型。
unsigned
. unsigned
。 -N
is positive, then N has unsigned
type, -N
为正,则N具有unsigned
类型, -N
is negative, but N is in the range 0 to INT_MAX
, then the type of N
could be signed
or unsigned
, but it would fit in the range of positive values of int
, which fits in the range of uintmax_t
. -N
为负,但N在0到INT_MAX
的范围内,则N
的类型可以是有signed
或unsigned
,但它适合int
的正值范围,它适合uintmax_t
的范围。 2nd REEDITION: 第二次罚款:
Ir seems that there are here to approaches to solve the same problem. Ir似乎有解决同样问题的方法。 My approach takes in account the range of values and integer promotion rules to produce the correct printed value with
printf()
. 我的方法考虑了值的范围和整数提升规则,以使用
printf()
生成正确的打印值。 On the other hand, Grzegorz Szpetkowski's approach determines the signed character of a type in straight form. 另一方面,Grzegorz Szpetkowski的方法确定了直线形式的有符号字符 。 I like both.
我喜欢这两个。
Since your are already using C99 header, there is a possibility to use exact width format specifier depending on sizeof(T)
and signed/unsigned check. 由于您已经在使用C99标头,因此可以根据
sizeof(T)
和signed / unsigned检查使用精确宽度格式说明符。 This however has to done after preprocessing phase (so sadly ##
operator cannot be used here to construct PRI token). 然而,这必须在预处理阶段之后完成(很遗憾,
##
operator不能用于构造PRI令牌)。 Here is an idea: 这是一个想法:
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0) /* determines if integer type is signed */
...
const char *fs = NULL;
size_t bytes = sizeof(T);
if (IS_SIGNED(T))
switch (bytes) {
case 1: fs = PRId8; break;
case 2: fs = PRId16; break;
case 4: fs = PRId32; break;
case 8: fs = PRId64; break;
}
else
switch (bytes) {
case 1: fs = PRIu8; break;
case 2: fs = PRIu16; break;
case 4: fs = PRIu32; break;
case 8: fs = PRIu64; break;
}
With this method cast is not needed anymore, however format string has to be manually constructed before passing it to printf
(ie no automatic string concatenation). 使用此方法不再需要强制转换,但是在将其传递给
printf
之前必须手动构造格式字符串(即没有自动字符串连接)。 Here is some working example: 这是一些工作示例:
#include <stdio.h>
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0)
/* using GCC extension: Statement Expr */
#define FMT_CREATE(T) ({ \
const char *fs = NULL; \
size_t bytes = sizeof(ino_t); \
\
if (IS_SIGNED(T)) \
switch (bytes) { \
case 1: fs = "%" PRId8; break; \
case 2: fs = "%" PRId16; break; \
case 4: fs = "%" PRId32; break; \
case 8: fs = "%" PRId64; break; \
} \
else \
switch (bytes) { \
case 1: fs = "%" PRIu8; break; \
case 2: fs = "%" PRIu16; break; \
case 4: fs = "%" PRIu32; break; \
case 8: fs = "%" PRIu64; break; \
} \
fs; \
})
int main(void) {
ino_t ino = 32;
printf(FMT_CREATE(ino_t), ino); putchar('\n');
return 0;
}
Note this requires some little trickery of Statement Expr , but there might be some other way (this is the "price" to be generic) as well. 注意这需要一些Statement Expr的小技巧 ,但也可能有其他方式(这是“价格”通用)。
Here is second version, that doesn't require specific compiler extension (don't worry I can't read it too) using function-like macro: 这是第二个版本,使用类似函数的宏不需要特定的编译器扩展(不用担心我也读不出来):
#include <stdio.h>
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0)
#define S(T) (sizeof(T))
#define FMT_CREATE(T) \
(IS_SIGNED(T) \
? (S(T)==1?"%"PRId8:S(T)==2?"%"PRId16:S(T)==4?"%"PRId32:"%"PRId64) \
: (S(T)==1?"%"PRIu8:S(T)==2?"%"PRIu16:S(T)==4?"%"PRIu32:"%"PRIu64))
int main(void)
{
ino_t ino = 32;
printf(FMT_CREATE(ino_t), ino);
putchar('\n');
return 0;
}
Note that conditional operator has left assiociativity (thus it evalutes from left to right as intended). 请注意,条件运算符已经保持了共生性(因此它按预期从左到右进行评估)。
Using C11 type generic macros, it is possible to construct the format string at compile time, eg: 使用C11类型的通用宏,可以在编译时构造格式字符串,例如:
#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#define PRI3(B,X,A) _Generic((X), \
unsigned char: B"%hhu"A, \
unsigned short: B"%hu"A, \
unsigned int: B"%u"A, \
unsigned long: B"%lu"A, \
unsigned long long: B"%llu"A, \
signed char: B"%hhd"A, \
short: B"%hd"A, \
int: B"%d"A, \
long: B"%ld"A, \
long long: B"%lld"A)
#define PRI(X) PRI3("",(X),"")
#define PRIFMT(B,X,A) PRI3(B,(X),A),(X)
int main () {
signed char sc = SCHAR_MIN;
unsigned char uc = UCHAR_MAX;
short ss = SHRT_MIN;
unsigned short us = USHRT_MAX;
int si = INT_MIN;
unsigned ui = UINT_MAX;
long sl = LONG_MIN;
unsigned long ul = ULONG_MAX;
long long sll = LLONG_MIN;
unsigned long long ull = ULLONG_MAX;
size_t z = SIZE_MAX;
intmax_t sj = INTMAX_MIN;
uintmax_t uj = UINTMAX_MAX;
(void) printf(PRIFMT("signed char : ", sc, "\n"));
(void) printf(PRIFMT("unsigned char : ", uc, "\n"));
(void) printf(PRIFMT("short : ", ss, "\n"));
(void) printf(PRIFMT("unsigned short : ", us, "\n"));
(void) printf(PRIFMT("int : ", si, "\n"));
(void) printf(PRIFMT("unsigned int : ", ui, "\n"));
(void) printf(PRIFMT("long : ", sl, "\n"));
(void) printf(PRIFMT("unsigned long : ", ul, "\n"));
(void) printf(PRIFMT("long long : ", sll, "\n"));
(void) printf(PRIFMT("unsigned long long: ", ull, "\n"));
(void) printf(PRIFMT("size_t : ", z, "\n"));
(void) printf(PRIFMT("intmax_t : ", sj, "\n"));
(void) printf(PRIFMT("uintmax_t : ", uj, "\n"));
}
There is a potential problem, though: if there are types distinct from those listed (ie, other than signed
and unsigned
versions of char
, short
, int
, long
, and long long
), this does not work for those types. 但是存在一个潜在的问题:如果存在与列出的类型不同的类型(即,除了有
signed
和unsigned
版本的char
, short
, int
, long
和long long
),这对这些类型不起作用。 It is also not possible to add types such as size_t
and intmax_t
to the type generic macro "just in case", because it will cause an error if they are typedef
d from one of the already listed types. 也不可能将
size_t
和intmax_t
等类型添加到类型通用宏“以防万一”,因为如果它们是已经列出的类型之一的typedef
,则会导致错误。 I've left the default
case unspecified so that the macro generates a compile-time error when no matching type is found. 我没有指定
default
情况,以便在没有找到匹配类型时宏生成编译时错误。
However, as seen in the example program, size_t
and intmax_t
work just fine on platforms where they are the same as one of the listed types (eg, same as long
). 但是,如示例程序中所示,
size_t
和intmax_t
在平台上工作正常,它们与列出的类型之一相同(例如, long
相同)。 Similarly there is no issue if, eg, long
and long long
, or long
and int
, are the same type. 类似地,如果例如
long
和long long
,或long
和int
是相同类型,则没有问题。 But a safer version might be to just cast to intmax_t
or uintmax_t
according to signedness (as seen in other answers), and make a type generic macro with only those options… 但是更安全的版本可能只是根据签名转换为
intmax_t
或uintmax_t
(如其他答案中所示),并且仅使用那些选项创建类型通用宏...
A cosmetic problem is that the type generic macro expands with parentheses around the string literal, preventing concatenation with adjacent string literals in the usual way. 一个美观的问题是类型泛型宏在字符串文字周围用括号扩展,防止以通常的方式连接相邻的字符串文字。 This prevents things like:
这可以防止以下情况:
(void) printf("var = " PRI(var) "\n", var); // does not work!
Hence the PRIFMT
macro with prefix and suffix included for the common case of printing a single variable: 因此,
PRIFMT
宏包含前缀和后缀,用于打印单个变量的常见情况:
(void) printf(PRIFMT("var = ", var, "\n"));
(Note that it would be simple to expand this macro with the non-integer types supported by printf
, eg, double
, char *
…) (请注意,使用
printf
支持的非整数类型扩展此宏会很简单,例如double
, char *
...)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.