Как можно напечатать переменную size_t с помощью семейства printf?

405

У меня есть переменная типа size_t, и я хочу напечатать ее с помощью printf(). Какой спецификатор формата я использую для его печати?

В 32-битной машине, %uпохоже, прав. Я собрал g++ -g -W -Wall -Werror -ansi -pedantic, и не было никакого предупреждения. Но когда я компилирую этот код на 64-битной машине, он выдает предупреждение.

size_t x = <something>;
printf("size = %u\n", x);

warning: format '%u' expects type 'unsigned int', 
    but argument 2 has type 'long unsigned int'

Как и ожидалось, предупреждение исчезнет, ​​если я поменяю его на %lu.

Вопрос в том, как мне написать код, чтобы он свободно компилировал предупреждения на 32- и 64-разрядных компьютерах?

Изменить: В качестве обходного пути, я думаю, один из ответов может быть «привести» переменную в целое число, например unsigned long, достаточно большой, и распечатать с помощью %lu. Это будет работать в обоих случаях. Я смотрю, есть ли другая идея.

Arun
источник
4
приведение к unsigned long- лучший вариант, если ваша реализация libc не поддерживает zмодификатор; стандарт C99 рекомендует size_tне иметь целочисленного ранга преобразования выше, чем long, поэтому вы достаточно безопасны
Кристоф
1
На платформе Windows size_t может быть больше, чем long. По причинам совместимости long всегда 32-битный, а size_t может быть 64-битным. Таким образом, приведение к unsigned long может привести к потере половины битов. Извините :-)
Брюс Доусон

Ответы:

484

Используйте zмодификатор:

size_t x = ...;
ssize_t y = ...;
printf("%zu\n", x);  // prints as unsigned decimal
printf("%zx\n", x);  // prints as hex
printf("%zd\n", y);  // prints as signed decimal
Адам Розенфилд
источник
7
+1. Это дополнение C99 или это относится и к C ++ (у меня нет C90 под рукой)?
Авакар
6
это дополнение C99, которое не фигурирует в списке printf()модификаторов длины черновика C ++ 0x от 2009-11-09 (таблица 84 на стр. 672)
Кристоф
3
@Christoph: И это не в последнем проекте, n3035.
GManNickG
11
@avakar @Adam Rosenfield @Christoph @GMan: Тем не менее, в n3035 §1.2 Нормативные ссылки упоминается только стандарт C99, а в §17.6.1.2 / 3 те же состояния: «Предоставляются возможности стандартной библиотеки C». Я бы сказал, что если не указано иное, все в стандартной библиотеке C99 является частью стандартной библиотеки C ++ 0x, включая дополнительные спецификаторы формата в C99.
Джеймс МакНеллис
9
@ArunSaha: это особенность только C99, а не C ++. Если вы хотите, чтобы он компилировался -pedantic, вам нужно либо получить компилятор, поддерживающий черновой вариант C ++ 1x (крайне маловероятно), либо вам нужно переместить свой код в файл, скомпилированный как C99. В противном случае, ваш единственный вариант - привести ваши переменные unsigned long longи использовать %lluих как максимально переносимые.
Адам Розенфилд
88

Похоже, это зависит от того, какой компилятор вы используете (blech):

... и, конечно, если вы используете C ++, вы можете использовать coutвместо этого, как предложено AraK .

TJ Crowder
источник
3
zтакже поддерживается newlib (то есть cygwin)
Кристоф
7
%zdневерно для size_t; это верно для подписанного типа, соответствующего size_t, но size_tсам по себе является неподписанным типом.
Кит Томпсон
1
@KeithThompson: я тоже упомянул %zu%zxв случае, если они хотят hex). Достаточно верно, что, %zuвероятно, должен был быть первым в списке. Исправлена.
TJ Crowder
10
@TJCrowder: я не думаю, что %zdдолжен быть в списке вообще. Я не могу думать ни о какой причине, чтобы использовать %zdвместо того, %zuчтобы напечатать size_tзначение. Это даже не верно (имеет неопределенное поведение), если значение превышает SIZE_MAX / 2. (Для полноты вы можете упомянуть %zoо восьмеричном.)
Кит Томпсон
2
@FUZxxl: POSIX не требует, чтобы ssize_tэто был тип со знаком, соответствующий size_t, поэтому он не гарантированно совпадает "%zd". (Это, вероятно , на большинстве реализаций.) Pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Кейт Томпсон
59

Для C89 используйте %luи приведите значение к unsigned long:

size_t foo;
...
printf("foo = %lu\n", (unsigned long) foo);

Для C99 и более поздних версий используйте %zu:

size_t foo;
...
printf("foo = %zu\n", foo);
Джон Боде
источник
7
Учитывая 2013 год, предложите «Для C99 и далее» и «Для до C99:». Лучший ответ.
Чукс - Восстановите Монику
8
Не делайте этого. Сбой в 64-битной Windows, где size_t - 64-битная, а long - 32-битная.
Иттрилл
1
@Yttrill: Каков ответ для 64-битных окон, тогда?
Джон Боде
1
@JohnBode Возможно unsigned long long?
Джеймс Ко
2
Или: вы можете привести к a, uint64_tа затем использовать PRIu64макрос из inttypes.h, который содержит спецификатор формата.
Джеймс Ко
9

Расширение на ответ Адама Розенфилда для Windows.

Я тестировал этот код на VS2013 Update 4 и VS2015 preview:

// test.c

#include <stdio.h>
#include <BaseTsd.h> // see the note below

int main()
{
    size_t x = 1;
    SSIZE_T y = 2;
    printf("%zu\n", x);  // prints as unsigned decimal
    printf("%zx\n", x);  // prints as hex
    printf("%zd\n", y);  // prints as signed decimal
    return 0;
}

VS2015 генерирует двоичные выходы:

1
1
2

в то время как тот, созданный VS2013, говорит:

ZU
ZX
ZD

Примечание: ssize_tэто расширение POSIX и SSIZE_Tаналогичное в типах данных Windows , поэтому я добавил <BaseTsd.h>ссылку.

Кроме того, за исключением следующих заголовков C99 / C11, все заголовки C99 доступны в предварительном просмотре VS2015:

C11 - <stdalign.h>
C11 - <stdatomic.h>
C11 - <stdnoreturn.h>
C99 - <tgmath.h>
C11 - <threads.h>

Кроме того, C11 <uchar.h>теперь включен в последний предварительный просмотр.

Для получения дополнительной информации см. Этот старый и новый список для соответствия стандарту.

вулкан ворон
источник
Обновление 5 VS2013 дает те же результаты, что и обновление 4.
Натан Кидд
6

Тем, кто говорит об этом в C ++, который не обязательно поддерживает расширения C99, я настоятельно рекомендую boost :: format. Это делает вопрос размера size_t спорным:

std::cout << boost::format("Sizeof(Var) is %d\n") % sizeof(Var);

Поскольку вам не нужны указатели размера в boost :: format, вы можете просто беспокоиться о том, как вы хотите отобразить значение.

swestrup
источник
4
Наверное, хочу %uтогда.
GManNickG
5
std::size_t s = 1024;
std::cout << s; // or any other kind of stream like stringstream!
арак
источник
8
Да, но спрашивающий спрашивает специально для printfуточнения. Я предполагаю, что у них есть некоторые другие неустановленные ограничения, которые делают использование std::coutпроблемы.
Donal Fellows
1
@ Донал Интересно, какую проблему могут создать потоки C ++ в проекте C ++!
AraK
9
@AraK. Они очень медленные? Они добавляют много байтов по небольшой причине. АрунСаха просто хочет знать для его / ее личного знания? Персональные предпочтения (я предпочитаю, чтобы stdio занимался сам). Есть много причин.
KitsuneYMG
1
@TKCrowder: Ну, в первоначальном запросе говорилось, что нужно решение C (с помощью тегов), и есть веские причины не использовать потоки в C ++, например, если дескриптор выходного формата извлекается из каталога сообщений. (Вы можете написать анализатор для сообщений и использовать потоки, если хотите, но это большая работа, когда вы можете просто использовать существующий код.)
Donal Fellows
1
@Donal: теги были C и C ++. Я никоим образом не сторонник потокового ввода-вывода в C ++ (я не фанат этого), просто отмечаю, что вопрос изначально не * * ... запрашивал спецификацию для printfспецификатора ".
TJ Crowder
2

Как сказал АраК, интерфейс потоков c ++ всегда будет работать переносимо.

std :: size_t s = 1024; std :: cout << s; // или любой другой вид потока, например, stringstream!

Если вы хотите использовать C stdio, для некоторых случаев «портативного» ответа на этот вопрос не существует. И это уродливо, поскольку, как вы уже видели, выбор неправильных флагов формата может привести к предупреждению компилятора или неверному выводу.

C99 пытался решить эту проблему с помощью таких форматов inttypes.h, как "%" PRIdMAX "\ n". Но, как и в случае с «% zu», не все поддерживают c99 (как MSVS до 2013 года). Есть файлы "msinttypes.h", которые разбираются с этим.

Если вы приведете к другому типу, в зависимости от флагов вы можете получить предупреждение компилятора об усечении или изменении знака. Если вы идете по этому маршруту, выберите больший соответствующий тип фиксированного размера. Один из unsigned long long и "% llu" или unsigned long "% lu" должен работать, но llu также может замедлить работу в 32-битном мире как чрезмерно большой. (Изменить - мой Mac выдает предупреждение в 64-битной версии, что% llu не соответствует size_t, хотя% lu,% llu и size_t имеют одинаковый размер. И% lu и% llu не совпадают по размеру на моем MSVS2012. вам может понадобиться привести + использовать формат, который соответствует.)

В этом отношении вы можете использовать фиксированные размеры, такие как int64_t. Но ждать! Теперь мы вернулись к c99 / c ++ 11, и старый MSVS снова дает сбой. Плюс у вас также есть приведения (например, map.size () не является фиксированным размером)!

Вы можете использовать сторонний заголовок или библиотеку, такую ​​как boost. Если вы еще не используете его, возможно, вы не захотите раздувать свой проект таким образом. Если вы готовы добавить один только для этой проблемы, почему бы не использовать потоки C ++ или условную компиляцию?

Таким образом, вы знакомы с потоками c ++, условной компиляцией, сторонними фреймворками или чем-то вроде портативного устройства, которое работает на вас.

Рик Берге
источник
-1

Будет ли он предупреждать вас, если вы передадите 32-разрядное целое число без знака в формат% lu? Это должно быть хорошо, так как преобразование четко определено и не теряет никакой информации.

Я слышал, что некоторые платформы определяют макросы в <inttypes.h>том смысле, что вы можете вставить их в строковый литерал формата, но я не вижу этого заголовка в моем компиляторе Windows C ++, что подразумевает, что он не может быть кроссплатформенным.

Kylotan
источник
1
Большинство компиляторов не предупредит вас, если вы передадите что-то неправильного размера в printf. GCC является исключением. inttypes.h был определен в C99, так что любой компилятор C, совместимый с C99, будет иметь его, и к настоящему времени он должен быть им всем. Тем не менее, вам, возможно, придется включить C99 с флагом компилятора. В любом случае, intttypes.h не определяет конкретный формат для size_t или ptrdiff_t, поскольку было решено, что они достаточно важны, чтобы получить свои собственные спецификаторы размера 'z' и 't' соответственно.
swestrup
Если вы используете %lu, вы должны привести size_tзначение к unsigned long. Не существует неявного преобразования (кроме рекламных) для аргументов в printf.
Кит Томпсон
-2

C99 определяет "% zd" и т. Д. Для этого. (спасибо комментаторам) В C ++ нет переносимого спецификатора формата - вы можете использовать %p, какое слово woulkd в этих двух сценариях, но также не является переносимым выбором, и дает значение в шестнадцатеричном формате.

В качестве альтернативы используйте потоковую передачу (например, stringstream) или безопасную замену printf, например Boost Format . Я понимаю, что этот совет имеет ограниченное использование (и требует C ++). (Мы использовали подобный подход, приспособленный для наших нужд при реализации поддержки юникода.)

Фундаментальная проблема для C состоит в том, что printf, использующий многоточие, небезопасен по своей конструкции - он должен определить размер дополнительного аргумента из известных аргументов, поэтому он не может быть исправлен для поддержки «что бы вы ни получили». Так что, если ваш компилятор не реализует некоторые проприетарные расширения, вам не повезло.

peterchen
источник
2
zразмер modidfier является стандартным C, но некоторые Libc реализации застряла в 1990 году по различным причинам (например , Microsoft в основном заброшенные C в пользу C ++ и - совсем недавно - C #)
Christoph
3
C99 определил спецификатор размера 'z' как размер значения size_t, а 't' как размер значения ptrdiff_t.
swestrup
2
%zdэто неправильно, это без знака, так и должно быть %zu.
Дэвид Конрад
-3

На некоторых платформах и для некоторых типов доступны определенные спецификаторы преобразования printf, но иногда приходится прибегать к приведению к более крупным типам.

Я задокументировал эту сложную проблему здесь, с примером кода: http://www.pixelbeat.org/programming/gcc/int_types/ и периодически обновляю его информацией о новых платформах и типах.

pixelbeat
источник
1
Обратите внимание, что ответы только на ссылки не приветствуются, поэтому ответы SO должны быть конечной точкой поиска решения (по сравнению с еще одной остановкой ссылок, которая, как правило, устаревает со временем). Пожалуйста, рассмотрите возможность добавления отдельного краткого обзора здесь, сохраняя ссылку в качестве ссылки.
Клеопатра
-5

если вы хотите напечатать значение size_t в виде строки, вы можете сделать это:

char text[] = "Lets go fishing in stead of sitting on our but !!";
size_t line = 2337200120702199116;

/* on windows I64x or I64d others %lld or %llx if it works %zd or %zx */
printf("number: %I64d\n",*(size_t*)&text);
printf("text: %s\n",*(char(*)[])&line);

результат:

номер: 2337200120702199116

Текст: Пойдем на рыбалку вместо того, чтобы сидеть на нашем но !!

Редактировать: перечитывая вопрос из-за отрицательных голосов, я заметил, что его проблема не в% llu или% I64d, а в типе size_t на разных машинах, смотрите этот вопрос https://stackoverflow.com/a/918909/1755797
http: // www. cplusplus.com/reference/cstdio/printf/

size_t - это unsigned int на 32-битной машине и unsigned long long int на 64-битной системе,
но% ll всегда ожидает unsigned long long int.

size_t различается по длине в разных операционных системах, в то время как% llu одинаково

Andre
источник
4
Что за чушь ?!
Антти Хаапала
приведение первых 8 байтов массива char к длинному 64-битному биту без знака через указатель size_t и печать их как числа с printf% I64d не очень впечатляет, я знаю, конечно, я не делал код, чтобы предотвратить переполнение типа, но это не в рамках вопроса.
Андре