简体   繁体   English

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

[英]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_ttime_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: 这种方法到目前为止有效,但它有一些缺点:

  • I have to know whether the type I'm trying print is signed or unsigned. 我必须知道我正在尝试打印的类型是签名还是未签名。
  • I have to use a type cast that possibly enlarges my code. 我必须使用可能会扩大我的代码的类型转换。

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 , or X conversion specifier applies to an intmax_t or uintmax_t argument; 指定以下diouxX转换说明符适用于intmax_tuintmax_t参数; or that a following n conversion specifier applies to a pointer to an intmax_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_tptrdiff_t (以及它们对应的有符号/无符号类型)的zt修饰符。

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并为所有值获得明确定义的结果,但打印-118446744073709551615 (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>printfj长度修饰符时才有效 - 即,如果它支持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,最宽的整数类型是longunsigned 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之前的编译器可能仍然支持宽度大于longunsigned 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_tintmax_t来轻松解决printf()调用中的任何歧义。

The issue of the signed or unsigned types can be solved in an easy way: 签名或无符号类型的问题可以通过简单的方式解决:

  • All unsigned integer operations works modulo "N" for some positive value N, depending of the type. 对于某些正值N,所有无符号整数运算都以模“N”为模,具体取决于类型。 It implies that every result involving only unsigned integer types gives a nonnegative value. 它意味着只涉及无符号整数类型的每个结果都给出一个非负值。
  • To detect if a variable 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: 第一次罚款:

  • As Pascal Cuoq pointed out in his comment, the integer promotions has to be taken in accont for unsigned char and short values fiiting in the range of int . 正如Pascal Cuoq在他的评论中所指出的那样,整数促销必须在accont中采用unsigned charshort值在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可以判断对象是否具有无符号整数类型。

  • If the value is negative, obviously the type is not unsigned . 如果值为负,显然类型不是unsigned
  • If the value N is positive then 如果值N是正的那么
    • if -N is positive, then N has unsigned type, 如果-N为正,则N具​​有unsigned类型,
    • If -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的类型可以是有signedunsigned ,但它适合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的技巧 ,但也可能有其他方式(这是“价格”通用)。

EDIT: 编辑:

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. 但是存在一个潜在的问题:如果存在与列出的类型不同的类型(即,除了有signedunsigned版本的charshortintlonglong 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_tintmax_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_tintmax_t在平台上工作正常,它们与列出的类型之一相同(例如, long相同)。 Similarly there is no issue if, eg, long and long long , or long and int , are the same type. 类似地,如果例如longlong long ,或longint是相同类型,则没有问题。 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_tuintmax_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支持的非整数类型扩展此宏会很简单,例如doublechar * ...)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM