如何打印像ino_t这样的未知大小的类型?

我经常遇到这样的情况:我想用printf打印整数类型的实现定义大小的值(比如ino_ttime_t )。 现在,我使用这样的模式:

 #include  ino_t ino; /* variable of unknown size */ printf("%" PRIuMAX, (uintmax_t)ino); 

这种方法到目前为止有效,但它有一些缺点:

  • 我必须知道我正在尝试打印的类型是签名还是未签名。
  • 我必须使用可能会扩大我的代码的类型转换。

有更好的策略吗?

 #include  ino_t ino; /* variable of unknown size */ /* ... */ printf("%" PRIuMAX, (uintmax_t)ino); 

这肯定会有效(有一些附带条件;见下文),但我会使用:

 printf("%ju", (uintmax_t)ino); 

j长度修饰符

指定以下diouxX转换说明符适用于intmax_tuintmax_t参数; 或者后面的n转换说明符适用于指向intmax_t参数的指针。

还有size_tptrdiff_t (以及它们对应的有符号/无符号类型)的zt修饰符。

个人而言,我发现定义的格式字符串宏难看且难以记住,这就是为什么我更喜欢"%ju""%jd"

正如您所提到的,知道类型(在本例中为ino_t )是有符号还是无符号是有帮助的。 如果你碰巧不知道这一点,就有可能搞清楚:

 #include  #include  #include  #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'); } 

虽然这可能是矫枉过正的。 如果您确定类型的值小于64位,则可以将值转换为intmax_t ,并保留该值。 或者您可以使用uintmax_t并为所有值获得明确定义的结果,但打印-118446744073709551615 (2 64 -1)可能会有点混乱。

所有这些只有在C实现支持printfj长度修饰符时才有效 - 即,如果它支持C99。 并非所有编译器都这样做( 咳嗽微软咳嗽 )。 对于C90,最宽的整数类型是longunsigned long ,您可以转换为那些并使用"%ld"和/或"%lu" 。 理论上,您可以使用__STDC_VERSION__预定义宏来测试C99合规性 - 尽管一些C99之前的编译器可能仍然支持宽度大于longunsigned long类型作为扩展。

整数类型的“大小”在这里不相关,但是它的值范围。

显然你已经尝试过了,可以转换为uintmax_tintmax_t来轻松解决printf()调用中的任何歧义。

签名或未签名类型的问题可以通过简单的方式解决:

  • 对于某些正值N,所有无符号整数运算都以模“N”为模,具体取决于类型。 这意味着只涉及无符号整数类型的每个结果都会给出一个非负值。
  • 要检测变量x是否具有有符号或无符号类型,只需validationx-x是否都是非负值。

例如:

  if ( (x>=0) && (-x>=0) ) printf("x has unsigned type"); else printf("x has signed type"); 

现在,我们可以编写一些宏:

(已编辑:宏的名称和表达已更改)

  #include  #include  #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); //..... 

注意:当值为0时,上面的宏没有很好地检测到变量的有符号或无符号字符。但是在这种情况下一切都运行良好,因为0在有符号或无符号类型中具有相同的位表示。

第一个宏可用于检测算术对象的基础类型是否具有未编号类型。
此结果在第二个宏中用于选择对象在屏幕上打印的方式。

第一次罚款:

  • 正如Pascal Cuoq在他的评论中指出的那样,整数促销必须在accont中用于unsigned charshort值,在int的范围内。 这相当于询问值是否在INT_MAX 0到INT_MAX

所以我将宏的名称更改为fits_signed_type
此外,我已修改宏以考虑正int值。

在大多数情况下,macro fits_unsigned_type可以判断对象是否具有无符号整数类型。

  • 如果值为负,显然类型不是unsigned
  • 如果值N是正的那么
    • 如果-N为正,则N具​​有unsigned类型,
    • 如果-N为负,但N在0到INT_MAX的范围内,则N的类型可以是有signedunsigned ,但它适合int的正值范围,它适合uintmax_t的范围。

第二次罚款:

Ir似乎有解决同样问题的方法。 我的方法考虑了值范围和整数提升规则,以使用printf()生成正确的打印值。 另一方面,Grzegorz Szpetkowski的方法确定了直线forms的有符号字符 。 我喜欢这两个。

由于您已经在使用C99标头,因此可以根据sizeof(T)和signed / unsigned检查使用精确宽度格式说明符。 然而,这必须在预处理阶段之后完成(很遗憾, ##运算符不能在这里用于构造PRI令牌)。 这是一个想法:

 #include  #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; } 

使用此方法不再需要强制转换,但是在将其传递给printf之前必须手动构造格式字符串(即没有自动字符串连接)。 这是一些工作示例:

 #include  #include  #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; } 

请注意,这需要一些Statement Expr的小技巧 ,但也可能有其他方式(这是“价格”通用)。

编辑:

这是第二个版本,它不需要特定的编译器扩展(不要担心我也不能读它)使用类似函数的宏:

 #include  #include  #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; } 

请注意,条件运算符已经保持了共生性(因此它按预期从左到右进行评估)。

使用C11类型的通用宏,可以在编译时构造格式字符串,例如:

 #include  #include  #include  #include  #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")); } 

但是存在一个潜在的问题:如果存在与列出的类型不同的类型(即,除了有signedunsigned版本的charshortintlonglong long ),这对这些类型不起作用。 也不可能将size_tintmax_t等类型添加到类型通用宏“以防万一”中,因为如果它们已经列出的类型之一的typedef ,则会导致错误。 我没有指定default情况,以便在没有找到匹配类型时宏生成编译时错误。

但是,如示例程序中所示, size_tintmax_t在平台上工作得很好,在这些平台上,它们与列出的类型之一相同(例如, long相同)。 类似地,如果例如longlong long ,或longint是相同类型,则没有问题。 但是更安全的版本可能只是根据签名转换为intmax_tuintmax_t (如其他答案所示),并且只使用那些选项制作类型通用宏…

一个美观的问题是类型generics宏在字符串文字周围用括号扩展,防止以通常的方式连接相邻的字符串文字。 这可以防止以下情况:

 (void) printf("var = " PRI(var) "\n", var); // does not work! 

因此PRIFMT宏包含前缀和后缀,用于打印单个变量的常见情况:

 (void) printf(PRIFMT("var = ", var, "\n")); 

(请注意,使用printf支持的非整数类型扩展此宏会很简单,例如doublechar * …)