Чистий код для друкуf size_t в C ++ (або: найближчий еквівалент% z в C ++)


96

У мене є код C ++, який друкує size_t:

size_t a;
printf("%lu", a);

Я хотів би, щоб це компілювалось без попереджень як для 32-, так і для 64-розрядної архітектури.

Якби це був C99, я міг би використати printf("%z", a);. Але AFAICT %zне існує в жодному стандартному діалекті С ++. Тому натомість я маю це зробити

printf("%lu", (unsigned long) a);

що насправді негарно.

Якщо немає можливості для друку size_ts, вбудованої в мову, цікаво, чи можна написати обгортку printf або щось таке, що вставить відповідні відливки на size_ts, щоб усунути помилкові попередження компілятора, зберігаючи при цьому хороші.

Якісь ідеї?


Редагувати Щоб пояснити, чому я використовую printf: У мене є відносно велика база коду, яку я прибираю. Він використовує обгортки printf, щоб робити такі речі, як "написати попередження, увійти в файл і, можливо, вийти з коду з помилкою". Можливо, я зможу зібрати достатньо C ++ - foo, щоб зробити це за допомогою обгортки cout, але я волів би не змінювати кожен виклик warn () у програмі, аби позбутися деяких попереджень компілятора.


4
Чому ви взагалі використовуєте printf, має бути питання.
Ed S.

ваш компілятор перевіряє для вас рядок printf та перевірку типу?
Pod

Мій компілятор справді перевіряє рядок формату printf і перевіряє тип для мене. Я хотів би, щоб ця функція була ввімкнена.
Джастін Л.

2
% zu, z - специфікатор ширини, а не специфікатор типу. Це працює для c printf, який ви можете легко використовувати на C ++. Я коментував це нижче, тож проголосуйте за це;)
Буде

Якщо ви використовуєте Visual Studio, чи не можете просто використовувати "%l"? Чи не завжди це буде правильного розміру? Або портативність має значення?
Mooing Duck

Відповіді:


61

У більшості компіляторів є власний специфікатор для size_tі ptrdiff_tаргументів, Visual C ++, наприклад, використовує% Iu та% Id відповідно, я думаю, що gcc дозволить вам використовувати% zu та% zd.

Ви можете створити макрос:

#if defined(_MSC_VER) || defined(__MINGW32__) //__MINGW32__ should goes before __GNUC__
  #define JL_SIZE_T_SPECIFIER    "%Iu"
  #define JL_SSIZE_T_SPECIFIER   "%Id"
  #define JL_PTRDIFF_T_SPECIFIER "%Id"
#elif defined(__GNUC__)
  #define JL_SIZE_T_SPECIFIER    "%zu"
  #define JL_SSIZE_T_SPECIFIER   "%zd"
  #define JL_PTRDIFF_T_SPECIFIER "%zd"
#else
  // TODO figure out which to use.
  #if NUMBITS == 32
    #define JL_SIZE_T_SPECIFIER    something_unsigned
    #define JL_SSIZE_T_SPECIFIER   something_signed
    #define JL_PTRDIFF_T_SPECIFIER something_signed
  #else
    #define JL_SIZE_T_SPECIFIER    something_bigger_unsigned
    #define JL_SSIZE_T_SPECIFIER   something_bigger_signed
    #define JL_PTRDIFF_T_SPECIFIER something-bigger_signed
  #endif
#endif

Використання:

size_t a;
printf(JL_SIZE_T_SPECIFIER, a);
printf("The size of a is " JL_SIZE_T_SPECIFIER " bytes", a);

5
Це не так просто. Якщо %zпідтримується чи ні , залежить від середовища виконання, а НЕ компіляції. Використовуючи __GNUC__тому трохи проблеми, якщо змішати GCC / MinGW з MSVCRT (і без використання доповненого Printf MinGW в).
jørgensen

68

Специфікатор printfформату %zuбуде чудово працювати в системах C ++; немає необхідності ускладнювати.


9
@ChrisMarkle Швидкий тест показує, що це не працює в MinGW. Також сайт MS не вказує його ( msdn.microsoft.com/en-us/library/tcxf1dw6%28v=vs.100%29.aspx ). Я припускаю, що відповідь - ні.
wump

17

C ++ 11

C ++ 11 імпортує C99, тому std::printfповинен підтримувати %zuспецифікатор формату C99 .

C ++ 98

На більшості платформ, size_tі uintptr_tеквівалентні, в цьому випадку ви можете використовувати PRIuPTRмакрос , визначений в <cinttypes>:

size_t a = 42;
printf("If the answer is %" PRIuPTR " then what is the question?\n", a);

Якщо ви дійсно хочете бути в безпеці, перейдіть uintmax_tі використовуйте PRIuMAX:

printf("If the answer is %" PRIuMAX " then what is the question?\n", static_cast<uintmax_t>(a));

16

У Windows і реалізація Visf Studio в printf

 %Iu

працює для мене. див. msdn


Дякую. Працює VS 2008теж. Крім того, майте на увазі , що можна використовувати %Id, %Ixі %IXтеж.
c00000fd

11

Оскільки ви використовуєте C ++, чому б не використовувати IOStreams? Це має компілюватися без попереджень і робити правильну інформацію, яка обізнана з типом, якщо ви не використовуєте реалізацію C ++, що не працює в мозку, яка не визначає operator <<for size_t.

Коли з фактичним виходом потрібно закінчити printf(), ви все ще можете комбінувати його з потоками IOS, щоб отримати безпечну поведінку типу:

size_t foo = bar;
ostringstream os;
os << foo;
printf("%s", os.str().c_str());

Це не надто ефективно, але у вашому випадку вище йдеться про введення / виведення файлів, тож це ваше вузьке місце, а не цей код форматування рядків.


Я знаю, що Google забороняє використовувати cout у їх коді. Можливо, Джастін Л. працює під таким обмеженням.

У моєму випадку (див. Редагування вище), цікавою ідеєю може бути спробувати реалізувати функцію warn () з точки зору cout. Але це передбачало б синтаксичний розбір рядків формату вручну, що ... хитро. :)
Джастін Л.

Ваше останнє редагування насправді протилежне тому, що, на мою думку, може працювати для мене. Я не хочу переписувати весь код, який викликає обгортку printf, але я не проти переписати реалізацію обгортки printf, щоб використовувати cout. Але я не думаю, що це станеться. :)
Джастін Л.

Використовуйте std::stringstreamзамість потоків вводу-виводу.
Thomas Eding

1
Потоки мають незручні позначення. Порівняйте: printf("x=%i, y=%i;\n", x, y);проти cout << "x=" << x << ", y=" << y << ";" << std::endl;.
wonder.mice

7

ось можливе рішення, але воно не зовсім гарне ..

template< class T >
struct GetPrintfID
{
  static const char* id;
};

template< class T >
const char* GetPrintfID< T >::id = "%u";


template<>
struct GetPrintfID< unsigned long long > //or whatever the 64bit unsigned is called..
{
  static const char* id;
};

const char* GetPrintfID< unsigned long long >::id = "%lu";

//should be repeated for any type size_t can ever have


printf( GetPrintfID< size_t >::id, sizeof( x ) );

2
Ну ... цим я досягаю своєї мети - безпека і відсутність попереджень. Але ... так. Я прийму попередження, якщо це те, що мені потрібно робити. :)
Джастін Л.

1
Не гарна ?! Залежить від смаку. Це дозволяє повністю переносити printf із такими звірами, як uintptr_t та подібні. Чудово!
Слава

@ user877329 ви можете створити цей рядок формату як std :: string, а потім додати GetPrintfID <size_t> :: id там, де вам це потрібно
stijn

@stijn Іншими словами: жодна конкатенація під час компіляції
недоступна

@ user877329 ні (якщо не використовую макроси, або я чогось пропускаю). Але чому це буде жорсткою вимогою?
stijn

4

Бібліотека fmt забезпечує швидку портативну (і безпечну) реалізацію, printfвключаючи zмодифікатор для size_t:

#include "fmt/printf.h"

size_t a = 42;

int main() {
  fmt::printf("%zu", a);
}

На додаток до цього він підтримує синтаксис рядка формату, подібного до Python, і фіксує інформацію про тип, так що вам не потрібно вводити його вручну:

fmt::print("{}", a);

Він був протестований з основними компіляторами та забезпечує стабільний вихід на різних платформах.

Застереження : Я автор цієї бібліотеки.


3

Ефективний тип, який лежить в основі size_t, залежить від реалізації . C Standard визначає його як тип, що повертається оператором sizeof; крім того, що не має підпису та є свого роду цілісним типом, size_t може бути майже будь-яким, розмір якого може вмістити найбільше значення, яке, як очікується, буде повернуто sizeof ().

Отже, рядок формату, який буде використовуватися для size_t, може змінюватися залежно від сервера. На ньому завжди повинно бути "u", але може бути l або d або, можливо, щось інше ...

Фокусом може бути приведення його до найбільшого цілісного типу на машині, не забезпечуючи втрат при перетворенні, а потім використання рядка формату, пов’язаного з цим відомим типом.


Я б здорово відкинув свій size_ts на найбільший інтегральний тип на машині та використовуючи рядок формату, пов’язаний з цим типом. Моє запитання: чи є спосіб зробити це, зберігаючи чистий код (попередження лише про допустимі помилки рядків у форматі printf, відсутність негарних приводів тощо)? Я міг написати обгортку, яка змінює рядок формату, але тоді GCC не зможе попереджати мене, коли я законно зіпсував свій рядок формату.
Джастін Л.

Використовуйте макроси CPP для перевірки розміру типів; перейдіть до тієї, що відповідає, і вкажіть рядок формату, який відповідає типу відповідності.
Ясніше

0

#include <cstdio>
#include <string>
#include <type_traits>

namespace my{
    template<typename ty>
    auto get_string(ty&& arg){
        using rty=typename::std::decay_t<::std::add_const_t<ty>>;
        if constexpr(::std::is_same_v<char, rty>)
            return ::std::string{1,arg};
        else if constexpr(::std::is_same_v<bool, rty>)
            return ::std::string(arg?"true":"false");
        else if constexpr(::std::is_same_v<char const*, rty>)
            return ::std::string{arg};
        else if constexpr(::std::is_same_v<::std::string, rty>)
            return ::std::forward<ty&&>(arg);
        else
            return ::std::to_string(arg);
    };

    template<typename T1, typename ... Args>
    auto printf(T1&& a1, Args&&...arg){
        auto str{(get_string(a1)+ ... + get_string(arg))};
        return ::std::printf(str.c_str());
    };
};

Пізніше в коді:

my::printf("test ", 1, '\t', 2.0);

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.