Строка кроссплатформенного формата для переменных типа size_t?

В кроссплатформенном проекте c / C++ (Win32, Linux, OSX) мне нужно использовать функции * printf для печати некоторых переменных типа size_t. В некоторых средах size_t составляет 8 байтов, а в других - 4. В glibc у меня есть% zd, а в Win32 я могу использовать %Идентификатор. Есть ли элегантный способ справиться с этим?

Примечание: %zd - это C99, который Microsoft очень неохотно внедряет.

Ciro Santilli TRUMP BAN IS BAD 02.10.2015 12:35

@CiroSantilli Это тоже C++ 11.

L. F. 02.05.2019 03:01

% zd в настоящее время реализован в Visual Studio См. также stackoverflow.com/questions/15610053/…

Étienne 08.07.2019 15:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
32
3
16 685
10

Ответы 10

Я не знаю какого-либо удовлетворительного решения, но вы можете подумать о специальной функции для форматирования элементов size_t в строку и печати строки.

(В качестве альтернативы, если вам это сойдет с рук, boost :: format легко справится с подобными вещами.)

Единственное, что я могу придумать, это типичное:

#ifdef __WIN32__ // or whatever
#define SSIZET_FMT "%ld"
#else
#define SSIZET_FMT "%zd"
#endif

а затем воспользовавшись постоянным сворачиванием:

fprintf(stream, "Your size_t var has value " SSIZET_FMT ".", your_var);

Хех - я надеялся, что до этого не дойдет.

twk 06.10.2008 19:09

Надеюсь, кто-то другой предоставит что-нибудь получше ...

tzot 06.10.2008 19:14

Макрос PRIuPTR (из <inttypes.h>) определяет десятичный формат для uintptr_t, который всегда должен быть достаточно большим, чтобы вы могли преобразовать в него size_t без усечения, например

fprintf(stream, "Your size_t var has value %" PRIuPTR ".", (uintptr_t) your_var);

finnw, вам лучше изменить свой PRIuPTR на PRIuPTR и префикс вашей строки «fprintf» с 4 пробелами, чтобы они были отформатированы как код и не было путаницы между PRIuPTR и PRluPTR (как показано здесь).

tzot 06.10.2008 19:37

@ ΤΖΩΤΖΙΟY, я добавил префикс, но что делают обратные кавычки вокруг PRIuPTR? Я не вижу разницы.

finnw 06.10.2008 20:49

Обратные кавычки позволяют вставлять код (или другие вещи, которые вы не хотите форматировать с помощью уценки) в тексте. Модуль форматирования уценки часто сбивает с толку из-за таких вещей, как подчеркивание в идентификаторах. В этом помогает обратный перенос встроенного кода.

Michael Burr 06.10.2008 21:15

Обратные кавычки в комментариях не работают. Не знаю почему. Также без превью в комментариях.

Rhythmic Fistman 11.10.2008 01:01

Обратные кавычки также позволяют читателю увидеть разницу между Int и lnt :)

tzot 15.10.2008 20:47

Если это C++, не забудьте определить __STDC_FORMAT_MACROS перед включением <inttypes.h>.

Sergey Shandar 10.05.2011 10:31

Visual Studio 2013 теперь поддерживает inttypes.h

BSalita 07.05.2014 22:04

Дэн Сакс написал статью в Embedded Systems Design, которая покрытый по этому поводу. По словам Дэна,% zu - это стандартный способ, но немногие компиляторы его поддерживают. В качестве альтернативы он рекомендовал использовать% lu вместе с явным приведением аргумента к unsigned long:

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

Это не так хорошо для систем, использующих модель программирования LLP64, таких как 64-битная Windows.

bk1e 07.10.2008 09:08

% zu - это изобретение C99. Эти компиляторы действительно редки. У C++ нет проблем с самого начала.

MSalters 07.10.2008 16:02

% zu не имеет ничего общего с компилятором и все, что связано со стандартными библиотеками ...

plinth 03.11.2008 16:28

% lu и unsigned long - проблема только в 64-битных системах, когда вы хотите иметь возможность отображать значения, превышающие 2 ^ 32-1. В противном случае всегда приводите к unsigned long long (работает так же хорошо для 32-битных) и используйте% llu.

paniq 16.03.2011 22:09
Архивная версия of the article by Dan Saks.
Nathan Mills 20.02.2019 13:24

Используйте boost::format. Это типично, поэтому он будет правильно печатать size_t с %d, также вам не нужно помнить о том, чтобы ставить c_str() на std::string при его использовании, и даже если вы передадите номер в %s или наоборот, он будет работать.

Здесь действительно есть два вопроса. Первый вопрос - какова правильная строка спецификатора printf для трех платформ. Обратите внимание, что size_t - это беззнаковый тип.

На Окна используйте «%Iu».

На Linux и OSX используйте «%zu».

Второй вопрос - как поддерживать несколько платформ, учитывая, что такие вещи, как строки формата, могут отличаться на каждой платформе. Как отмечали другие люди, использование #ifdef быстро становится уродливым.

Вместо этого напишите отдельный make-файл или файл проекта для каждой целевой платформы. Затем обратитесь к спецификатору по имени макроса в ваших исходных файлах, соответствующим образом определяя макрос в каждом make-файле. В частности, и GCC, и Visual Studio принимают переключатель «D» для определения макросов в командной строке.

Если ваша система сборки очень сложна (несколько вариантов сборки, сгенерированные источники и т. д.), Поддержка трех отдельных make-файлов может стать громоздкой, и вам придется использовать какую-то расширенную систему сборки, такую ​​как CMake или автоинструменты GNU. Но основной принцип тот же - используйте систему сборки для определения макросов, специфичных для платформы, вместо того, чтобы помещать логику определения платформы в ваши исходные файлы.

Хорошо, чтобы указать на %Iu (win) и %zu (mac / linux), которые более официально верны, чем то, что предлагалось в вопросе. Windows официально определяет макрос _WIN32, и я лично считаю, что при разработке проще полагаться на небольшие, сконцентрированные предложения #ifdef, основанные на этом макросе, а не на make-файлах для каждой платформы. Хотя я использую Visual Studio и Xcode, у которых также есть собственная версия make-файлов. Разница заключается в минимизации количества определений макросов и случаев #ifdef.

Tyler 18.07.2014 20:26

Согласно cppcheck, в Windows мы могли использовать оба формата.

alcor 06.02.2015 18:43

Вам просто нужно найти целочисленный тип с самым большим классом хранения, привести к нему значение, а затем использовать соответствующую строку формата для большего типа. Обратите внимание, что это решение будет работать для любого типа (ptrdiff_t и т. д.), А не только для size_t.

Вы хотите использовать uintmax_t и макрос формата PRIuMAX. Для Visual C++ вам потребуется загрузить c99-совместимые заголовки stdint.h и inttypes.h, поскольку Microsoft их не предоставляет.

Также см

http://www.embedded.com/columns/technicalinsights/204700432

Эта статья исправляет ошибки в статье, цитируемой Фредерико.

Мой выбор для этой проблемы - просто привести аргумент size_t к unsigned long и использовать% lu везде - это, конечно, только там, где не ожидается, что значения превысят 2 ^ 32-1. Если это слишком мало для вас, вы всегда можете преобразовать его в unsigned long long и отформатировать его как% llu.

В любом случае, ваши струны никогда не будут неудобными.

size_t - это тип беззнаковый длиной не менее 16 бит. Часто встречаются ширины 32 и 64.

printf("%zu\n", some_size_t_object); // Standard since C99

Выше - лучший способ продвижения вперед, но если код должен также переноситься на платформы до C99, сократите значение до некоторого широкого типа. unsigned long - разумный кандидат, но может отсутствовать.

// OK, yet insufficient with large sizes > ULONG_MAX
printf("%lu\n", (unsigned long) some_size_t_object); 

или с условным кодом

#ifdef ULLONG_MAX
  printf("%llu\n", (unsigned long long) some_size_t_object); 
#else
  printf("%lu\n", (unsigned long) some_size_t_object); 
#endif

Наконец, рассмотрим double. Это немного неэффективно, но должно обрабатывать все старые и новые платформы примерно до 2030-2040 годов, учитывая Закон Мура, когда double может не получить точного результата.

printf("%.0f\n", (double) some_size_t_object);

Двойные значения оставляют пропуски выше 32 бит, поэтому они часто дают неправильный результат для больших значений.

rubenvb 01.08.2017 00:28

@rubenvb Типичный double имеет 53-битную точность и поэтому оставит пробелы около 9 000 000 гигабайт, а не 32, как предлагается. Даже педантичная минимальная спецификация C допускает 33+ бит точности. Интересно, откуда у вас 32-битная прецессия?

chux - Reinstate Monica 01.08.2017 01:07

Ну, 32 исходит из того факта, что между этим и 64 нет целого числа, и, как вы говорите, 64 бита - это слишком много для двойного. Так что да, пробелы при использовании этого для размеров могут быть не обычным явлением, но вопрос (заголовок) здесь в том, как напечатать size_t. Использование double не является хорошим общим решением. И не будет хорошо работать ...

rubenvb 02.08.2017 17:59

@rubenvb FWIW, я работал с (u)int48_t, и, конечно, это не обычный тип. Для печати размеров объектов на ближайшее десятилетие достаточно 53 бита, и использование double для этого остается переносимым. Тем не менее, к вашему мнению, OP действительно сказал «напечатать некоторые переменные типа size_t», поэтому эти значения могут превышать размер одного объекта из-за различных вычислений. Таким образом, это не соответствует точной цели OP. Проблема с производительностью в лучшем случае незначительна, а в худшем - простая преждевременная оптимизация. Цель OP - кроссплатформенность, а не скорость. Любопытно - какое решение вы считаете лучшим для кроссплатформенности?

chux - Reinstate Monica 02.08.2017 18:12

используя достойный компилятор / библиотеку C99, которая поддерживает правильные строки / макросы формата printf. За исключением этого, # определите их самостоятельно. На самом деле, Visual Studio делает это труднее, чем нужно.

rubenvb 02.08.2017 18:54

@rubenvb Достаточно честно - я решил, что кроссплатформенный тег также ищет решение C89 - C11, не заставляя компилятор вносить изменения. Согласитесь, что использование недавно совместимого поставщика решает эту проблему. Спасибо за отзыв, с которым я в основном согласен.

chux - Reinstate Monica 02.08.2017 19:06

@rubenvb% zd и% zu работает в Visual Studio 2013 и выше: stackoverflow.com/questions/15610053/…

Étienne 08.07.2019 15:28

Опция 1:

Since on most (if not all?) systems, the PRIuPTRстрока формата printf from также достаточно длинный, чтобы содержать тип size_t, я рекомендую использовать следующие определения для строк формата printf size_t.

Однако важно убедиться, что это будет работать для вашей конкретной архитектуры (компилятора, оборудования и т. д.), Поскольку стандарт этого не требует.

#include <inttypes.h>

// Printf format strings for `size_t` variable types.
#define PRIdSZT PRIdPTR
#define PRIiSZT PRIiPTR
#define PRIoSZT PRIoPTR
#define PRIuSZT PRIuPTR
#define PRIxSZT PRIxPTR
#define PRIXSZT PRIXPTR

Пример использования:

size_t my_variable;
printf("%" PRIuSZT "\n", my_variable);

Вариант 2:

Однако там, где это возможно, просто используйте спецификатор длины %zu "z", как показано здесь, для типов size_t:

Пример использования:

size_t my_variable;
printf("%zu\n", my_variable);

Однако в некоторых системах, таких как микроконтроллеры STM32, использующие gcc в качестве компилятора, спецификатор длины %z не обязательно реализован, и выполнение чего-то вроде printf("%zu\n", my_size_t_num); может просто привести к распечатке буквального "% zu" (я лично протестировал это и обнаружил это правда) вместо значения вашей переменной size_t.

Вариант 3:

Однако если вам нужно, чтобы это был абсолютно гарантированно работать, или если вы не уверены в своей конкретной архитектуре, просто выполните приведение и печать как uint64_t и готово, так как это гарантированно сработает, но требует дополнительного шага приведения.

Пример использования:

#include <stdint.h>    // for uint64_t
#include <inttypes.h>  // for PRIu64

size_t my_variable;
printf("%" PRIu64 "\n", (uint64_t)my_variable);

Цитированные источники:

  1. http://www.cplusplus.com/reference/cstdio/printf/
  2. http://www.cplusplus.com/reference/cinttypes/
  3. http://www.cplusplus.com/reference/cstdint/

Другие вопросы по теме