Как печатать типы неизвестного размера, такие как 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 typedef
d из одного из уже перечисленных типов. Я оставил 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 *
...)