Как печатать типы неизвестного размера, такие как ino t?

Я часто испытываю ситуации, когда я хочу печатать с printf значение целочисленного типа размера, определенного реализацией (например,ino_t или time_t). Прямо сейчас я использую такой шаблон для этого:

#include <inttypes.h>

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

этот подход работает до сих пор, но он имеет несколько недостатков:

  • Я должен знать, является ли тип я пытаюсь печати signed или unsigned.
  • Я должен использовать тип cast, который, возможно, увеличивает мой код.

есть ли лучшая стратегия?

4 ответов


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

это, безусловно, сработает (с некоторыми оговорками; см. ниже), но я бы использовал:

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

на j длина модификатор

указывает, что после d, i, o, u, x или X спецификатор преобразования применяется к intmax_t или uintmax_t аргумент; или что a после n преобразование спецификатор применяется к указателю на intmax_t


"размер" целочисленного типа здесь не имеет значения, но его диапазон значений.

как, видимо, вы пытались, все же, можно бросить в uintmax_t и intmax_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 имеет одно и то же битовое представление в знаковых или неподписанных типах.

первый макрос может использоваться для определения, имеет ли базовый тип арифметического объекта незаполненный тип или нет.
Этот результат используется во втором макросе для выбора способа печати объекта на экране.

1-й REEDITION:

  • As Паскаль Cuoq указал в своем комментарии, целочисленные акции должны быть приняты в accont для unsigned char и short значения fiiting в диапазоне int. Это равносильно спросить, если значение в Ранго 0 до INT_MAX.

поэтому я изменил имя макроса на fits_signed_type.
Кроме того, я изменил макрос, чтобы учесть положительный int значения.

макрос fits_unsigned_type может сказать, имеет ли объект целочисленный тип без знака или нет в большинстве случаев.

  • если значение отрицательное, очевидно, что тип не unsigned.
  • если значение N положительное, то
    • если -N положительно, тогда N имеет unsigned тип,
    • если -N отрицательно, а N находится в диапазоне от 0 до INT_MAX, то типа N может быть signed или unsigned, но это подойдет в диапазоне положительных значенийint, который подходит в диапазоне uintmax_t.

2-е переиздание:

Ir кажется, что здесь есть подходы к решению той же проблемы. Мой подход учитывает диапазон значений и правила продвижения целое число для получения правильного печатного значения с printf(). С другой стороны, подход Гжегожа Шпетковского определяет подписано характер тип в прямой форме. Мне нравятся оба.


поскольку вы уже используете заголовок C99, есть возможность использовать точный спецификатор формата ширины в зависимости от sizeof(T) и подписанный/неподписанный чек. Это, однако, должно быть сделано после фазы предварительной обработки (так печально ## оператор не может использоваться здесь для построения PRI-токена). Вот идея:--7-->

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

для этого требуется немного плутовство Заявление Expr, но может быть какой-то другой способ (это "цена", чтобы быть общим), а также.

EDIT:

вот вторая версия, которая не требует определенного расширения компилятора (не волнуйтесь, я не могу его прочитать), используя функциональный макрос:

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

обратите внимание, что условный оператор оставил assiociativity (таким образом evalutes слева по назначению).


используя универсальные макросы типа 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"));
}

существует потенциальная проблема: если есть типы, отличные от перечисленных (т. е., кроме signed и unsigned версии char, short, int, long и long long), это не работает для этих типов. Также невозможно добавить такие типы, как size_t и intmax_t к типу generic macro "на всякий случай", потому что он будет вызвать ошибку, если они are typedefd из одного из уже перечисленных типов. Я оставил default случай не указан, так что макрос генерирует ошибку времени компиляции, когда не найден соответствующий тип.

однако, как видно из примера программы,size_t и intmax_t отлично работает на платформах, где они такие же, как один из перечисленных типов (например, такие же, как long). Аналогично, нет проблемы, если, например,long и long long или long и int, являются однотипный. Но более безопасной версией может быть просто cast to intmax_t или uintmax_t в соответствии с signedness (как видно из других ответов), и сделать тип общий макрос только с этими параметрами...

косметическая проблема заключается в том, что тип generic macro расширяется круглыми скобками вокруг строкового литерала, предотвращая конкатенацию с соседними строковыми литералами обычным способом. Это предотвращает такие вещи, как:

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

отсюда PRIFMT макрос с префиксом и суффиксом включены для общего случая печати одной переменной:

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

(обратите внимание, что было бы просто развернуть этот макрос с нецелыми типами, поддерживаемыми printf, например, double, char *...)