printf - แหล่งที่มาของข้อบกพร่อง? [ปิด]


9

ฉันใช้มากprintfสำหรับการติดตาม / การบันทึกในรหัสของฉันฉันพบว่ามันเป็นแหล่งที่มาของข้อผิดพลาดในการเขียนโปรแกรม ฉันมักจะพบตัวดำเนินการแทรก ( <<) ค่อนข้างแปลก แต่ฉันเริ่มคิดว่าการใช้มันแทนฉันสามารถหลีกเลี่ยงข้อบกพร่องบางอย่างเหล่านี้ได้

ใครเคยมีการเปิดเผยที่คล้ายกันหรือฉันแค่จับฟางที่นี่?

บางจุดนำออกไป

  • แนวความคิดปัจจุบันของฉันคือความปลอดภัยประเภทนั้นมีประโยชน์มากกว่าการใช้งาน printf ปัญหาที่แท้จริงคือสตริงรูปแบบและการใช้ฟังก์ชัน Variadic ที่ไม่ปลอดภัยต่อประเภท
  • บางทีฉันอาจจะไม่ได้ใช้<<และตัวแปรสตรีมเอาต์พุต stl แต่แน่นอนฉันจะใช้กลไกการพิมพ์ที่ปลอดภัยซึ่งคล้ายกันมาก
  • การติดตาม / การบันทึกจำนวนมากมีเงื่อนไข แต่ฉันต้องการเรียกใช้รหัสเพื่อไม่ให้พลาดข้อบกพร่องในการทดสอบเพียงเพราะมันเป็นสาขาที่ไม่ค่อยได้ใช้

4
printfในโลก C ++? ฉันพลาดอะไรบางอย่างที่นี่?
user827992

10
@ user827992: คุณพลาดข้อเท็จจริงที่ว่ามาตรฐาน C ++ รวมถึงไลบรารีมาตรฐาน C โดยอ้างอิงหรือไม่? มันถูกกฎหมายอย่างสมบูรณ์แบบที่จะใช้printfใน C ++ (ไม่ว่าจะเป็นความคิดที่ดีหรือไม่ก็เป็นคำถามอื่น)
Keith Thompson

2
@ user827992: printfมีข้อดีบางประการ เห็นคำตอบของฉัน
Keith Thompson

1
คำถามนี้สวยมาก คำถาม "พวกคุณคิดว่ายังไง" มักปิดคำถาม
dbracey

1
@ แรงฉันเดา (ขอบคุณสำหรับเคล็ดลับ) ฉันแค่สับสนนิดหน่อยจากการกลั่นกรองที่ก้าวร้าว มันไม่ได้ทำให้เกิดการสนทนาที่น่าสนใจเกี่ยวกับสถานการณ์การเขียนโปรแกรมซึ่งเป็นสิ่งที่ฉันต้องการมากขึ้น
John Leidegren

คำตอบ:


2

printf โดยเฉพาะอย่างยิ่งในกรณีที่คุณอาจสนใจเกี่ยวกับประสิทธิภาพ (เช่น sprintf และ fprintf) เป็นแฮ็คที่แปลกจริง ๆ มันทำให้ฉันประหลาดใจอย่างต่อเนื่องว่าคนที่ทุบบน C ++ เนื่องจากค่าใช้จ่ายที่น้อยลงของประสิทธิภาพการทำงานที่เกี่ยวข้องกับฟังก์ชั่นเสมือนจริงจะช่วยปกป้อง io ของ C

ใช่เพื่อที่จะหารูปแบบของผลลัพธ์ของเราบางสิ่งที่เราสามารถรู้ได้ 100% ณ เวลารวบรวมเรามาลองแยกสตริงรูปแบบที่เต็มไปด้วยรอยร้าวที่รันไทม์ภายในตารางกระโดดแปลก ๆ ขนาดใหญ่โดยใช้รหัสรูปแบบที่ไม่น่าเชื่อถือ!

แน่นอนว่าไม่สามารถสร้างรหัสรูปแบบเหล่านี้ให้ตรงกับประเภทที่เป็นตัวแทนซึ่งจะง่ายเกินไป ... และคุณจะได้รับการเตือนทุกครั้งที่คุณค้นหาไม่ว่าจะเป็น% llg หรือ% lg ที่ภาษานี้ (พิมพ์อย่างรุนแรง) ทำให้คุณ คิดประเภทด้วยตนเองเพื่อพิมพ์ / สแกนบางสิ่งบางอย่างและได้รับการออกแบบสำหรับโปรเซสเซอร์ pre-32 บิต

ฉันจะยอมรับว่าการจัดการรูปแบบความกว้างและความแม่นยำของ C ++ นั้นมีขนาดใหญ่และสามารถใช้น้ำตาลประโยคได้ แต่นั่นไม่ได้หมายความว่าคุณต้องปกป้องแฮ็กแปลก ๆ ที่เป็นระบบ io หลักของ C พื้นฐานแบบสัมบูรณ์ค่อนข้างง่ายในภาษาใดภาษาหนึ่ง (แม้ว่าคุณควรจะใช้บางอย่างเช่นฟังก์ชั่นข้อผิดพลาดที่กำหนดเอง / สตรีมข้อผิดพลาดสำหรับการแก้ไขข้อผิดพลาดโค้ด), กรณีปานกลางคือ regex เหมือนใน C (ง่ายต่อการเขียน ) และกรณีที่ซับซ้อนเป็นไปไม่ได้ใน C

(หากคุณใช้คอนเทนเนอร์มาตรฐานให้เขียนโอเปอเรเตอร์ด่วนบางตัว << โอเวอร์โหลดที่อนุญาตให้คุณทำสิ่งต่าง ๆ เช่นstd::cout << my_list << "\n";debug โดยที่ my_list เป็นประเภทlist<vector<pair<int,string> > >)


1
ปัญหาของไลบรารี่ C ++ มาตรฐานคือว่าตัวละครส่วนใหญ่จะนำไปใช้operator<<(ostream&, T)โดยการโทร ... อืม, sprintf! ประสิทธิภาพของการทำงานsprintfนั้นไม่เหมาะสม แต่ด้วยเหตุนี้ประสิทธิภาพการทำงานของ iostreams จึงแย่ลงกว่าเดิม
Jan Hudec

@JanHudec: นั่นไม่เป็นความจริงมานานประมาณสิบปี ณ จุดนี้ การพิมพ์จริงจะดำเนินการด้วยการเรียกใช้ระบบพื้นฐานเดียวกันและการประยุกต์ใช้ C ++ มักจะเรียกใช้ไลบรารี C สำหรับสิ่งนั้น ... แต่นั่นไม่ใช่สิ่งเดียวกันกับการกำหนดเส้นทาง std :: cout ผ่าน printf
jkerian

16

การผสมเอาต์พุตC-style printf()(หรือputs()หรือputchar()หรือ ... ) กับ C ++ - std::cout << ...เอาต์พุตสไตล์ไม่ปลอดภัย หากฉันจำอย่างถูกต้องพวกเขาสามารถมีกลไกการบัฟเฟอร์แยกกันดังนั้นผลลัพธ์อาจไม่ปรากฏตามลำดับที่ต้องการ (ตามที่ AProgrammer กล่าวถึงในความคิดเห็นให้sync_with_stdioระบุสิ่งนี้)

printf()เป็นประเภทที่ไม่ปลอดภัยขั้นพื้นฐาน ประเภทที่คาดไว้สำหรับอาร์กิวเมนต์จะถูกกำหนดโดยสตริงรูปแบบ ( "%d"จำเป็นต้องใช้intหรือสิ่งที่ส่งเสริมการint, "%s"ต้องใช้char*ซึ่งจะต้องชี้ไปที่สตริง C-สไตล์ยกเลิกอย่างถูกต้อง ฯลฯ ) แต่ผ่านผิดประเภทของผลการโต้แย้งในพฤติกรรมที่ไม่ได้กำหนด ไม่ใช่ข้อผิดพลาดที่สามารถวินิจฉัยได้ คอมไพเลอร์บางตัวเช่น gcc ทำงานได้ดีพอสมควรในการเตือนเกี่ยวกับความไม่ตรงกันของประเภท แต่พวกเขาสามารถทำได้เฉพาะในกรณีที่รูปแบบสตริงเป็นตัวอักษรหรือเป็นที่รู้จักกันในเวลารวบรวม (ซึ่งเป็นกรณีที่พบบ่อยที่สุด) - และเช่น คำเตือนไม่จำเป็นต้องใช้ภาษา หากคุณผ่านประเภทของการโต้แย้งที่ไม่ถูกต้องสิ่งเลวร้ายโดยพลการสามารถเกิดขึ้นได้

ในทางตรงกันข้ามกระแส I / O ของ C ++ นั้นปลอดภัยกว่าประเภทมากเนื่องจากตัว<<ดำเนินการโอเวอร์โหลดสำหรับประเภทที่แตกต่างกันมากมาย std::cout << xไม่ต้องระบุประเภทของx; คอมไพเลอร์จะสร้างรหัสที่เหมาะสมสำหรับสิ่งที่ประเภทxมี

ในทางกลับกันprintfตัวเลือกการจัดรูปแบบของ IMHO สะดวกกว่ามาก หากฉันต้องการพิมพ์ค่าเลขทศนิยมด้วยตัวเลข 3 หลักหลังจุดทศนิยมฉันสามารถใช้"%.3f"- และไม่มีผลกับอาร์กิวเมนต์อื่นแม้ในการprintfโทรเดียวกัน ในทางกลับกัน C ++ setprecisionนั้นมีผลกระทบต่อสถานะของกระแสข้อมูลและสามารถทำให้เกิดความสับสนในภายหลังหากคุณไม่ระมัดระวังในการคืนค่ากระแสข้อมูลให้เป็นสถานะก่อนหน้า (นี่คือสัตว์เลี้ยงโกรธส่วนตัวของฉันถ้าฉันไม่มีวิธีทำความสะอาดเพื่อหลีกเลี่ยงโปรดแสดงความคิดเห็น)

ทั้งสองมีข้อดีและข้อเสีย ความพร้อมใช้งานของprintfมีประโยชน์อย่างยิ่งหากคุณมีพื้นหลัง C และคุณคุ้นเคยกับมันมากขึ้นหรือถ้าคุณนำเข้ารหัสต้นฉบับ C ลงในโปรแกรม C ++ std::cout << ...มีความหมายมากกว่าสำหรับ C ++ และไม่ต้องใช้ความระมัดระวังมากนักในการหลีกเลี่ยงประเภทที่ไม่ตรงกัน ทั้งสองเป็น C ++ ที่ถูกต้อง (มาตรฐาน C ++ รวมถึงไลบรารีมาตรฐาน C ส่วนใหญ่โดยการอ้างอิง)

มันอาจจะดีที่สุดที่จะใช้std::cout << ...เพื่อประโยชน์ในการเขียนโปรแกรมภาษา C ++ อื่น ๆ ที่อาจทำงานในรหัสของคุณ แต่คุณสามารถใช้อย่างใดอย่างหนึ่ง - โดยเฉพาะอย่างยิ่งในรหัสร่องรอยว่าคุณกำลังจะโยนออกไป

และแน่นอนว่ามันคุ้มค่าที่จะใช้เวลาในการเรียนรู้วิธีใช้ debuggers (แต่นั่นอาจไม่เป็นไปได้ในบางสภาพแวดล้อม)


ไม่มีการพูดถึงการมั่วสุมในคำถามดั้งเดิม
dbracey

1
@dbracey: ไม่มี printfแต่ฉันคิดว่ามันเป็นมูลค่าการกล่าวขวัญว่าเป็นอุปสรรคที่เป็นไปได้ของ
Keith Thompson

6
std::ios_base::sync_with_stdioสำหรับปัญหาการประสานให้ดู
AProgrammer

1
+1 การใช้ std :: cout เพื่อพิมพ์ข้อมูลการดีบักในแอพแบบมัลติเธรดนั้นไม่มีประโยชน์ 100% อย่างน้อยที่สุดกับสิ่งที่พิมพ์ไม่น่าจะเป็น interleaved และ unparsable โดยคนหรือเครื่องจักร
James

@James: นั่นเป็นเพราะstd::coutใช้การโทรแยกต่างหากสำหรับแต่ละรายการที่พิมพ์หรือไม่ คุณสามารถหลีกเลี่ยงปัญหานั้นได้ด้วยการรวบรวมบรรทัดของเอาต์พุตลงในสตริงก่อนที่จะพิมพ์ และแน่นอนคุณยังสามารถพิมพ์ครั้งละหนึ่งรายการด้วยprintf; สะดวกกว่าในการพิมพ์บรรทัด (หรือมากกว่า) ในการโทรครั้งเดียว
Keith Thompson

2

ปัญหาของคุณน่าจะมาจากการผสมตัวจัดการเอาต์พุตมาตรฐานสองตัวที่ต่างกันซึ่งแต่ละอันมีวาระของตัวเองสำหรับ STDOUT ตัวเล็กที่น่าสงสาร คุณจะได้รับการค้ำประกันเกี่ยวกับวิธีที่พวกเขาจะดำเนินการไม่ได้และก็เป็นไปได้อย่างสมบูรณ์แบบที่พวกเขาตั้งค่าตัวเลือกที่ขัดแย้งกันไฟล์อธิบายทั้งสองพยายามที่จะทำสิ่งที่แตกต่างกันไป ฯลฯ นอกจากนี้ผู้ประกอบการแทรกมีมากกว่าหนึ่งที่สำคัญprintf: printfจะช่วยให้คุณทำเช่นนี้:

printf("%d", SomeObject);

ในขณะที่<<จะไม่

หมายเหตุ: สำหรับการแก้จุดบกพร่องคุณไม่ได้ใช้หรือprintf coutคุณสามารถใช้และfprintf(stderr, ...)cerr


ไม่มีการพูดถึงการมั่วสุมในคำถามดั้งเดิม
dbracey

แน่นอนคุณสามารถพิมพ์ที่อยู่ของวัตถุ แต่แตกต่างใหญ่คือไม่ได้พิมพ์ปลอดภัยและฉันบรรทัดปัจจุบันของการคิดคือประเภทความปลอดภัยเมื่อเทียบกับประโยชน์ของการใช้ใดprintfprintfปัญหาคือสตริงการจัดรูปแบบและฟังก์ชัน Variadic ที่ไม่ปลอดภัยต่อประเภท
John Leidegren

@JohnLeidegren: แต่ถ้าSomeObjectไม่ใช่ตัวชี้ล่ะ? SomeObjectคุณจะได้รับข้อมูลที่พลไบนารีที่คอมไพเลอร์ตัดสินใจที่แสดงให้เห็นถึง
Linuxios

ฉันคิดว่าฉันอ่านคำตอบของคุณไปข้างหลัง ... nvm
John Leidegren

1

มีหลายกลุ่มเช่น google ซึ่งไม่ชอบสตรีม

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(ป๊อปเปิดสามเหลี่ยมสิ่งเพื่อให้คุณสามารถดูการสนทนา) ฉันคิดว่าคู่มือสไตล์ Google C ++ มีคำแนะนำที่เหมาะสมมาก

ฉันคิดว่าข้อเสียคือกระแสที่ปลอดภัยกว่า แต่ printf นั้นอ่านง่ายกว่า (และง่ายกว่าที่จะจัดรูปแบบตามที่คุณต้องการ)


2
คู่มือสไตล์ Google นั้นดี แต่ก็มีบางรายการที่ไม่เหมาะกับคำแนะนำทั่วไป (ซึ่งก็โอเคเพราะหลังจากทั้งหมดเป็นคู่มือของ Google สำหรับรหัสทำงานที่ / สำหรับ Google.)
Martin Ba

1

printfอาจทำให้เกิดข้อบกพร่องเนื่องจากการขาดความปลอดภัยประเภท มีไม่กี่วิธีของการแก้ไขปัญหาว่าไม่มีการเปลี่ยนไปเป็นiostreamของ<<ผู้ประกอบการและมีความซับซ้อนมากขึ้นการจัดรูปแบบ:

  • คอมไพเลอร์บางตัว (เช่น GCC และ Clang) สามารถเลือกที่จะตรวจสอบprintfสตริงรูปแบบของคุณกับprintfข้อโต้แย้งและสามารถแสดงคำเตือนเช่นต่อไปนี้หากไม่ตรงกัน
    คำเตือน: การแปลงระบุประเภท 'int' แต่อาร์กิวเมนต์มีประเภท 'char *'
  • typesafeprintfสคริปต์สามารถ preprocess ของคุณprintfโทรสไตล์ที่จะทำให้พวกเขาชนิดปลอดภัย
  • ไลบรารี่เช่นBoost.FormatและFastFormatให้คุณใช้printfสตริงรูปแบบที่คล้ายกัน (โดยเฉพาะอย่างยิ่ง Boost.Format นั้นเกือบจะเหมือนกันprintf) ในขณะที่ยังรักษาiostreamsความปลอดภัยของประเภทและความสามารถในการขยายประเภท

1

ไวยากรณ์ Printf นั้นใช้งานได้ดีลบด้วยการพิมพ์ที่ไม่ชัดเจน ถ้าคุณคิดว่ามันผิดทำไม C #, Python และภาษาอื่น ๆ ใช้โครงสร้างที่คล้ายกันมาก? ปัญหาใน C หรือ C ++: ไม่ได้เป็นส่วนหนึ่งของภาษาจึงไม่ได้ตรวจสอบโดยคอมไพเลอร์เพื่อหาไวยากรณ์ที่ถูกต้อง (*) และไม่แยกย่อยเป็นชุดของการโทรแบบเนทีฟหากปรับความเร็วให้เหมาะสม โปรดทราบว่าหากปรับขนาดให้เหมาะสมการเรียกใช้ printf อาจมีประสิทธิภาพมากขึ้น! ไวยากรณ์สตรีมมิ่ง C ++ นั้นไม่มีอะไรนอกจากดี มันใช้งานได้ความปลอดภัยของประเภทอยู่ที่นั่น แต่ไวยากรณ์ verbose ... bleh ฉันหมายถึงฉันใช้มัน แต่ไม่มีความสุข

(*) คอมไพเลอร์บางตัวทำการตรวจสอบนี้บวกกับเครื่องมือวิเคราะห์แบบสแตติกเกือบทั้งหมด (ฉันใช้ Lint และไม่เคยมีปัญหาใด ๆ กับ printf ตั้งแต่)


1
มีBoost.Formatที่รวมไวยากรณ์ที่สะดวก ( format("fmt") % arg1 % arg2 ...;) กับประเภทความปลอดภัย ด้วยต้นทุนของประสิทธิภาพที่เพิ่มขึ้นเนื่องจากมันจะสร้างการโทรแบบสตริงสตรีมที่จะสร้างการโทรแบบ sprintf ภายในในการใช้งานหลายอย่าง
Jan Hudec

0

printfในความเห็นของฉันเองเครื่องมือส่งออกที่มีความยืดหยุ่นมากกว่าสำหรับจัดการกับตัวแปรต่าง ๆ กว่าเอาต์พุตสตรีม CPP ใด ๆ ตัวอย่างเช่น:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

อย่างไรก็ตามที่ที่คุณอาจต้องการใช้ตัว<<ดำเนินการCPP คือเมื่อคุณใช้งานมากเกินไปสำหรับวิธีการเฉพาะ ... ตัวอย่างเช่นเพื่อรับการถ่ายโอนข้อมูลของวัตถุที่เก็บข้อมูลของบุคคลโดยเฉพาะ, PersonData....

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

เพื่อที่จะมีประสิทธิภาพมากขึ้นที่จะพูด (สมมติว่าaเป็นวัตถุของ PersonData)

std::cout << a;

กว่า:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

อดีตนั้นสอดคล้องกับหลักการของการห่อหุ้มมากขึ้น (ไม่จำเป็นต้องรู้ข้อมูลเฉพาะตัวแปรสมาชิกส่วนตัว) และอ่านง่ายขึ้น


0

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

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

cout << "String '" << str << "' has " << str.size() << " characters\n";

ตอนนี้นักแปลชาวเยอรมันเข้ามาและพูดว่า: โอเคในเยอรมันข้อความควรจะเป็น

n Zeichen lang ist die Zeichenkette ' s '

และตอนนี้คุณกำลังมีปัญหาเพราะเขาต้องการชิ้นสับรอบ มันควรจะกล่าวว่าแม้การดำเนินการจำนวนมากprintfมีปัญหากับเรื่องนี้ เว้นแต่พวกเขาสนับสนุนส่วนขยายเพื่อให้คุณสามารถใช้

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

boost.formatสนับสนุนรูปแบบ printf สไตล์และมีคุณลักษณะนี้ ดังนั้นคุณเขียน:

cout << format("String '%1' has %2 characters\n") % str % str.size();

แต่น่าเสียดายที่จะดำเนินการบิตของการลงโทษประสิทธิภาพเพราะภายในมันจะสร้าง stringstream และใช้<<ประกอบการจัดรูปแบบแต่ละบิตและในการดำเนินการหลายผู้ประกอบการภายในเรียก<< sprintfฉันสงสัยว่าการติดตั้งจะมีประสิทธิภาพมากขึ้นถ้าเป็นไปได้


-1

คุณกำลังทำงานที่ไร้ประโยชน์มากมายนอกเหนือจากข้อเท็จจริงที่ว่าstlเป็นความชั่วร้ายหรือไม่ดีบั๊กโค้ดของคุณด้วยชุดข้อมูลที่printfเพิ่มความล้มเหลวที่อาจเกิดขึ้นได้อีก 1 ระดับเท่านั้น

เพียงใช้เครื่องมือดีบั๊กเกอร์และอ่านบางอย่างเกี่ยวกับข้อยกเว้นและวิธีจับและโยนทิ้ง พยายามไม่ให้ละเอียดมากกว่าที่คุณต้องการ

PS

printf ใช้ใน C สำหรับ C ++ ที่คุณมี std::cout


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