ทำความสะอาดโค้ดเพื่อ printf size_t ใน C ++ (หรือ: ใกล้เคียงที่สุดกับ C99 ของ% z ใน C ++)


96

ฉันมีรหัส C ++ ที่พิมพ์size_t:

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

ฉันต้องการให้สิ่งนี้รวบรวมโดยไม่มีคำเตือนทั้งในสถาปัตยกรรม 32- และ 64 บิต

ถ้าเป็น C99 ฉันสามารถprintf("%z", a);ใช้ได้ แต่ AFAICT %zไม่มีอยู่ในภาษา C ++ มาตรฐานใด ๆ ดังนั้นฉันต้องทำแทน

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

ซึ่งน่าเกลียดจริงๆ

หากไม่มีสิ่งอำนวยความสะดวกสำหรับการพิมพ์size_tในภาษาฉันสงสัยว่าเป็นไปได้หรือไม่ที่จะเขียน printf wrapper หรือบางอย่างเช่นนั้นจะแทรก casts ที่เหมาะสมลงในsize_ts เพื่อกำจัดคำเตือนของคอมไพเลอร์ปลอมในขณะที่ยังคงรักษาสิ่งที่ดีไว้

ความคิดใด ๆ ?


แก้ไขเพื่อชี้แจงว่าเหตุใดฉันจึงใช้ printf: ฉันมีฐานรหัสที่ค่อนข้างใหญ่ซึ่งฉันกำลังทำความสะอาด มันใช้ printf wrapper เพื่อทำสิ่งต่างๆเช่น "เขียนคำเตือนบันทึกลงในไฟล์และอาจออกจากโค้ดด้วยข้อผิดพลาด" ฉันอาจรวบรวม C ++ ได้เพียงพอ - foo ที่จะทำสิ่งนี้ด้วย Cout wrapper แต่ฉันไม่อยากเปลี่ยนคำเตือน () ทุกครั้งในโปรแกรมเพียงเพื่อกำจัดคำเตือนของคอมไพเลอร์


4
ทำไมคุณถึงใช้ printf เลยควรเป็นคำถาม
Ed S.

คอมไพเลอร์ของคุณตรวจสอบสตริง printf และพิมพ์ตรวจสอบให้คุณหรือไม่?
Pod

คอมไพเลอร์ของฉันตรวจสอบสตริงรูปแบบ printf และพิมพ์ตรวจสอบให้ฉัน ฉันต้องการเปิดฟีเจอร์นี้ไว้
Justin L.

2
% zu, z เป็นตัวระบุความกว้างไม่ใช่ตัวระบุประเภท ใช้งานได้กับ c printf ที่คุณสามารถใช้งานจาก C ++ ได้อย่างราบรื่น ฉันได้แสดงความคิดเห็นไว้ด้านล่างดังนั้นโหวตให้;)
จะ

หากคุณใช้ Visual Studio คุณจะใช้"%l"ไม่ได้หรือ? นั่นจะไม่ใช่ขนาดที่เหมาะสมเสมอไปหรือ? หรือการพกพาไม่สำคัญ?
เป็ดพะโล้

คำตอบ:


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 (และไม่ใช้ augmented 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 และการนำ Visual Studio ไปใช้ printf

 %Iu

เหมาะกับฉัน ดู msdn


ขอบคุณ. ใช้งานได้VS 2008เช่นกัน นอกจากนี้ยังเก็บไว้ในใจที่หนึ่งสามารถใช้%Id, %Ixและ%IXมากเกินไป
c00000fd

11

เนื่องจากคุณใช้ C ++ ทำไมไม่ใช้ IOStreams ที่ควรรวบรวมโดยไม่มีคำเตือนและทำสิ่งที่ถูกต้องตราบเท่าที่คุณไม่ได้ใช้การใช้งาน C ++ ที่ทำให้สมองตายซึ่งไม่ได้กำหนดoperator <<สำหรับsize_t .

เมื่อต้องทำผลลัพธ์จริงprintf()คุณยังสามารถรวมเข้ากับ IOStreams เพื่อให้ได้ลักษณะการทำงานที่ปลอดภัย:

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

มันไม่ได้มีประสิทธิภาพสูง แต่กรณีของคุณข้างต้นเกี่ยวข้องกับไฟล์ I / O นั่นคือคอขวดของคุณไม่ใช่รหัสการจัดรูปแบบสตริงนี้


ฉันรู้ว่า Google ห้ามใช้ cout ในโค้ดของพวกเขา บางทีจัสตินแอลอาจทำงานภายใต้ข้อ จำกัด ดังกล่าว

ในกรณีของฉัน (ดูแก้ไขด้านบน) ความคิดที่น่าสนใจคือการลองใช้ฟังก์ชันคำเตือน () ในแง่ของ cout แต่นั่นจะเกี่ยวข้องกับการแยกวิเคราะห์สตริงรูปแบบด้วยตนเองซึ่ง ... ยุ่งยาก :)
Justin L.

การแก้ไขล่าสุดของคุณตรงข้ามกับที่ฉันคิดว่าอาจได้ผลสำหรับฉัน ฉันไม่ต้องการเขียนโค้ดทั้งหมดที่เรียกใช้ wrapper printf แต่ฉันไม่รังเกียจที่จะเขียนการใช้งาน printf wrapper ใหม่เพื่อใช้ cout แต่ฉันไม่คิดว่ามันจะเกิดขึ้น :)
Justin L.

ใช้std::stringstreamแทนสตรีม IO
Thomas Eding

1
สตรีมมีสัญกรณ์ที่น่าอึดอัดใจ เปรียบเทียบ: VSprintf("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
นั่นทำให้ฉันบรรลุเป้าหมายเรื่องความปลอดภัยและไม่มีคำเตือน แต่ ... ฉันจะรับคำเตือนหากนั่นคือสิ่งที่ฉันต้องทำ :)
Justin L.

1
ไม่สวย?! ขึ้นอยู่กับรสนิยม. ช่วยให้สามารถใช้งาน printf แบบพกพาได้อย่างเต็มที่กับสัตว์เช่น uintptr_t และเหมือนกัน เยี่ยมมาก!
Slava

@ user877329 คุณสามารถสร้างสตริงรูปแบบนั้นเป็น std :: string จากนั้นต่อท้าย GetPrintfID <size_t> :: id ในสถานที่ที่คุณต้องการ
stijn

@stijn กล่าวอีกนัยหนึ่ง: ไม่มีการเชื่อมต่อเวลาคอมไพล์
user877329

@ 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);

ได้รับการทดสอบกับคอมไพเลอร์หลักและให้เอาต์พุตที่สอดคล้องกันในทุกแพลตฟอร์ม

Disclaimer : ฉันเป็นผู้เขียนห้องสมุดนี้


3

ประเภทที่มีประสิทธิภาพภายใต้size_t ขึ้นอยู่กับการใช้งานคือการดำเนินการขึ้นอยู่กับ C Standard กำหนดให้เป็นประเภทที่ส่งคืนโดยตัวดำเนินการ sizeof นอกเหนือจากการไม่ได้ลงนามและประเภทอินทิกรัลแล้ว size_t ยังเป็นอะไรก็ได้ที่ขนาดสามารถรองรับค่าที่ใหญ่ที่สุดที่คาดว่าจะส่งคืนโดย sizeof ()

ดังนั้นสตริงรูปแบบที่จะใช้สำหรับ size_t อาจแตกต่างกันไปขึ้นอยู่กับเซิร์ฟเวอร์ มันควรจะมีตัว "u" เสมอ แต่อาจจะเป็น l หรือ d หรืออย่างอื่นก็ได้ ...

เคล็ดลับคือการแคสต์ให้เป็นประเภทอินทิกรัลที่ใหญ่ที่สุดบนเครื่องเพื่อให้แน่ใจว่าไม่มีการสูญเสียในการแปลงจากนั้นใช้สตริงรูปแบบที่เกี่ยวข้องกับประเภทที่รู้จักนี้


ฉันจะเจ๋งหล่อของฉันsize_tเป็นประเภทอินทิกรัลที่ใหญ่ที่สุดในเครื่องและใช้สตริงรูปแบบที่เกี่ยวข้องกับประเภทนี้ คำถามของฉันคือ: มีวิธีใดบ้างที่ฉันสามารถทำได้ในขณะที่รักษารหัสที่สะอาด (คำเตือนสำหรับข้อผิดพลาดของสตริงรูปแบบ printf ที่ถูกต้องเท่านั้นไม่มีการร่ายที่น่าเกลียด ฯลฯ ) ฉันสามารถเขียน wrapper ซึ่งเปลี่ยนสตริงรูปแบบได้ แต่ GCC จะไม่สามารถให้คำเตือนฉันได้เมื่อฉันทำสตริงรูปแบบของฉันผิดปกติ
Justin L.

ใช้มาโคร 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);

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.