В кроссплатформенном проекте c / C++ (Win32, Linux, OSX) мне нужно использовать функции * printf для печати некоторых переменных типа size_t. В некоторых средах size_t составляет 8 байтов, а в других - 4. В glibc у меня есть% zd, а в Win32 я могу использовать %Идентификатор. Есть ли элегантный способ справиться с этим?
@CiroSantilli Это тоже C++ 11.
% zd в настоящее время реализован в Visual Studio См. также stackoverflow.com/questions/15610053/…
Я не знаю какого-либо удовлетворительного решения, но вы можете подумать о специальной функции для форматирования элементов 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);
Хех - я надеялся, что до этого не дойдет.
Надеюсь, кто-то другой предоставит что-нибудь получше ...
Макрос 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 (как показано здесь).
@ ΤΖΩΤΖΙΟY, я добавил префикс, но что делают обратные кавычки вокруг PRIuPTR? Я не вижу разницы.
Обратные кавычки позволяют вставлять код (или другие вещи, которые вы не хотите форматировать с помощью уценки) в тексте. Модуль форматирования уценки часто сбивает с толку из-за таких вещей, как подчеркивание в идентификаторах. В этом помогает обратный перенос встроенного кода.
Обратные кавычки в комментариях не работают. Не знаю почему. Также без превью в комментариях.
Обратные кавычки также позволяют читателю увидеть разницу между Int и lnt :)
Если это C++, не забудьте определить __STDC_FORMAT_MACROS перед включением <inttypes.h>.
Visual Studio 2013 теперь поддерживает inttypes.h
Дэн Сакс написал статью в Embedded Systems Design, которая покрытый по этому поводу. По словам Дэна,% zu - это стандартный способ, но немногие компиляторы его поддерживают. В качестве альтернативы он рекомендовал использовать% lu вместе с явным приведением аргумента к unsigned long:
size_t n; ... printf("%lu", (unsigned long)n);
Это не так хорошо для систем, использующих модель программирования LLP64, таких как 64-битная Windows.
% zu - это изобретение C99. Эти компиляторы действительно редки. У C++ нет проблем с самого начала.
% zu не имеет ничего общего с компилятором и все, что связано со стандартными библиотеками ...
% lu и unsigned long - проблема только в 64-битных системах, когда вы хотите иметь возможность отображать значения, превышающие 2 ^ 32-1. В противном случае всегда приводите к unsigned long long (работает так же хорошо для 32-битных) и используйте% llu.
Используйте 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
.
Согласно cppcheck, в Windows мы могли использовать оба формата.
Вам просто нужно найти целочисленный тип с самым большим классом хранения, привести к нему значение, а затем использовать соответствующую строку формата для большего типа. Обратите внимание, что это решение будет работать для любого типа (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 Типичный double имеет 53-битную точность и поэтому оставит пробелы около 9 000 000 гигабайт, а не 32, как предлагается. Даже педантичная минимальная спецификация C допускает 33+ бит точности. Интересно, откуда у вас 32-битная прецессия?
Ну, 32 исходит из того факта, что между этим и 64 нет целого числа, и, как вы говорите, 64 бита - это слишком много для двойного. Так что да, пробелы при использовании этого для размеров могут быть не обычным явлением, но вопрос (заголовок) здесь в том, как напечатать size_t. Использование double не является хорошим общим решением. И не будет хорошо работать ...
@rubenvb FWIW, я работал с (u)int48_t
, и, конечно, это не обычный тип. Для печати размеров объектов на ближайшее десятилетие достаточно 53 бита, и использование double
для этого остается переносимым. Тем не менее, к вашему мнению, OP действительно сказал «напечатать некоторые переменные типа size_t», поэтому эти значения могут превышать размер одного объекта из-за различных вычислений. Таким образом, это не соответствует точной цели OP. Проблема с производительностью в лучшем случае незначительна, а в худшем - простая преждевременная оптимизация. Цель OP - кроссплатформенность, а не скорость. Любопытно - какое решение вы считаете лучшим для кроссплатформенности?
используя достойный компилятор / библиотеку C99, которая поддерживает правильные строки / макросы формата printf. За исключением этого, # определите их самостоятельно. На самом деле, Visual Studio делает это труднее, чем нужно.
@rubenvb Достаточно честно - я решил, что кроссплатформенный тег также ищет решение C89 - C11, не заставляя компилятор вносить изменения. Согласитесь, что использование недавно совместимого поставщика решает эту проблему. Спасибо за отзыв, с которым я в основном согласен.
@rubenvb% zd и% zu работает в Visual Studio 2013 и выше: stackoverflow.com/questions/15610053/…
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);
Однако там, где это возможно, просто используйте спецификатор длины %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
.
Однако если вам нужно, чтобы это был абсолютно гарантированно работать, или если вы не уверены в своей конкретной архитектуре, просто выполните приведение и печать как uint64_t
и готово, так как это гарантированно сработает, но требует дополнительного шага приведения.
Пример использования:
#include <stdint.h> // for uint64_t
#include <inttypes.h> // for PRIu64
size_t my_variable;
printf("%" PRIu64 "\n", (uint64_t)my_variable);
Примечание:
%zd
- это C99, который Microsoft очень неохотно внедряет.