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

时间:2014-07-19 21:07:12

标签: c printf portability

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

#include <inttypes.h>

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

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

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

有更好的策略吗?

4 个答案:

答案 0 :(得分:8)

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

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

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

j长度修饰符

  

指定以下 d i o { {1}} 下,    u x 转换说明符适用于 X   或 intmax_t 参数;或者以下 uintmax_t 转换   说明符适用于指向 n 参数的指针。

intmax_tz(及其对应的有符号/无符号类型)也分别有tsize_t修饰符。

就个人而言,我发现ptrdiff_t中定义的格式字符串宏难以记忆,这就是我更喜欢<inttypes.h>"%ju"的原因。

正如您所提到的,了解类型(在这种情况下为"%jd")是签名还是未签名是有帮助的。如果你不碰巧知道这一点,就有可能弄明白:

ino_t

虽然这可能有点矫枉过正。如果您确定类型的值小于64位,则可以将值转换为#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'); } ,并保留该值。或者您可以使用intmax_t并为所有值获得明确定义的结果,但将uintmax_t打印为-1(2 64 -1)可能会有点令人困惑

所有这些仅适用于您的C实现支持18446744073709551615<stdint.h>的{​​{1}}长度修饰符 - 即,如果它支持C99。并非所有编译器都这样做(咳嗽 Microsoft cough )。对于C90,最宽的整数类型是jprintf,您可以转换为那些并使用long和/或unsigned long。理论上,您可以使用"%ld"预定义宏来测试C99合规性 - 尽管某些C99之前的编译器可能仍然支持比"%lu"__STDC_VERSION__更宽的类型作为扩展。

答案 1 :(得分:6)

整数类型的“大小”与此无关,但与其值范围无关。

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

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

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

例如:

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

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

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

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

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

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

第一次申请:

  • 正如 Pascal Cuoq 在他的评论中指出的那样,对于未签名charshort值的整数促销必须在{{1 }}。这相当于询问值是否在rango 0到int中。

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

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

  • 如果值为负,显然类型不是fits_unsigned_type
  • 如果值N为正,则为
    • 如果unsigned为正数,则N具有-N类型,
    • 如果unsigned为负数,但N的范围为0到-N,那么INT_MAX的类型可以是Nsigned,但是它将适合unsigned的正值范围,其范围为int

第二次罚款:

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

答案 2 :(得分:4)

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

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

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

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

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

编辑:

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

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

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

答案 3 :(得分:3)

使用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"));
}

但是存在一个潜在的问题:如果存在与列出的类型不同的类型(即signedunsigned char以外的short版本,int,{ {1}},longlong long),这不适用于这些类型。也无法将size_tintmax_t等类型添加到类型通用宏“以防万一”中,因为如果它们 {{ 1}} d来自已列出的类型之一。我没有指定typedef情况,因此当没有找到匹配的类型时,宏会生成编译时错误。

但是,如示例程序中所示,defaultsize_t在与列出的类型之一相同的平台上工作正常(例如,与intmax_t相同) 。同样,如果longlong,或long longlong属于同一类型,则没有问题。但更安全的版本可能只是根据签名(如其他答案中所示)转换为intintmax_t,并使用这些选项制作类型通用宏...

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

uintmax_t

因此,对于打印单个变量的常见情况,包含前缀和后缀的(void) printf("var = " PRI(var) "\n", var); // does not work! 宏:

PRIFMT

(请注意,使用(void) printf(PRIFMT("var = ", var, "\n")); 支持的非整数类型扩展此宏很简单,例如printfdouble ...)