เหตุผลของการยกเลิกสตริงเป็นโมฆะคืออะไร?


281

เท่าที่ฉันรัก C และ C ++ ฉันไม่สามารถช่วย แต่เกาหัวของฉันในการเลือกสตริงที่สิ้นสุดด้วยค่า null:

  • มีความยาวนำหน้าสตริง (เช่น Pascal) ที่มีอยู่ก่อนหน้า C
  • สตริงที่มีคำนำหน้าความยาวทำให้อัลกอริทึมหลายอย่างเร็วขึ้นโดยอนุญาตให้ค้นหาความยาวของเวลาคงที่
  • สตริงที่มีคำนำหน้าความยาวทำให้ยากต่อการทำให้เกิดข้อผิดพลาดบัฟเฟอร์เกิน
  • แม้บนเครื่อง 32 บิตหากคุณอนุญาตให้สตริงเป็นขนาดของหน่วยความจำที่มีอยู่สตริงที่ขึ้นต้นความยาวจะมีความกว้างเพียงสามไบต์กว้างกว่าสตริงที่สิ้นสุดด้วยค่า null บนเครื่อง 16 บิตนี่เป็นไบต์เดียว บนเครื่อง 64 บิต 4GB เป็นขีดจำกัดความยาวของสตริงที่เหมาะสม แต่แม้ว่าคุณต้องการขยายให้มีขนาดของคำว่าเครื่องเครื่อง 64 บิตมักจะมีหน่วยความจำเพียงพอที่จะทำให้อาร์กิวเมนต์ที่เจ็ดมีค่าเพิ่มขึ้นอีกเจ็ดไบต์ ฉันรู้ว่ามาตรฐาน C ดั้งเดิมเขียนขึ้นสำหรับเครื่องที่ไม่ดีอย่างบ้าคลั่ง (ในแง่ของหน่วยความจำ) แต่อาร์กิวเมนต์ประสิทธิภาพไม่ได้ขายที่นี่
  • ค่อนข้างทุกภาษาอื่น ๆ (เช่น Perl, Pascal, Python, Java, C #, ฯลฯ ) ใช้สตริงคำนำหน้าความยาว ภาษาเหล่านี้มักจะเอาชนะ C ในมาตรฐานการจัดการสตริงเพราะพวกเขามีประสิทธิภาพมากขึ้นด้วยสตริง
  • C ++ แก้ไขบิตนี้ด้วยstd::basic_stringเทมเพลต แต่อาร์เรย์อักขระธรรมดาคาดว่าสตริงที่สิ้นสุดด้วยค่า null จะยังคงแพร่หลาย สิ่งนี้ยังไม่สมบูรณ์เนื่องจากต้องการการจัดสรรฮีป
  • สตริงที่สิ้นสุดแล้ว Null ต้องสำรองอักขระ (กล่าวคือ null) ซึ่งไม่มีอยู่ในสตริงในขณะที่สตริงที่ขึ้นต้นด้วยความยาวสามารถมี null ที่ฝังตัวได้

สิ่งต่าง ๆ เหล่านี้มีแสงสว่างเมื่อเร็ว ๆ นี้มากกว่า C ดังนั้นจึงเหมาะสมที่ C ที่จะไม่รู้จัก อย่างไรก็ตามมีหลายคนที่เรียบง่ายก่อนที่ C จะมา เหตุใดจึงต้องเลือกสตริงที่สิ้นสุดด้วยค่า null แทนที่จะเป็นส่วนนำหน้ายาวที่เหนือกว่าอย่างเห็นได้ชัด

แก้ไข : เนื่องจากบางคนถามข้อเท็จจริง (และไม่ชอบสิ่งที่ฉันให้ไว้) ในจุดประสิทธิภาพของฉันด้านบนพวกเขามาจากบางสิ่ง:

  • การเชื่อมต่อโดยใช้สตริงที่สิ้นสุดด้วยค่า Null ต้องใช้ความซับซ้อนของเวลา O (n + m) ความยาวของคำนำหน้ามักใช้เพียง O (m)
  • ความยาวโดยใช้สตริงที่สิ้นสุดด้วยค่า Null ต้องใช้ความซับซ้อนของเวลา O (n) ความยาวส่วนนำหน้าคือ O (1)
  • ความยาวและการต่อกันนั้นเป็นการดำเนินการของสตริงที่พบได้บ่อยที่สุด มีหลายกรณีที่สตริงที่สิ้นสุดด้วยค่า null จะมีประสิทธิภาพมากกว่า แต่เกิดขึ้นน้อยกว่ามาก

จากคำตอบด้านล่างนี่คือบางกรณีที่สตริงที่สิ้นสุดด้วยค่า null จะมีประสิทธิภาพมากกว่า:

  • เมื่อคุณต้องการตัดจุดเริ่มต้นของสตริงและต้องผ่านไปยังวิธีการบางอย่าง คุณไม่สามารถทำสิ่งนี้ได้ในเวลาที่แน่นอนด้วยการขึ้นต้นความยาวแม้ว่าคุณจะได้รับอนุญาตให้ทำลายสตริงเดิมเพราะคำนำหน้าความยาวอาจต้องปฏิบัติตามกฎการจัดตำแหน่ง
  • ในบางกรณีที่คุณเพิ่งวนลูปผ่านตัวอักษรสตริงโดยตัวละครคุณอาจจะสามารถบันทึกการลงทะเบียน CPU โปรดทราบว่าวิธีนี้ใช้งานได้เฉพาะในกรณีที่คุณไม่ได้จัดสรรสตริงแบบไดนามิก (เพราะคุณจะต้องปล่อยให้เป็นอิสระโดยจำเป็นต้องใช้ CPU ที่คุณบันทึกไว้เพื่อบันทึกตัวชี้ที่คุณได้รับจาก malloc และเพื่อน ๆ )

ไม่ตรงกับความยาวและความยาวเลย

มีอีกหนึ่งคำตอบที่ยืนยันด้านล่าง:

  • คุณต้องตัดส่วนท้ายของสตริง

แต่อันนี้ไม่ถูกต้อง - มันเป็นจำนวนเวลาเท่ากันสำหรับการยกเลิก null และสตริงที่มีคำนำหน้ายาว (สตริงที่สิ้นสุดแล้วจะเป็นโมฆะแค่ติดค่า null ที่คุณต้องการให้ปลายใหม่เป็นส่วนนำหน้าความยาวเพียงลบออกจากคำนำหน้า)


110
ฉันมักจะคิดว่ามันเป็นพิธีทางให้โปรแกรมเมอร์ C ++ ทุกคนเขียนไลบรารีสตริงของตนเอง
Juliet

31
สิ่งนี้เกี่ยวกับการคาดหวังคำอธิบายเหตุผล ฉันคิดว่าคุณต้องการได้ยินเหตุผลสำหรับ x86 หรือ DOS ต่อไปหรือไม่ เท่าที่ฉันกังวลเทคโนโลยีที่เลวร้ายที่สุดชนะ ทุกเวลา. และการเป็นตัวแทนสตริงที่เลวร้ายที่สุด
jalf

4
เหตุใดคุณจึงอ้างสิทธิ์สตริงคำนำหน้าความยาวดีกว่า ท้ายที่สุด C เริ่มได้รับความนิยมเนื่องจากใช้สตริงที่สิ้นสุดด้วยค่า null ซึ่งทำให้แตกต่างจากภาษาอื่น
Daniel C. Sobral

44
@ Daniel: C กลายเป็นที่นิยมเพราะมันเป็นเรื่องง่ายที่มีประสิทธิภาพและการเป็นตัวแทนแบบพกพาของโปรแกรมปฏิบัติการบนเครื่อง Von Neumann และเพราะมันถูกนำมาใช้สำหรับ Unix แน่นอนไม่ใช่เพราะตัดสินใจใช้สตริงที่สิ้นสุดด้วยค่า null ถ้าเป็นการตัดสินใจออกแบบที่ดีผู้คนจะลอกเลียนแบบและพวกเขาก็ไม่ได้ พวกเขาได้คัดลอกแน่นอนทุกอย่างสวยมากอื่นจากซี
บิลลี่ ONeal

4
Concat เป็นเพียง O (m) ที่มีคำนำหน้ายาวถ้าคุณทำลายหนึ่งในสตริง มิฉะนั้นความเร็วเดียวกัน การใช้งานทั่วไปของสตริง C (ในอดีต) คือการพิมพ์และการสแกน ในทั้งสองสิ่งนี้การยกเลิกค่า null จะเร็วขึ้นเพราะจะบันทึกการลงทะเบียนหนึ่งรายการ
Daniel C. Sobral

คำตอบ:


195

จากปากม้า

BCPL, B หรือ C ไม่รองรับข้อมูลตัวละครในภาษาอย่างมาก แต่ละคนถือว่าสตริงเหมือนเวกเตอร์ของจำนวนเต็มและกฎทั่วไปเพิ่มเติมโดยการประชุมไม่กี่ ใน BCPL และ B สตริงตัวอักษรหมายถึงที่อยู่ของพื้นที่คงที่เริ่มต้นด้วยตัวอักษรของสตริงที่บรรจุในเซลล์ ใน BCPL ไบต์ที่บีบอัดแรกประกอบด้วยจำนวนอักขระในสตริง ใน B มีนับไม่และสตริงจะสิ้นสุดด้วยตัวอักษรพิเศษซึ่ง B *eสะกด การเปลี่ยนแปลงนี้ถูกทำขึ้นบางส่วนเพื่อหลีกเลี่ยงข้อ จำกัด เกี่ยวกับความยาวของสตริงที่เกิดจากการนับจำนวนในสล็อต 8- หรือ 9 บิตและส่วนหนึ่งเป็นเพราะการรักษาจำนวนดูเหมือนว่าในประสบการณ์ของเราสะดวกกว่าการใช้เทอร์มิเนเตอร์

Dennis M Ritchie การพัฒนาภาษา C


12
อีกคำพูดที่เกี่ยวข้อง: "... ความหมายของสตริงที่มีวิทยอย่างเต็มที่ตามกฎทั่วไปมากขึ้นปกครองอาร์เรย์ทั้งหมดและเป็นผลให้ภาษาที่ง่ายที่จะอธิบาย ..."
AShelly

151

C ไม่มีสตริงเป็นส่วนหนึ่งของภาษา 'สตริง' ใน C เป็นเพียงตัวชี้เพื่อถ่าน ดังนั้นคุณอาจถามคำถามผิด

"อะไรคือเหตุผลในการออกจากประเภทสตริง" อาจมีความเกี่ยวข้องมากกว่า เพื่อที่ฉันจะชี้ให้เห็นว่า C ไม่ใช่ภาษาเชิงวัตถุและมีเพียงประเภทค่าพื้นฐาน สตริงเป็นแนวคิดระดับที่สูงขึ้นซึ่งจะต้องมีการดำเนินการโดยวิธีการรวมค่าประเภทอื่น ๆ C อยู่ในระดับที่ต่ำกว่าของนามธรรม

ในแง่ของความโกลาหลพายุด้านล่าง:

ฉันแค่ต้องการชี้ให้เห็นว่าฉันไม่ได้พยายามที่จะพูดว่านี่เป็นคำถามที่โง่หรือไม่ดีหรือวิธีการแสดงสตริง C เป็นตัวเลือกที่ดีที่สุด ฉันพยายามที่จะชี้แจงว่าคำถามจะชัดเจนขึ้นถ้าคุณคำนึงถึงความจริงที่ว่า C ไม่มีกลไกในการแยกสตริงเป็นประเภทข้อมูลจากอาร์เรย์ไบต์ นี่เป็นตัวเลือกที่ดีที่สุดสำหรับการประมวลผลและพลังหน่วยความจำของคอมพิวเตอร์ในปัจจุบันหรือไม่? อาจจะไม่. แต่ปัญหาหลังเหตุการณ์อยู่เสมอ 20/20 และทั้งหมดนั้น :)


29
char *temp = "foo bar";เป็นคำสั่งที่ถูกต้องใน C ... เฮ้! ไม่ได้เป็นสตริง? มันจะไม่สิ้นสุดหรือเปล่า
Yanick Rochon

56
@ Yanick: นั่นเป็นวิธีที่สะดวกในการบอกคอมไพเลอร์เพื่อสร้างอาร์เรย์ของ char โดยมีค่า null ตอนท้าย ไม่ใช่ 'string'
Robert S Ciaccio

28
@calavera: แต่มันอาจจะเป็นแค่เพียงความหมาย "สร้างหน่วยความจำบัฟเฟอร์ที่มีเนื้อหาสตริงนี้และมีความยาวสองไบต์คำนำหน้า"
บิลลี่ ONeal

14
@Billy: ดีเนื่องจาก 'string' เป็นเพียงตัวชี้ไปยัง char ซึ่งเทียบเท่ากับตัวชี้ไปยัง byte คุณจะรู้ได้อย่างไรว่าบัฟเฟอร์ที่คุณจัดการนั้นมีจุดประสงค์เพื่อให้เป็น 'string' คุณจะต้องมีประเภทใหม่นอกเหนือจากถ่าน / ไบต์ * เพื่อแสดงถึงสิ่งนี้ อาจจะเป็น struct หรือไม่?
โรเบิร์ตเอ Ciaccio

27
ผมคิดว่าเป็นสิทธิ @calavera, C ไม่ได้มีชนิดข้อมูลสำหรับสตริง ตกลงคุณสามารถพิจารณาอาร์เรย์ของตัวอักษรเหมือนสตริงได้ แต่นี่ไม่ได้หมายความว่ามันจะเป็นสตริงเสมอ (สำหรับสตริงฉันหมายถึงลำดับของอักขระที่มีความหมายชัดเจน) ไฟล์ไบนารีเป็นอาร์เรย์ของตัวอักษร แต่ตัวอักษรเหล่านั้นไม่ได้มีความหมายอะไรสำหรับมนุษย์
BlackBear

106

คำถามที่ถามเป็นLength Prefixed Strings (LPS)เทียบกับzero terminated strings (SZ)สิ่งที่ แต่ส่วนใหญ่เปิดเผยผลประโยชน์ของความยาวนำหน้าสตริง ที่อาจดูเหมือนล้นหลาม แต่ตามจริงแล้วเราควรพิจารณาข้อเสียของ LPS และข้อดีของ SZ

ตามที่ฉันเข้าใจแล้วคำถามอาจถูกเข้าใจว่าเป็นวิธีที่มีอคติที่จะถามว่า "อะไรคือข้อดีของ Zero Terminated Strings?"

ข้อดี (เห็น) ของ Zero Terminated Strings:

  • ง่ายมากไม่จำเป็นต้องแนะนำแนวคิดใหม่ในภาษาตัวชี้คำสั่ง char / char char สามารถทำได้
  • ภาษาหลักเพียงแค่ใส่น้ำตาลไวยากรณ์น้อยที่สุดเพื่อแปลงบางสิ่งระหว่างเครื่องหมายคำพูดคู่ไปเป็นกลุ่มตัวอักษร (จริงๆเป็นจำนวนไบต์) ในบางกรณีสามารถใช้เพื่อเริ่มต้นสิ่งต่าง ๆ ที่ไม่เกี่ยวข้องกับข้อความได้อย่างสมบูรณ์ ตัวอย่างเช่นรูปแบบไฟล์รูปภาพ xpm เป็นแหล่งข้อมูล C ที่ถูกต้องซึ่งมีข้อมูลรูปภาพที่เข้ารหัสเป็นสตริง
  • โดยวิธีการที่คุณสามารถ"this\0is\0valid\0C"ใส่ศูนย์ในตัวอักษรสตริงคอมไพเลอร์ก็จะยังเพิ่มอีกคนหนึ่งในตอนท้ายของตัวอักษรนี้: มันเป็นสตริงหรือไม่? หรือสี่สาย? หรือพวงของไบต์ ...
  • การใช้แบนไม่มีการซ่อนเร้นไม่มีจำนวนเต็ม
  • ไม่มีการจัดสรรหน่วยความจำที่ซ่อนอยู่ (เช่นกันฟังก์ชั่นที่ไม่ได้มาตรฐานบางอย่างเช่น strdup ทำการจัดสรร แต่ส่วนใหญ่เป็นสาเหตุของปัญหา)
  • ไม่มีปัญหาเฉพาะสำหรับฮาร์ดแวร์ขนาดเล็กหรือใหญ่ (ลองนึกถึงภาระในการจัดการความยาว 32 บิตบนไมโครคอนโทรลเลอร์ 8 บิตหรือข้อ จำกัด ในการ จำกัด ขนาดสตริงให้เหลือน้อยกว่า 256 ไบต์นั่นเป็นปัญหาที่ฉันเคยใช้กับ Turbo Pascal eons มาก่อน)
  • การดำเนินงานของการจัดการสตริงเป็นเพียงไม่กี่คนของฟังก์ชั่นห้องสมุดง่ายมาก
  • มีประสิทธิภาพสำหรับการใช้งานหลักของสตริง: ข้อความคงที่อ่านตามลำดับจากจุดเริ่มต้นที่รู้จัก
  • การยกเลิกศูนย์นั้นไม่จำเป็นต้องใช้แม้แต่เครื่องมือที่จำเป็นทั้งหมดในการจัดการกับตัวอักษรเช่นจำนวนไบต์ เมื่อทำการเริ่มต้นอาร์เรย์ใน C คุณสามารถหลีกเลี่ยง NUL terminator ได้ เพียงแค่ตั้งค่าขนาดที่เหมาะสมchar a[3] = "foo";คือ C ที่ถูกต้อง (ไม่ C ++) และจะไม่ใส่ศูนย์สุดท้ายใน
  • เชื่อมโยงกับมุมมอง unix "ทุกอย่างคือไฟล์" รวมถึง "ไฟล์" ที่ไม่มีความยาวที่แท้จริงเช่น stdin, stdout คุณควรจำไว้ว่าการอ่านและเขียนแบบดั้งเดิมเปิดใช้งานในระดับที่ต่ำมาก ไม่ใช่การเรียกใช้ไลบรารี แต่เป็นการเรียกระบบ และ API เดียวกันนั้นใช้สำหรับไบนารีหรือไฟล์ข้อความ อ่านวิทยาการไฟล์ได้รับที่อยู่บัฟเฟอร์และขนาดและกลับขนาดใหม่ และคุณสามารถใช้สตริงเป็นบัฟเฟอร์ในการเขียน การใช้การแทนค่าสายอักขระชนิดอื่นจะแปลว่าคุณไม่สามารถใช้สตริงตัวอักษรเป็นบัฟเฟอร์ในการส่งออกได้อย่างง่ายดายหรือคุณต้องทำให้มันมีพฤติกรรมที่แปลกมากเมื่อส่งไปที่char*ใช้ชนิดของการแสดงสตริงอื่นจะบ่งบอกว่าคุณไม่สามารถใช้สตริงตัวอักษรเป็นกันชนเพื่อการส่งออกหรือคุณจะต้องทำให้มันมีพฤติกรรมแปลกมากเมื่อหล่อมันคือไม่ส่งคืนที่อยู่ของสตริง แต่แทนที่จะส่งคืนข้อมูลจริง
  • มากง่ายต่อการจัดการข้อมูลที่เป็นข้อความอ่านจากแฟ้มในสถานที่โดยไม่ต้องคัดลอกไร้ประโยชน์ของบัฟเฟอร์เพียงแค่ใส่เลขที่สถานที่ที่เหมาะสม (ดีไม่ได้จริงๆกับ C ที่ทันสมัยเป็นสตริงที่ยกมาคู่อาร์เรย์ถ่าน const ปัจจุบันมักจะเก็บไว้ในข้อมูลที่ไม่สามารถแก้ไขได้ ส่วน)
  • การเตรียมค่า int บางอย่างที่มีขนาดใดก็ตามจะแสดงถึงปัญหาการจัดตำแหน่ง ความยาวเริ่มต้นควรจะจัดตำแหน่ง แต่ไม่มีเหตุผลที่จะทำเช่นนั้นสำหรับตัวละครข้อมูล (และอีกครั้งการบังคับให้จัดตำแหน่งของสตริงจะบ่งบอกถึงปัญหาเมื่อปฏิบัติต่อพวกเขาเป็นพวงของไบต์)
  • ความยาวเป็นที่รู้จักกันในเวลารวบรวมสำหรับสตริงตัวอักษรคงที่ (ขนาดของ) เหตุใดใครจึงต้องการเก็บไว้ในหน่วยความจำที่เตรียมไว้กับข้อมูลจริง
  • ในทางที่ C ทำเหมือน (เกือบ) ทุกคนอื่น ๆ สตริงจะถูกมองว่าเป็นอาร์เรย์ของถ่าน เนื่องจากความยาวของอาเรย์ไม่ได้รับการจัดการโดย C จึงเป็นความยาวแบบลอจิคัลที่ไม่ถูกจัดการสำหรับสตริง สิ่งที่น่าประหลาดใจเพียงอย่างเดียวคือเพิ่ม 0 รายการในตอนท้าย แต่นั่นเป็นเพียงระดับภาษาหลักเมื่อพิมพ์สตริงระหว่างเครื่องหมายคำพูดคู่ ผู้ใช้สามารถเรียกฟังก์ชั่นการจัดการสตริงอย่างสมบูรณ์แบบผ่านความยาวหรือแม้กระทั่งใช้ memcopy ธรรมดาแทน SZ เป็นเพียงสิ่งอำนวยความสะดวก ในความยาวอาร์เรย์ภาษาอื่น ๆ ส่วนใหญ่มีการจัดการมันเป็นตรรกะที่เหมือนกันสำหรับสตริง
  • ในยุคปัจจุบันอย่างไรก็ตามชุดอักขระ 1 ไบต์ไม่เพียงพอและบ่อยครั้งที่คุณต้องจัดการกับสตริง Unicode ที่เข้ารหัสซึ่งจำนวนอักขระนั้นแตกต่างกันมากกับจำนวนไบต์ หมายความว่าผู้ใช้อาจต้องการมากกว่า "แค่ขนาด" แต่ยังมีข้อมูลอื่น ๆ การรักษาความยาวไม่ใช้สิ่งใด (โดยเฉพาะอย่างยิ่งไม่มีที่เก็บของธรรมดา) เกี่ยวกับข้อมูลที่เป็นประโยชน์อื่น ๆ เหล่านี้

ที่กล่าวว่าไม่จำเป็นต้องบ่นในกรณีที่หายากที่สตริง C มาตรฐานไม่มีประสิทธิภาพแน่นอน Libs พร้อมใช้งาน ถ้าฉันทำตามแนวโน้มนั้นฉันควรจะบ่นว่ามาตรฐาน C ไม่ได้รวมฟังก์ชั่นการสนับสนุน regex ... แต่จริงๆแล้วทุกคนรู้ว่ามันไม่ใช่ปัญหาจริงเพราะมีห้องสมุดที่มีให้สำหรับจุดประสงค์นั้น ดังนั้นเมื่อมีประสิทธิภาพการจัดการสตริงเป็นที่ต้องการทำไมไม่ใช้ห้องสมุดเช่นbstring ? หรือแม้แต่สตริง C ++?

แก้ไข : ฉันเพิ่งได้ดูไปD สตริง เป็นเรื่องที่น่าสนใจพอที่จะเห็นว่าโซลูชันที่เลือกไว้นั้นไม่ใช่ทั้งคำนำหน้าขนาดหรือการเลิกศูนย์ เช่นเดียวกับใน C สตริงตัวอักษรที่อยู่ในเครื่องหมายคำพูดคู่นั้นเป็นเพียงคำสั้น ๆ สำหรับอาร์เรย์อักขระที่ไม่เปลี่ยนรูปแบบและภาษาก็มีคีย์เวิร์ดสตริงซึ่งหมายความว่า

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

ในอาร์เรย์ char ปกติหรือสตริงที่ไม่ใช่ตัวอักษรไม่มีศูนย์สุดท้ายดังนั้นโปรแกรมเมอร์ต้องใส่มันเองถ้าเขาต้องการเรียกใช้ฟังก์ชัน C บางตัวจาก D ในกรณีเฉพาะของสตริงตัวอักษรอย่างไรก็ตามคอมไพเลอร์ D ยังคงวางศูนย์ไว้ที่ จุดสิ้นสุดของแต่ละสตริง (เพื่ออนุญาตให้ใช้งานง่ายต่อการโยนสาย C เพื่อให้เรียกใช้ฟังก์ชัน C ได้ง่ายขึ้น) แต่นี่ไม่ใช่ศูนย์ส่วนหนึ่งของสตริง (D ไม่นับในขนาดสตริง)

สิ่งเดียวที่ทำให้ฉันผิดหวังบ้างคือสตริงควรจะเป็น utf-8 แต่ความยาวก็ยังคงส่งกลับจำนวนไบต์ (อย่างน้อยก็เป็นจริงในคอมไพเลอร์ gdc ของฉัน) แม้ในขณะที่ใช้ตัวอักษรแบบหลายไบต์ มันไม่ชัดเจนสำหรับฉันถ้ามันเป็นข้อผิดพลาดของคอมไพเลอร์หรือตามวัตถุประสงค์ (ตกลงฉันอาจจะได้รู้ว่าเกิดอะไรขึ้นการที่จะบอกว่าคอมไพเลอร์ D ของคุณใช้ utf-8 คุณต้องใส่เครื่องหมายคำสั่ง stupid byte ที่จุดเริ่มต้นฉันเขียน stupid เพราะฉันรู้ว่าไม่ใช่บรรณาธิการที่ทำเช่นนั้นโดยเฉพาะ UTF- 8 ที่ควรจะเข้ากันได้กับ ASCII)


7
... ดำเนินการต่อ ... หลายประเด็นที่ฉันคิดว่าผิดธรรมดานั่นคืออาร์กิวเมนต์ "ทุกอย่างเป็นไฟล์" ไฟล์คือการเข้าถึงตามลำดับสตริง C ไม่ใช่ ความยาวของคำนำหน้าสามารถทำได้ด้วย syntactic น้ำตาลน้อยที่สุด ข้อโต้แย้งที่สมเหตุสมผลเพียงอย่างเดียวคือการพยายามจัดการส่วนนำหน้า 32 บิตบนฮาร์ดแวร์ขนาดเล็ก (เช่น 8 บิต) ฉันคิดว่าสามารถแก้ไขได้โดยการบอกขนาดของความยาวนั้นพิจารณาจากการนำไปใช้ std::basic_stringท้ายที่สุดนั่นคือสิ่งที่ทำ
Billy ONeal

3
@Billy ONeal: จริงๆมีสองส่วนที่แตกต่างกันในคำตอบของฉัน หนึ่งคือเกี่ยวกับสิ่งที่เป็นส่วนหนึ่งของ 'ภาษา C หลัก' อีกคนหนึ่งเป็นเรื่องเกี่ยวกับสิ่งที่ห้องสมุดมาตรฐานควรส่งมอบ เกี่ยวกับการสนับสนุนสตริงมีเพียงหนึ่งรายการจากภาษาหลัก: ความหมายของคำพูดสองครั้งที่ล้อมรอบมัดไบต์ ฉันไม่ได้มีความสุขกว่าคุณด้วยพฤติกรรม C ฉันรู้สึกอย่างน่าอัศจรรย์ที่เพิ่มว่าศูนย์ในตอนท้ายของการปิดสองครั้งทุกครั้งที่มีการปิดล้อมมัดไบต์นั้นไม่ดีพอ ฉันต้องการและชัดเจน\0ที่สุดเมื่อโปรแกรมเมอร์ต้องการที่แทนที่จะเป็นหนึ่งโดยนัย ความยาวที่เตรียมไว้นั้นแย่กว่ามาก
kriss

2
@Billy ONeal: นั่นไม่เป็นความจริงเลยการใช้งานนั้นคำนึงถึงสิ่งที่เป็นแก่นและห้องสมุดคืออะไร จุดที่ใหญ่ที่สุดคือเมื่อใช้ C เพื่อใช้งานระบบปฏิบัติการ ในระดับนั้นไม่มีห้องสมุดให้บริการ C มักใช้ในบริบทฝังตัวหรืออุปกรณ์การเขียนโปรแกรมที่คุณมักจะมีข้อ จำกัด ชนิดเดียวกัน ในหลายกรณี Joes อาจไม่ควรใช้ C เลยในตอนนี้: "โอเคคุณต้องการมันบนคอนโซลหรือไม่คุณมีคอนโซลหรือไม่ไม่เลวเกินไป ... "
kriss

5
@Billy "ดีสำหรับ. 01% ของโปรแกรมเมอร์ C ที่ใช้ระบบปฏิบัติการก็โอเค" โปรแกรมเมอร์อื่น ๆ สามารถใช้เวลาธุดงค์ C ถูกสร้างขึ้นเพื่อเขียนระบบปฏิบัติการ
Daniel C. Sobral

5
ทำไม? เพราะมันบอกว่ามันเป็นภาษาวัตถุประสงค์ทั่วไป? มันพูดในสิ่งที่คนที่เขียนมันทำเมื่อมันสร้างขึ้น? มันใช้อะไรในช่วงสองสามปีแรกของชีวิต? ดังนั้นมันบอกว่าไม่เห็นด้วยกับฉันคืออะไร? มันเป็นภาษาอเนกประสงค์ที่สร้างขึ้นเพื่อเขียนระบบปฏิบัติการ มันปฏิเสธมันได้หรือไม่
Daniel C. Sobral

61

ฉันคิดว่ามันมีเหตุผลทางประวัติศาสตร์และพบสิ่งนี้ในวิกิพีเดีย :

ในเวลา C (และภาษาที่ได้รับมา) ได้รับการพัฒนาหน่วยความจำถูก จำกัด อย่างมากดังนั้นการใช้ค่าใช้จ่ายเพียงหนึ่งไบต์ของค่าใช้จ่ายในการจัดเก็บความยาวของสตริงเป็นที่น่าสนใจ ทางเลือกเดียวที่เป็นที่นิยมในเวลานั้นมักเรียกว่า "Pascal string" (แม้ว่าจะใช้โดย BASIC รุ่นแรก ๆ ) ก็ใช้ไบต์นำหน้าเพื่อจัดเก็บความยาวของสตริง สิ่งนี้อนุญาตให้สตริงมี NUL และทำให้การค้นหาความยาวต้องใช้การเข้าถึงหน่วยความจำเพียงครั้งเดียว (O (1) (คงที่) เวลา) แต่หนึ่งไบต์จำกัดความยาวไว้ที่ 255 ข้อ จำกัด ความยาวนี้มีข้อ จำกัด มากกว่าปัญหาของสตริง C ดังนั้นสตริง C โดยทั่วไปจึงชนะ


2
@muntoo อืม ... ความเข้ากันได้?
khachik

19
@muntoo: เพราะนั่นจะทำลายรหัส C และ C ++ ที่มีอยู่เป็นจำนวนมาก
Billy ONeal

10
@muntoo: กระบวนทัศน์มาและไป แต่รหัสดั้งเดิมเป็นอมตะ เวอร์ชัน C ในอนาคตใด ๆ จะต้องสนับสนุนสตริงที่สิ้นสุด 0 ต่อไปมิฉะนั้นต้องมีการเขียนรหัสดั้งเดิมมูลค่า 30+ ปีขึ้นไป (ซึ่งจะไม่เกิดขึ้น) และตราบใดที่ยังมีวิธีเดิมอยู่นั่นคือสิ่งที่ผู้คนจะใช้ต่อไปเนื่องจากเป็นสิ่งที่พวกเขาคุ้นเคย
John Bode

8
@muntoo: เชื่อฉันบางครั้งฉันหวังว่าฉันจะทำได้ แต่ฉันยังคงต้องการสตริงที่สิ้นสุดด้วย 0 มากกว่าสตริง Pascal
John Bode

2
พูดคุยเกี่ยวกับมรดก ... สตริง C ++ ได้รับคำสั่งให้ยกเลิก NUL
Jim Balter

32

Calaveraถูกต้องแต่เนื่องจากคนดูเหมือนจะไม่ได้จุดของฉันฉันจะให้ตัวอย่างรหัส

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

ถ้า C มีstringประเภทเช่นintหรือcharมันจะเป็นประเภทที่ไม่เหมาะกับการลงทะเบียนหรือในสแต็กและจะต้องมีการจัดสรรหน่วยความจำ (ด้วยโครงสร้างพื้นฐานที่สนับสนุนทั้งหมด) เพื่อจัดการ ทั้งหมดนี้ขัดแย้งกับหลักพื้นฐานของค

ดังนั้นสตริงใน C คือ:

char s*;

ดังนั้นสมมติว่านี่เป็นคำนำหน้ายาว ลองเขียนโค้ดเพื่อเชื่อมสองสายเข้าด้วยกัน:

char* concat(char* s1, char* s2)
{
    /* What? What is the type of the length of the string? */
    int l1 = *(int*) s1;
    /* How much? How much must I skip? */
    char *s1s = s1 + sizeof(int);
    int l2 = *(int*) s2;
    char *s2s = s2 + sizeof(int);
    int l3 = l1 + l2;
    char *s3 = (char*) malloc(l3 + sizeof(int));
    char *s3s = s3 + sizeof(int);
    memcpy(s3s, s1s, l1);
    memcpy(s3s + l1, s2s, l2);
    *(int*) s3 = l3;
    return s3;
}

ทางเลือกอื่นจะใช้ struct เพื่อกำหนดสตริง:

struct {
  int len; /* cannot be left implementation-defined */
  char* buf;
}

ณ จุดนี้การจัดการสตริงทั้งหมดจะต้องมีการจัดสรรสองครั้งซึ่งในทางปฏิบัติหมายความว่าคุณต้องผ่านห้องสมุดเพื่อจัดการกับมัน

สิ่งที่ตลกคือ ... structs เหมือนที่ทำอยู่ใน C! พวกเขาไม่ได้ใช้สำหรับการแสดงข้อความแบบวันต่อวันเพื่อการจัดการผู้ใช้

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

ตอนนี้ C สามารถจัดการกับหน่วยความจำได้แล้วและmemฟังก์ชั่นในไลบรารี (ใน<string.h>, ถึง!) ให้เครื่องมือทั้งหมดที่คุณต้องการในการจัดการหน่วยความจำเป็นคู่ของตัวชี้และขนาด "สตริง" ที่เรียกว่า ใน C ถูกสร้างขึ้นเพื่อจุดประสงค์เดียว: แสดงข้อความในบริบทของการเขียนระบบปฏิบัติการสำหรับเทอร์มินัลข้อความ และสำหรับสิ่งนั้นการยกเลิกค่า Null ก็เพียงพอแล้ว


2
1. +1 2. แน่นอนว่าถ้าพฤติกรรมเริ่มต้นของภาษานั้นเกิดขึ้นโดยใช้คำนำหน้ายาวก็จะมีสิ่งอื่น ๆ ที่ทำให้ง่ายขึ้น ตัวอย่างเช่นการปลดเปลื้องทั้งหมดของคุณจะถูกซ่อนไว้โดยการโทรหาstrlenและเพื่อน ๆ แทน สำหรับปัญหาที่เกิดขึ้นกับ "การปล่อยให้การนำไปใช้งาน" คุณสามารถพูดได้ว่าคำนำหน้าคือสิ่งที่shortอยู่บนกล่องเป้าหมาย จากนั้นการแสดงทั้งหมดของคุณจะยังคงทำงาน 3. ฉันสามารถสร้างสถานการณ์สมมติขึ้นมาได้ทั้งวันซึ่งทำให้ระบบใดระบบหนึ่งดูแย่
Billy ONeal

5
@Billy สิ่งที่ห้องสมุดเป็นจริงเพียงพอนอกเหนือจากความจริงที่ว่า C ถูกออกแบบมาสำหรับการใช้งานห้องสมุดน้อยที่สุดหรือไม่มีเลย ตัวอย่างเช่นการใช้ต้นแบบไม่ได้เป็นเรื่องปกติในช่วงต้น การกล่าวคำนำหน้าเป็นการshortจำกัด ขนาดของสตริงอย่างมีประสิทธิภาพซึ่งดูเหมือนจะเป็นสิ่งหนึ่งที่พวกเขาไม่กระตือรือร้น ตัวเองทำงานร่วมกับ 8 บิตบิตพื้นฐานและปาสคาลสตริงโคบอลขนาดคงที่และสิ่งที่คล้ายกันกลายเป็นแฟนตัวยงของสตริง C ไม่ จำกัด ขนาดอย่างรวดเร็ว ทุกวันนี้ขนาด 32 บิตจะจัดการกับสตริงที่ใช้งานได้จริง แต่การเพิ่มไบต์เหล่านั้นก่อนหน้านั้นเป็นปัญหา
Daniel C. Sobral

1
@Billy: ก่อนอื่นต้องขอบคุณ Daniel ... ดูเหมือนว่าคุณจะเข้าใจว่าฉันกำลังทำอะไรอยู่ ประการที่สองบิลลี่ฉันคิดว่าคุณยังขาดจุดที่กำลังทำอยู่ที่นี่ ฉันหนึ่งไม่ได้โต้เถียงข้อดีและข้อเสียของ prefixing ข้อมูลสตริงชนิดที่มีความยาวของพวกเขา สิ่งที่ฉันพูดและสิ่งที่แดเนียลเน้นย้ำอย่างชัดเจนว่ามีการตัดสินใจในการดำเนินงานของ C จะได้จัดการกับข้อโต้แย้งว่าที่ทุกคน เงื่อนไขไม่มีอยู่เท่าที่ภาษาพื้นฐานเกี่ยวข้อง การตัดสินใจเกี่ยวกับวิธีการจัดการสตริงถูกทิ้งไว้ที่โปรแกรมเมอร์ ... และการเลิกจ้าง null กลายเป็นที่นิยม
Robert S Ciaccio

1
+1 โดยฉัน อีกสิ่งหนึ่งที่ฉันต้องการเพิ่ม; struct ตามที่คุณเสนอมันพลาดขั้นตอนสำคัญไปสู่stringประเภทของจริง: ไม่ทราบถึงตัวอักษร มันเป็นชุดของ "ถ่าน" ("ถ่าน" ในเครื่องศัพท์แสงเป็นตัวละครมากเท่ากับ "คำ" เป็นสิ่งที่มนุษย์จะเรียกคำในประโยค) สตริงอักขระเป็นแนวคิดระดับสูงกว่าซึ่งสามารถนำไปใช้กับอาร์เรย์ของcharถ้าคุณนำแนวคิดของการเข้ารหัส
Frerich Raabe

2
@ DanielC.Sobral: โครงสร้างที่คุณพูดถึงไม่จำเป็นต้องมีการจัดสรรสองแบบ ไม่ว่าจะใช้งานได้ตามที่คุณมีไว้ในสแต็ค (เท่านั้นดังนั้นbufต้องจัดสรร) หรือการใช้งานstruct string {int len; char buf[]};และการจัดสรรสิ่งที่ทั้งกับการจัดสรรหนึ่งเป็นสมาชิกของอาร์เรย์ที่มีความยืดหยุ่นและผ่านมันไปรอบ ๆ string*เป็น (หรือstruct string {int capacity; int len; char buf[]};สำหรับเหตุผลประสิทธิภาพที่ชัดเจน)
Mooing Duck

20

เห็นได้ชัดว่าสำหรับประสิทธิภาพและความปลอดภัยคุณจะต้องรักษาความยาวของสายอักขระขณะที่คุณทำงานกับมันแทนที่จะทำซ้ำ ๆstrlenหรือเทียบเท่า อย่างไรก็ตามการจัดเก็บความยาวในตำแหน่งที่คงที่ก่อนที่เนื้อหาสตริงจะเป็นการออกแบบที่ไม่ดีอย่างเหลือเชื่อ ในขณะที่Jörgenชี้ให้เห็นในความคิดเห็นเกี่ยวกับคำตอบของ Sanjit มันจะ จำกัด การรักษาหางของสตริงเป็นสตริงซึ่งตัวอย่างเช่นทำให้การดำเนินงานทั่วไปมากมายเช่นpath_to_filenameหรือfilename_to_extensionเป็นไปไม่ได้โดยไม่ต้องจัดสรรหน่วยความจำใหม่ (และทำให้เกิดความล้มเหลว . และแน่นอนว่ามีปัญหาที่ไม่มีใครสามารถยอมรับจำนวนไบต์ที่ฟิลด์ความยาวสตริงควรครอบครอง (สตริง "ปาสกาล" ที่แย่มาก)

การออกแบบของ C ในการให้โปรแกรมเมอร์เลือกว่า / ที่ไหน / วิธีการเก็บความยาวนั้นมีความยืดหยุ่นและทรงพลังมากขึ้น แต่แน่นอนว่าโปรแกรมเมอร์ต้องฉลาด C ลงโทษความโง่เขลากับโปรแกรมที่ผิดพลาดบดขยี้หยุดหรือให้ศัตรูของคุณรูต


+1 คงจะดีถ้ามีสถานที่มาตรฐานในการจัดเก็บความยาวเพื่อให้พวกเราที่ต้องการบางสิ่งเช่นการขึ้นต้นความยาวไม่จำเป็นต้องเขียน "รหัสกาว" ทุกที่
Billy ONeal

2
ไม่มีสถานที่มาตรฐานที่เป็นไปได้ที่เกี่ยวข้องกับข้อมูลสตริง แต่แน่นอนว่าคุณสามารถใช้ตัวแปรโลคอลแยกต่างหาก (ทำการคำนวณใหม่แทนที่จะส่งผ่านเมื่อสิ่งหลังไม่สะดวกและอันก่อนไม่เสียเปล่า) หรือโครงสร้างที่มีตัวชี้ กับสตริง (และดียิ่งกว่านั้นแฟล็กที่ระบุว่าโครงสร้าง "เป็นเจ้าของ" ตัวชี้สำหรับการจัดสรรหรือว่าเป็นการอ้างอิงถึงสตริงที่เป็นเจ้าของที่อื่น ๆ และแน่นอนว่าคุณสามารถรวมสมาชิกอาร์เรย์ที่ยืดหยุ่นในโครงสร้างเพื่อความยืดหยุ่นในการจัดสรร สตริงที่มีโครงสร้างเมื่อมันเหมาะกับคุณ
R. GitHub STOP HELPING ICE

13

ความขี้เกียจลงทะเบียนความประหยัดและความสะดวกในการพิจารณาไส้ในของภาษาใด ๆ โดยเฉพาะ C ซึ่งเป็นหนึ่งในขั้นตอนข้างบนชุดประกอบ (ดังนั้นจึงสืบทอดรหัสมรดกชุดประกอบจำนวนมาก) คุณจะเห็นด้วยว่าตัวละครที่เป็นโมฆะจะไร้ประโยชน์ในวัน ASCII เหล่านั้น (และอาจดีเท่ากับตัวควบคุม EOF)

ลองดูในรหัสเทียม

function readString(string) // 1 parameter: 1 register or 1 stact entries
    pointer=addressOf(string) 
    while(string[pointer]!=CONTROL_CHAR) do
        read(string[pointer])
        increment pointer

รวม 1 ใช้การลงทะเบียน

กรณีที่ 2

 function readString(length,string) // 2 parameters: 2 register used or 2 stack entries
     pointer=addressOf(string) 
     while(length>0) do 
         read(string[pointer])
         increment pointer
         decrement length

ใช้ทั้งหมด 2 ลงทะเบียน

นั่นอาจดูเหมือนว่าเป็นกางเกงขาสั้นในเวลานั้น แต่เมื่อพิจารณาถึงความตระหนี่ในรหัสและการลงทะเบียน (ซึ่งเป็นพรีเมี่ยมในเวลานั้นเวลาที่คุณรู้ว่าพวกเขาใช้บัตรเจาะ) ดังนั้นเมื่อเร็วขึ้น (เมื่อนับความเร็วโปรเซสเซอร์สามารถนับเป็น kHz) "แฮ็ค" นี้ก็สวยดีและพกพาไปยังโปรเซสเซอร์ที่ไม่ต้องลงทะเบียนได้อย่างง่ายดาย

เพื่อประโยชน์ในการโต้แย้งฉันจะดำเนินการ 2 การดำเนินการสตริงทั่วไป

stringLength(string)
     pointer=addressOf(string)
     while(string[pointer]!=CONTROL_CHAR) do
         increment pointer
     return pointer-addressOf(string)

ความซับซ้อน O (n) โดยที่ในกรณีส่วนใหญ่สตริง PASCAL คือ O (1) เนื่องจากความยาวของสตริงจะถูก pre-pended กับโครงสร้างสตริง (นั่นก็หมายความว่าการดำเนินการนี้จะต้องดำเนินการในระยะก่อนหน้านี้)

concatString(string1,string2)
     length1=stringLength(string1)
     length2=stringLength(string2)
     string3=allocate(string1+string2)
     pointer1=addressOf(string1)
     pointer3=addressOf(string3)
     while(string1[pointer1]!=CONTROL_CHAR) do
         string3[pointer3]=string1[pointer1]
         increment pointer3
         increment pointer1
     pointer2=addressOf(string2)
     while(string2[pointer2]!=CONTROL_CHAR) do
         string3[pointer3]=string2[pointer2]
         increment pointer3
         increment pointer1
     return string3

ความซับซ้อน O (n) และการต่อเติมความยาวสตริงจะไม่เปลี่ยนความซับซ้อนของการดำเนินการในขณะที่ฉันยอมรับว่าจะใช้เวลาน้อยลง 3 ครั้ง

ในอีกทางหนึ่งถ้าคุณใช้สตริง PASCAL คุณจะต้องออกแบบ API ของคุณใหม่โดยคำนึงถึงความยาวของการลงทะเบียนบัญชีและบิตเอนด์เนสสตริง PASCAL มีข้อ จำกัด ที่รู้จักกันดีของ 255 ถ่าน (0xFF) เนื่องจากความยาวถูกเก็บไว้ใน 1 ไบต์ (8 บิต) ) และคุณต้องการสตริงที่ยาวกว่า (16bits-> อะไรก็ได้) คุณจะต้องคำนึงถึงสถาปัตยกรรมในโค้ดเลเยอร์หนึ่งซึ่งหมายความว่าในกรณีส่วนใหญ่สตริง API ที่เข้ากันไม่ได้ถ้าคุณต้องการสตริงที่ยาวกว่า

ตัวอย่าง:

ไฟล์หนึ่งไฟล์เขียนด้วยสตริง api ที่ได้รับการเติมของคุณบนคอมพิวเตอร์ 8 บิตและจากนั้นจะต้องมีการอ่านบนคอมพิวเตอร์ 32 บิตสิ่งที่โปรแกรมสันหลังยาวจะพิจารณาว่า 4bytes ของคุณมีความยาวของสตริงแล้วจัดสรรหน่วยความจำจำนวนมาก จากนั้นลองอ่านหลาย ๆ ไบต์ อีกกรณีหนึ่งคือสตริงการอ่าน PPC 32 ไบต์ (เล็ก ๆ น้อย ๆ endian) ไปยัง x86 (big endian) แน่นอนถ้าคุณไม่รู้ว่าอีกอันเขียนโดยคนอื่นจะมีปัญหา ความยาว 1 ไบต์ (0x00000001) จะกลายเป็น 16777216 (0x0100000) นั่นคือ 16 MB สำหรับการอ่านสตริง 1 ไบต์ แน่นอนคุณจะบอกว่าผู้คนควรเห็นด้วยกับมาตรฐานเดียว แต่แม้แต่ Unicode 16 บิตก็มีความเพียรน้อยและใหญ่

แน่นอน C จะมีปัญหาเช่นกัน แต่จะได้รับผลกระทบน้อยมากจากปัญหาที่เกิดขึ้นที่นี่


2
@deemoowoor: Concat: O(m+n)ด้วยสตริง nullterm O(n)ทั่วไปทุกที่อื่น ความยาวO(n)กับสตริง nullterm O(1)ทุกที่อื่น เข้าร่วม: O(n^2)กับสตริง nullterm O(n)ทุกที่อื่น มีบางกรณีที่สตริงที่สิ้นสุดด้วยค่า null จะมีประสิทธิภาพมากขึ้น (เช่นเพิ่มเพียงหนึ่งไปยังตัวชี้กรณี) แต่ concat และ length นั้นเป็นการดำเนินการที่พบบ่อยที่สุด (ความยาวอย่างน้อยต้องมีการจัดรูปแบบเอาต์พุตไฟล์คอนโซลแสดง ฯลฯ ) . หากคุณแคระยะเวลาในการตัดจำหน่ายO(n)คุณเพิ่งทำจุดของฉันที่ความยาวควรจะเก็บไว้กับสตริง
Billy ONeal

1
ฉันยอมรับว่าในรหัสของวันนี้สตริงประเภทนี้ไม่มีประสิทธิภาพและมีแนวโน้มที่จะเกิดข้อผิดพลาด แต่ตัวอย่างเช่นจอแสดงผล Console ไม่จำเป็นต้องทราบความยาวของสตริงที่จะแสดงอย่างมีประสิทธิภาพเอาต์พุตไฟล์ไม่จำเป็นต้องรู้เกี่ยวกับสตริงจริงๆ ความยาว (เพียงการจัดสรรคลัสเตอร์ในระหว่างการเดินทาง), และการจัดรูปแบบสตริงในเวลานี้ได้ทำกับความยาวสตริงคงที่ในกรณีส่วนใหญ่ ต่อไปที่คุณจะต้องเขียนโค้ดไม่ดีถ้าคุณ concat ใน C มี O (n ^ 2) ความซับซ้อนผมค่อนข้างแน่ใจว่าฉันสามารถเขียนหนึ่งใน O (n) ความซับซ้อน
dvhh

1
@dvhh: ฉันไม่ได้พูดว่า n ^ 2 - ฉันพูดว่า m + n - มันยังคงเป็นเส้นตรง แต่คุณต้องพยายามหาจุดสิ้นสุดของสายเดิมเพื่อที่จะทำการต่อเรียงกันในขณะที่คำนำหน้ายาวไม่ได้ค้นหา ต้องระบุ. (นี่เป็นอีกผลสืบเนื่องของความยาวที่ต้องใช้เวลาเชิงเส้น)
Billy ONeal

1
@Billy ONeal: จากความอยากรู้เพียงอย่างเดียวฉันได้ grep ในโครงการ C ปัจจุบันของฉัน (ประมาณ 50,000 บรรทัดของรหัส) สำหรับการเรียกฟังก์ชั่นการจัดการสตริง strlen 101, strcpy และตัวแปร (strncpy, strlcpy): 85 (ฉันยังมีหลายร้อยสตริงตัวอักษรที่ใช้สำหรับข้อความคัดลอกโดยนัย), strcmp: 56, strcat: 13 (และ 6 เป็น concatenations สตริงศูนย์ความยาวเพื่อเรียก strncat) . ฉันยอมรับความยาวที่มีคำนำหน้าจะเพิ่มความเร็วในการเรียกไปยัง strlen แต่ไม่ใช่เพื่อ strcpy หรือ strcmp (อาจเป็นถ้า strcmp API ไม่ได้ใช้คำนำหน้าทั่วไป) สิ่งที่น่าสนใจที่สุดเกี่ยวกับความคิดเห็นข้างต้นคือ strcat นั้นหายากมาก
kriss

1
@supercat: ไม่จริงดูการใช้งานบางอย่าง สตริงสั้น ๆ กำลังใช้บัฟเฟอร์แบบสั้นตามสแต็ก (ไม่มีการจัดสรรฮีป) ใช้ฮีปเดียวเมื่อใหญ่ขึ้น แต่คุณสามารถนำแนวคิดของคุณไปใช้จริงในฐานะห้องสมุด โดยปกติปัญหาจะปรากฏเฉพาะเมื่อเราไปถึงรายละเอียดไม่ใช่ในการออกแบบโดยรวม
kriss

9

ในหลาย ๆ วิธี C เป็นดั้งเดิม และฉันก็ชอบมัน

มันเป็นขั้นตอนเหนือภาษาแอสเซมบลีที่ให้คุณเกือบจะมีประสิทธิภาพเดียวกันกับภาษาที่ง่ายต่อการเขียนและบำรุงรักษา

ตัวปิดเทอร์มินัลธรรมดานั้นง่ายและไม่ต้องการการสนับสนุนเป็นพิเศษจากภาษา

มองย้อนกลับไปมันดูไม่สะดวกนัก แต่ฉันใช้ภาษาแอสเซมบลีย้อนกลับไปในยุค 80 และดูเหมือนว่าเวลาจะสะดวกมาก ฉันแค่คิดว่าซอฟต์แวร์นั้นมีการพัฒนาอย่างต่อเนื่องและแพลตฟอร์มและเครื่องมือจะมีความซับซ้อนมากขึ้นเรื่อย ๆ


ฉันไม่เห็นว่ามีอะไรดั้งเดิมเกี่ยวกับสตริงที่สิ้นสุดด้วย null มากกว่าสิ่งอื่น ปาสกาลถือกำเนิด C และใช้ความยาวของคำนำหน้า แน่นอนว่ามัน จำกัด อยู่ที่ 256 ตัวอักษรต่อสตริง แต่การใช้ฟิลด์ 16 บิตจะช่วยแก้ไขปัญหาได้ในกรณีส่วนใหญ่
Billy ONeal

ความจริงที่ว่ามัน จำกัด จำนวนของตัวละครเป็นประเภทของปัญหาที่คุณต้องพิจารณาเมื่อทำอะไรเช่นนั้น ใช่คุณสามารถทำให้มันยาวขึ้นได้ แต่กลับมีจำนวนไบต์ที่สำคัญ และฟิลด์ 16 บิตจะยาวพอสำหรับทุกกรณีหรือไม่ C'mon คุณต้องยอมรับว่าโมฆะสิ้นสุดเป็นแนวคิดดั้งเดิม
โจนาธานวู้ด

10
ไม่ว่าคุณจะจำกัดความยาวของสตริงหรือคุณ จำกัด เนื้อหา (ไม่ จำกัด อักขระ) หรือคุณยอมรับค่าใช้จ่ายเพิ่มเติมของการนับ 4 ถึง 8 ไบต์ ไม่มีอาหารกลางวันฟรี ในช่วงเวลาของการเริ่มต้นสายสิ้นสุดโมฆะทำให้รู้สึกที่สมบูรณ์แบบ ในการชุมนุมบางครั้งฉันก็ใช้บิตบนสุดของตัวละครเพื่อทำเครื่องหมายจุดสิ้นสุดของสตริงช่วยประหยัดไบต์ได้มากขึ้น!
Mark Ransom

แน่นอน Mark: ไม่มีอาหารกลางวันฟรี มันเป็นการประนีประนอมเสมอ วันนี้เราไม่จำเป็นต้องประนีประนอมแบบเดียวกัน แต่ก่อนหน้านี้วิธีการนี้ดูเหมือนดีเหมือนกัน
โจนาธานวู้ด

8

สมมติว่าสักครู่ที่ C นำสตริงไปใช้วิธี Pascal โดยนำหน้าด้วยความยาว: สตริงที่มีความยาว 7 อักขระเหมือนกับ DATA TYPE เหมือนกับ 3-char string หรือไม่ หากคำตอบคือใช่แล้วคอมไพเลอร์ควรสร้างรหัสแบบใดเมื่อฉันกำหนดค่าแบบเก่าให้กับหลัง ควรตัดทอนสตริงหรือปรับขนาดอัตโนมัติหรือไม่ หากปรับขนาดแล้วการดำเนินการนั้นควรได้รับการปกป้องโดยล็อคเพื่อทำให้เธรดปลอดภัยหรือไม่ ด้านวิธีการ C ก้าวปัญหาเหล่านี้ทั้งหมดออกมาชอบหรือไม่ :)


2
เอ่อ .. ไม่เลย วิธีการ C ไม่อนุญาตให้กำหนดสตริงอักขระยาว 7 อักขระให้กับสตริงอักขระอักขระยาว 3 รูปเลย
Billy ONeal

@Billy ONeal: ทำไมไม่? เท่าที่ฉันเข้าใจในกรณีนี้สตริงทั้งหมดเป็นชนิดข้อมูลเดียวกัน (char *) ดังนั้นความยาวจึงไม่สำคัญ ไม่เหมือนปาสกาล แต่นั่นเป็นข้อ จำกัด ของ Pascal แทนที่จะเป็นปัญหากับสตริงที่มีคำนำหน้ายาว
โอลิเวอร์เมสัน

4
@Billy: ฉันคิดว่าคุณเพิ่งปรับปรุงจุด Cristian C เกี่ยวข้องกับปัญหาเหล่านี้โดยไม่ติดต่อกับพวกเขาเลย คุณยังคงคิดในแง่ของ C ที่บรรจุความคิดของสตริง เป็นเพียงตัวชี้เพื่อให้คุณสามารถกำหนดสิ่งที่คุณต้องการ
Robert S Ciaccio

2
มันเหมือนกับเมทริกซ์ **: "ไม่มีสตริง"
Robert S Ciaccio

1
@ กาลาเวร่า: ฉันไม่เห็นว่ามันพิสูจน์อะไร คุณสามารถแก้ไขได้ด้วยวิธีเดียวกันกับการขึ้นต้นความยาว ... นั่นคือไม่อนุญาตให้มีการมอบหมายเลย
Billy ONeal

8

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

#define PREFIX_STR(s) ((prefix_str_t){ sizeof(s)-1, (s) })

typedef struct { int n; char * p; } prefix_str_t;

int main() {
    prefix_str_t string1, string2;

    string1 = PREFIX_STR("Hello!");
    string2 = PREFIX_STR("Allows \0 chars (even if printf directly doesn't)");

    printf("%d %s\n", string1.n, string1.p); /* prints: "6 Hello!" */
    printf("%d %s\n", string2.n, string2.p); /* prints: "48 Allows " */

    return 0;
}

อย่างไรก็ตามสิ่งนี้จะไม่เกิดขึ้นโดยไม่มีปัญหาใด ๆ ตามที่คุณต้องระวังเมื่อจะให้ตัวชี้สตริงนั้นและเมื่อมีการจัดสรรแบบคงที่ ( charอาร์เรย์ตัวอักษร) โดยเฉพาะ

แก้ไข:เนื่องจากเป็นคำตอบที่ตรงกับคำถามมากขึ้นมุมมองของฉันคือนี่เป็นวิธีที่ C สามารถรองรับทั้งความยาวของสตริงที่มี (เป็นค่าคงที่เวลาคอมไพล์) ถ้าคุณต้องการ แต่ก็ยังไม่มีค่าใช้จ่ายหน่วยความจำหากคุณต้องการใช้ ตัวชี้และการยกเลิกศูนย์

แน่นอนว่าดูเหมือนว่าการทำงานกับสตริงที่สิ้นสุดลงเป็นแนวทางปฏิบัติที่แนะนำเนื่องจากไลบรารีมาตรฐานโดยทั่วไปไม่ได้ใช้ความยาวของสตริงเป็นอาร์กิวเมนต์และเนื่องจากการแยกความยาวไม่ได้เป็นรหัสที่ตรงไปตรงมาดังchar * s = "abc"ที่ตัวอย่างของฉันแสดง


ปัญหาคือห้องสมุดไม่ทราบว่าโครงสร้างของคุณมีอยู่จริงและยังคงจัดการกับสิ่งต่าง ๆ เช่นการฝังค่า Null ที่ไม่ถูกต้อง นอกจากนี้ยังไม่ตอบคำถามที่ฉันถาม
Billy ONeal

1
นั่นเป็นเรื่องจริง ดังนั้นปัญหาที่ใหญ่กว่าคือไม่มีวิธีมาตรฐานที่ดีกว่าในการจัดเตรียมอินเทอร์เฟซด้วยพารามิเตอร์สตริงกว่าสตริงที่สิ้นสุดด้วยศูนย์แบบเก่า ฉันยังคงเรียกร้องมีห้องสมุดที่รองรับการให้อาหารในคู่ความยาวตัวชี้ (อย่างน้อยคุณสามารถสร้างสตริง C ++ std :: กับพวกเขา)
Pyry Jahkola

2
แม้ว่าคุณจะเก็บความยาวคุณไม่ควรอนุญาตให้ใช้สตริงที่มีค่า null ฝังอยู่ นี่คือสามัญสำนึกพื้นฐาน หากข้อมูลของคุณอาจมีค่าว่างคุณไม่ควรใช้กับฟังก์ชั่นที่คาดหวังสตริง
. GitHub หยุดช่วยน้ำแข็ง

1
@supercat: จากจุดรักษาความปลอดภัยฉันยินดีต้อนรับความซ้ำซ้อนนั้น มิฉะนั้นโปรแกรมเมอร์ (หรือที่ไม่มีการนอนหลับ) ไม่รู้จะจบลงด้วยการเชื่อมข้อมูลไบนารีและสตริงและส่งต่อไปยังสิ่งต่าง ๆ ที่คาดว่าจะเป็นสตริง [สิ้นสุดค่า null] ...
.. GitHub STOP ช่วย ICE

1
@R .. : ในขณะที่วิธีการที่คาดหวังสตริงโมฆะยกเลิกโดยทั่วไปคาดว่าจะมีchar*วิธีการหลายอย่างที่ไม่ได้คาดหวังการเลิกจ้าง null char*ยังคาดหวังว่า ประโยชน์ที่สำคัญของการแยกประเภทจะเกี่ยวข้องกับพฤติกรรม Unicode มันอาจจะคุ้มค่าสำหรับการใช้งานสตริงเพื่อรักษาธงว่าสตริงเป็นที่รู้จักกันมีตัวละครบางชนิดหรือเป็นที่รู้จักกันไม่ได้ที่จะมีพวกเขา [เช่นการค้นหาจุดรหัส 999,990th ในสตริงล้านตัวอักษรซึ่งเป็นที่รู้จัก ตัวอักษรใดเกินกว่าเครื่องบินพูดได้หลายภาษาพื้นฐานจะเป็นคำสั่งของขนาดได้เร็วขึ้น ...
SuperCat

6

"แม้แต่บนเครื่อง 32 บิตหากคุณอนุญาตให้สตริงมีขนาดของหน่วยความจำที่มีอยู่สตริงที่ขึ้นต้นความยาวจะมีความกว้างเพียงสามไบต์กว้างกว่าสตริงที่สิ้นสุดด้วยค่า null"

ก่อนอื่นไบต์พิเศษ 3 ไบต์อาจมีค่าใช้จ่ายสูงสำหรับสายอักขระสั้น โดยเฉพาะอย่างยิ่งสตริงที่มีความยาวเป็นศูนย์จะใช้หน่วยความจำมากถึง 4 เท่า พวกเราบางคนใช้เครื่อง 64- บิตดังนั้นเราต้องการ 8 ไบต์ในการจัดเก็บสตริงที่มีความยาวเป็นศูนย์หรือรูปแบบของสตริงไม่สามารถรับมือกับสตริงที่ยาวที่สุดที่แพลตฟอร์มรองรับ

อาจมีปัญหาการจัดตำแหน่งที่จะจัดการกับ สมมติว่าฉันมีบล็อกของหน่วยความจำที่มี 7 สตริงเช่น "solo \ 0 วินาทีสอง \ 0 \ 0 สี่ \ 0five \ 0 \ 0 สิบเจ็ด" สตริงที่สองเริ่มต้นที่ออฟเซ็ต 5 ฮาร์ดแวร์อาจต้องการให้มีจำนวนเต็ม 32- บิตที่จัดเรียงตามที่อยู่ที่เป็นทวีคูณของ 4 ดังนั้นคุณต้องเพิ่มช่องว่างภายในเพิ่มค่าใช้จ่ายเพิ่มเติม การแทนค่า C เป็นหน่วยความจำที่มีประสิทธิภาพในการเปรียบเทียบ (หน่วยความจำ - ประสิทธิภาพดี; ช่วยให้ประสิทธิภาพของแคชเป็นต้น)


ฉันเชื่อว่าฉันตอบคำถามทั้งหมดนี้ได้ ใช่บนแพลตฟอร์ม x64 คำนำหน้า 32 บิตไม่สามารถใส่สตริงที่เป็นไปได้ทั้งหมด ในทางกลับกันคุณไม่ต้องการสตริงที่มีขนาดใหญ่เท่าสตริงที่ถูกยกเลิกค่า null เนื่องจากจะทำทุกสิ่งที่คุณต้องตรวจสอบทั้งหมด 4 พันล้านไบต์เพื่อหาจุดสิ้นสุดสำหรับการดำเนินการเกือบทุกอย่างที่คุณต้องการ นอกจากนี้ฉันไม่ได้บอกว่าสตริงที่สิ้นสุดด้วย null นั้นเป็นสิ่งที่ชั่วร้ายเสมอ - หากคุณกำลังสร้างหนึ่งในโครงสร้างบล็อกเหล่านี้และแอปพลิเคชันเฉพาะของคุณจะถูกเร่งความเร็วด้วยการก่อสร้างประเภทนั้น ฉันแค่ต้องการพฤติกรรมเริ่มต้นของภาษาที่ไม่ได้ทำ
Billy ONeal

2
ฉันยกมาเป็นส่วนหนึ่งของคำถามของคุณเพราะในมุมมองของฉันมัน underrated ปัญหาประสิทธิภาพ ความต้องการของหน่วยความจำสองเท่าหรือสี่เท่า (ใน 16 บิตและ 32 บิตตามลำดับ) อาจมีค่าใช้จ่ายมาก สายยาวอาจช้า แต่อย่างน้อยก็รองรับและยังใช้งานได้ ประเด็นอื่น ๆ ของฉันเกี่ยวกับการจัดตำแหน่งคุณไม่ได้พูดถึงเลย
Brangdon

การจัดตำแหน่งอาจได้รับการจัดการโดยการระบุว่าค่าที่เกินกว่า UCHAR_MAX ควรทำตัวเหมือนว่าแพ็คและคลายไฟล์โดยใช้การเข้าถึงไบต์และการเลื่อนบิต ประเภทของสตริงที่ได้รับการออกแบบมาอย่างเหมาะสมสามารถให้ประสิทธิภาพในการจัดเก็บโดยเปรียบเทียบได้กับสตริงที่ถูกยกเลิกในขณะที่ยังอนุญาตให้ตรวจสอบขอบเขตบนบัฟเฟอร์โดยไม่มีหน่วยความจำเพิ่มเติม (ใช้หนึ่งบิตในส่วนนำหน้าเพื่อบอกว่าบัฟเฟอร์เต็ม); ไม่ใช่และไบต์สุดท้ายไม่ใช่ศูนย์ไบต์นั้นจะแทนพื้นที่ที่เหลือหากบัฟเฟอร์ไม่เต็มและไบต์สุดท้ายเป็นศูนย์ดังนั้น 256 ไบต์สุดท้ายจะไม่ถูกใช้ดังนั้น ...
supercat

... หนึ่งสามารถเก็บจำนวนไบต์ที่ไม่ได้ใช้ในพื้นที่นั้นโดยมีค่าใช้จ่ายหน่วยความจำเพิ่มเติมเป็นศูนย์) ค่าใช้จ่ายในการทำงานกับส่วนนำหน้าจะถูกชดเชยด้วยความสามารถในการใช้วิธีการเช่น fgets () โดยไม่ต้องผ่านความยาวสตริง (เนื่องจากบัฟเฟอร์จะรู้ว่าใหญ่แค่ไหน)
supercat

4

การยกเลิกค่า Null ช่วยให้สามารถดำเนินการตามตัวชี้อย่างรวดเร็ว


5
ฮะ? "การดำเนินการตัวชี้อย่างรวดเร็ว" อะไรที่ไม่สามารถนำหน้าความยาวได้ ที่สำคัญกว่าภาษาอื่น ๆ ที่ใช้ความยาวของคำนำหน้าจะเร็วกว่าการจัดการสตริง C wrt
Billy ONeal

12
@billy: ด้วยสตริงนำหน้าความยาวคุณไม่สามารถใช้ตัวชี้สตริงและเพิ่ม 4 ลงไปและคาดว่ามันจะยังคงเป็นสตริงที่ถูกต้องเพราะมันไม่มีคำนำหน้าความยาว
Jörgen Sigvardsson

3
@j_random_hacker: การต่อข้อมูลยิ่งแย่ยิ่งขึ้นสำหรับสตริง asciiz (O (m + n) แทนที่จะเป็น O (n)) และ concat นั้นเป็นเรื่องธรรมดามากกว่าการดำเนินการอื่น ๆ ที่ระบุไว้ที่นี่
Billy ONeal

3
มีการดำเนินการอย่างใดอย่างหนึ่งเพียงเล็กน้อย tiiny strlenที่มีราคาแพงมากขึ้นด้วยสตริงโมฆะสิ้นสุด: ฉันจะบอกว่านั่นเป็นข้อเสียเปรียบเล็กน้อย
jalf

10
@Billy ONeal: ทุกคนก็สนับสนุน regex เช่นกัน แล้วอะไรล่ะ ใช้ห้องสมุดที่พวกเขาทำ C เป็นเรื่องเกี่ยวกับประสิทธิภาพสูงสุดและความเรียบง่ายไม่รวมแบตเตอรี่ เครื่องมือ C ยังช่วยให้คุณสามารถใช้สตริงคำนำหน้ายาวโดยใช้ struct ได้อย่างง่ายดาย และไม่มีอะไรที่ห้ามไม่ให้คุณใช้โปรแกรมจัดการสตริงผ่านการจัดการความยาวและบัฟเฟอร์ของคุณเอง ปกติแล้วฉันจะทำอย่างไรเมื่อฉันต้องการประสิทธิภาพและใช้ C โดยไม่เรียกฟังก์ชั่นที่คาดหวังว่าศูนย์ในตอนท้ายของบัฟเฟอร์บัฟเฟอร์ไม่ใช่ปัญหา
kriss

4

จุดหนึ่งที่ยังไม่ได้กล่าวถึง: เมื่อ C ได้รับการออกแบบมีหลายเครื่องที่ 'char' ไม่ใช่แปดบิต (แม้วันนี้จะมีแพลตฟอร์ม DSP ที่ไม่มี) หากใครตัดสินใจว่าสตริงจะต้องมีคำนำหน้ายาวความยาวของคำนำหน้ายาวจำนวนอักขระที่ควรใช้ การใช้สองจะกำหนดข้อ จำกัด เทียมกับความยาวสตริงสำหรับเครื่องที่มีพื้นที่ 8 บิตและที่อยู่ 32 บิตในขณะที่เปลืองพื้นที่บนเครื่องด้วย 16-bit char และ 16-bit addressing space

ถ้าใครอยากจะอนุญาตให้เก็บสายยาว ๆ ได้อย่างมีประสิทธิภาพและถ้า 'ถ่าน' อยู่เสมอ 8-bits คน ๆ หนึ่งสามารถ - สำหรับค่าใช้จ่ายในความเร็วและขนาดรหัส - กำหนดรูปแบบเป็นสตริงนำหน้าด้วยเลขคู่ N จะมีความยาว N / 2 ไบต์สตริงนำหน้าด้วยค่าคี่ N และค่าคู่ M (การอ่านย้อนหลัง) อาจเป็น ((N-1) + M * char_max) / 2 ฯลฯ และต้องการบัฟเฟอร์ใด ๆ อ้างว่าเสนอพื้นที่จำนวนหนึ่งเพื่อเก็บสตริงจะต้องอนุญาตให้มีไบต์ที่เพียงพอก่อนหน้าพื้นที่นั้นเพื่อจัดการกับความยาวสูงสุด ความจริงที่ว่า 'char' ไม่ได้เป็น 8 บิตเสมอไป แต่จะทำให้รูปแบบดังกล่าวซับซ้อนเนื่องจากจำนวน 'char' ที่ต้องการเก็บความยาวของสตริงจะแตกต่างกันไปขึ้นอยู่กับสถาปัตยกรรมของ CPU


sizeof(char)คำนำหน้าได้อย่างง่ายดายสามารถมีขนาดการดำเนินงานที่กำหนดไว้เช่นเดียวกับที่เป็น
Billy ONeal

@BillyONeal: sizeof(char)เป็นหนึ่ง เสมอ. หนึ่งอาจมีคำนำหน้าเป็นขนาดที่กำหนดการนำไปใช้ แต่มันจะน่าอึดอัดใจ นอกจากนี้ยังไม่มีวิธีการที่แท้จริงในการรู้ขนาดที่เหมาะสม หากมีสายอักขระ 4 ตัวจำนวนมากศูนย์การเติมเต็มจะกำหนดค่าใช้จ่าย 25% ในขณะที่ส่วนนำหน้าความยาวสี่ไบต์จะกำหนดค่าใช้จ่าย 100% นอกจากนี้เวลาที่ใช้ในการบรรจุและนำหน้าความยาวสี่ไบต์ออกจากกล่องอาจเกินค่าใช้จ่ายในการสแกนสตริง 4 ไบต์สำหรับศูนย์ไบต์
supercat

1
อ่าใช่ คุณถูก. คำนำหน้าอาจเป็นสิ่งอื่นที่ไม่ใช่ตัวละครได้อย่างง่ายดาย สิ่งใดก็ตามที่จะทำให้ความต้องการการจัดตำแหน่งบนแพลตฟอร์มเป้าหมายทำงานได้ดี ฉันจะไม่ไปที่นั่นแม้ว่าฉันจะเถียงกันเรื่องนี้ไปสู่ความตาย
Billy ONeal

สมมติว่าสายอักขระมีความยาวนำหน้าอาจเป็นสิ่ง sanest ที่จะเป็นsize_tคำนำหน้า (เสียหน่วยความจำถูกสาปมันจะ sanest --- อนุญาตให้สายยาวที่สุดเท่าที่จะเป็นไปได้ในหน่วยความจำ) ในความเป็นจริงว่าเป็นชนิดของสิ่งที่ D ไม่; อาร์เรย์และสตริงเป็นเพียงอาร์เรย์struct { size_t length; T* ptr; } immutable(char)
ทิม Febas

@ TimČas: ยกเว้นว่าจำเป็นต้องมีการจัดเรียงคำศัพท์ค่าใช้จ่ายในการทำงานกับสตริงสั้น ๆ ในหลาย ๆ แพลตฟอร์มจะถูกครอบงำโดยข้อกำหนดในการแพ็คและคลายความยาว ฉันไม่เห็นว่าเป็นจริง หากต้องการให้สตริงเป็นอาร์เรย์ไบต์ขนาดไม่เชื่อเรื่องพระเจ้าฉันคิดว่ามันจะเป็นการดีกว่าถ้าแยกความยาวจากตัวชี้ไปยังข้อมูลตัวอักษรและมีภาษาอนุญาตให้รับข้อมูลทั้งสองชิ้นสำหรับสตริงตัวอักษร .
supercat

2

การตัดสินใจออกแบบหลายรอบ C เกิดจากข้อเท็จจริงที่ว่าเมื่อมันถูกนำมาใช้จริงการส่งผ่านพารามิเตอร์นั้นค่อนข้างแพง ให้ทางเลือกระหว่างเช่น

void add_element_to_next(arr, offset)
  char[] arr;
  int offset;
{
  arr[offset] += arr[offset+1];
}

char array[40];

void test()
{
  for (i=0; i<39; i++)
    add_element_to_next(array, i);
}

กับ

void add_element_to_next(ptr)
  char *p;
{
  p[0]+=p[1];
}

char array[40];

void test()
{
  int i;
  for (i=0; i<39; i++)
    add_element_to_next(arr+i);
}

หลังจะมีราคาถูกกว่าเล็กน้อย (และเป็นที่ต้องการ) เนื่องจากต้องผ่านพารามิเตอร์เดียวมากกว่าสองพารามิเตอร์ หากวิธีการที่ถูกเรียกไม่จำเป็นต้องรู้ที่อยู่ฐานของอาร์เรย์หรือดัชนีที่อยู่ภายในผ่านตัวชี้เดียวที่รวมกันทั้งสองจะถูกกว่าการส่งผ่านค่าแยกต่างหาก

ในขณะที่มีหลายวิธีที่ C สามารถเข้ารหัสความยาวของสตริงได้ แต่วิธีการที่ถูกประดิษฐ์จนถึงเวลานั้นจะมีฟังก์ชั่นที่จำเป็นทั้งหมดที่ควรจะสามารถทำงานกับส่วนของสตริงเพื่อยอมรับที่อยู่ฐานของสตริงและ ดัชนีที่ต้องการเป็นพารามิเตอร์สองตัวที่แยกกัน การใช้การยกเลิกแบบศูนย์ไบต์ทำให้สามารถหลีกเลี่ยงข้อกำหนดนั้นได้ แม้ว่าวิธีการอื่น ๆ จะดีกว่ากับเครื่องของวันนี้ (คอมไพเลอร์สมัยใหม่มักจะส่งพารามิเตอร์ในการลงทะเบียนและ memcpy สามารถปรับให้เหมาะสมในรูปแบบ strcpy () - ไม่สามารถเทียบเท่าได้) รหัสการผลิตที่เพียงพอจะใช้สตริงที่สิ้นสุดด้วยศูนย์ไบต์

ป.ล. - เพื่อแลกกับการลงโทษความเร็วเล็กน้อยในการดำเนินการบางอย่างและค่าใช้จ่ายเพิ่มเติมเล็กน้อยในสตริงที่ยาวกว่ามันอาจเป็นไปได้ที่จะมีวิธีการที่ทำงานกับสตริงยอมรับพอยน์เตอร์โดยตรงกับสตริงบัฟเฟอร์สตริงที่ตรวจสอบขอบเขตหรือ โครงสร้างข้อมูลที่ระบุสตริงย่อยของสตริงอื่น ฟังก์ชั่นอย่าง "strcat" น่าจะดูเหมือน [modern syntax]

void strcat(unsigned char *dest, unsigned char *src)
{
  struct STRING_INFO d,s;
  str_size_t copy_length;

  get_string_info(&d, dest);
  get_string_info(&s, src);
  if (d.si_buff_size > d.si_length) // Destination is resizable buffer
  {
    copy_length = d.si_buff_size - d.si_length;
    if (s.src_length < copy_length)
      copy_length = s.src_length;
    memcpy(d.buff + d.si_length, s.buff, copy_length);
    d.si_length += copy_length;
    update_string_length(&d);
  }
}

ใหญ่กว่าวิธี K&R strcat เล็กน้อย แต่มันจะรองรับการตรวจสอบขอบเขตซึ่งวิธี K&R ไม่ได้ ยิ่งไปกว่านั้นไม่เหมือนกับวิธีการในปัจจุบันมันเป็นไปได้ที่จะต่อเชื่อมซับสตริงโดยพลการเช่น

/* Concatenate 10th through 24th characters from src to dest */

void catpart(unsigned char *dest, unsigned char *src)
{
  struct SUBSTRING_INFO *inf;
  src = temp_substring(&inf, src, 10, 24);
  strcat(dest, src);
}

โปรดทราบว่าอายุการใช้งานของสตริงที่ส่งคืนโดย temp_substring จะถูก จำกัด โดยค่าของsและsrcซึ่งเคยสั้นกว่า (ซึ่งเป็นสาเหตุที่วิธีการนั้นต้องการinfจะต้องผ่านใน - ถ้ามันเป็นท้องถิ่นมันจะตายเมื่อวิธีการส่งกลับ)

ในแง่ของต้นทุนหน่วยความจำสตริงและบัฟเฟอร์สูงสุด 64 ไบต์จะมีหนึ่งไบต์ของค่าใช้จ่าย (เช่นเดียวกับสตริงที่สิ้นสุดด้วยศูนย์); สตริงที่ยาวกว่าจะมีอีกเล็กน้อย (ไม่ว่าจะเป็นหนึ่งในจำนวนที่อนุญาตค่าใช้จ่ายระหว่างสองไบต์และสูงสุดที่ต้องการจะเป็นการแลกเปลี่ยนเวลา / พื้นที่) ค่าพิเศษของความยาว / โหมดไบต์จะใช้เพื่อระบุว่าฟังก์ชั่นสตริงได้รับโครงสร้างที่มีไบต์ธงตัวชี้และความยาวบัฟเฟอร์ (ซึ่งสามารถดัชนีดัชนีโดยพลการลงในสตริงอื่น ๆ )

แน่นอน K&R ไม่ได้ใช้สิ่งนี้ แต่เป็นไปได้มากที่สุดเพราะพวกเขาไม่ต้องการใช้ความพยายามอย่างมากในการจัดการสายอักขระ - พื้นที่ที่แม้กระทั่งทุกวันนี้หลาย ๆ ภาษาดูเหมือนจะเป็นโลหิตจาง


ไม่มีสิ่งใดที่จะป้องกันไม่ให้char* arrชี้ไปที่โครงสร้างของแบบฟอร์มstruct { int length; char characters[ANYSIZE_ARRAY] };หรือสิ่งที่คล้ายกันซึ่งยังคงสามารถผ่านได้เป็นพารามิเตอร์เดียว
Billy ONeal

@BillyONeal: สองปัญหาเกี่ยวกับวิธีการที่: (1) มันจะอนุญาตให้ผ่านสายโดยรวมในขณะที่วิธีการปัจจุบันยังช่วยให้ผ่านหางของสตริง; (2) จะเสียพื้นที่มากเมื่อใช้กับสตริงขนาดเล็ก หาก K&R ต้องการใช้เวลากับสายอักขระพวกเขาสามารถทำให้สิ่งต่าง ๆ แข็งแกร่งขึ้น แต่ฉันไม่คิดว่าพวกเขาตั้งใจว่าภาษาใหม่ของพวกเขาจะถูกใช้งานในอีกสิบปีต่อมาน้อยกว่าสี่สิบ
supercat

1
บิตเกี่ยวกับการประชุมที่เรียกนี้เป็นเรื่องที่ไม่มีอะไรเกี่ยวข้องกับความเป็นจริง ... มันไม่ได้เป็นการพิจารณาในการออกแบบ และอนุสัญญาการโทรที่อิงตามการลงทะเบียนนั้นได้ถูก "ประดิษฐ์" แล้ว นอกจากนี้วิธีการเช่นพอยน์เตอร์สองตัวนั้นไม่ได้เป็นตัวเลือกเนื่องจาก structs ไม่ใช่คลาสแรก ... มีเพียงแบบดั้งเดิมเท่านั้นที่สามารถกำหนดได้หรือผ่านได้ การคัดลอก struct ไม่มาถึงจนกว่า UNIX V7 ต้องการ memcpy (ซึ่งยังไม่มี) เพียงเพื่อคัดลอกตัวชี้สตริงเป็นเรื่องตลก ลองเขียนโปรแกรมเต็มรูปแบบไม่ใช่แค่ฟังก์ชั่นที่แยกออกมาถ้าคุณทำข้ออ้างในการออกแบบภาษา
Jim Balter

1
"มีแนวโน้มมากที่สุดเพราะพวกเขาไม่ต้องการใช้ความพยายามอย่างมากในการจัดการสตริง" - ไร้สาระ โดเมนแอ็พพลิเคชันทั้งหมดของ UNIX เริ่มต้นคือการจัดการสตริง ถ้ามันไม่ได้เป็นอย่างนั้นเราก็จะไม่เคยได้ยิน
Jim Balter

1
'ฉันไม่คิดว่า "บัฟเฟอร์ถ่านเริ่มต้นด้วย int ที่มีความยาว" เป็นอะไรที่วิเศษกว่านี้ - ถ้าคุณจะstr[n]อ้างถึง char ที่ถูกต้อง เหล่านี้เป็นประเภทของสิ่งที่คนคุยนี้ไม่คิดเกี่ยวกับ
Jim Balter

2

ตามที่โจ Spolsky ในบล็อกโพสต์นี้ ,

เป็นเพราะไมโครโปรเซสเซอร์ PDP-7 ซึ่ง UNIX และภาษาการเขียนโปรแกรม C ถูกคิดค้นมีประเภทสตริง ASCIZ ASCIZ หมายถึง "ASCII ที่มี Z (ศูนย์) ที่ส่วนท้าย"

หลังจากเห็นคำตอบอื่น ๆ ทั้งหมดที่นี่ฉันเชื่อว่าแม้ว่าจะเป็นจริงมันเป็นเพียงส่วนหนึ่งของเหตุผลที่ C มี "สตริง" ที่สิ้นสุดด้วยค่า null โพสต์นั้นค่อนข้างให้ความกระจ่างว่าสิ่งที่ง่าย ๆ อย่างสตริงนั้นจริง ๆ แล้วค่อนข้างยาก


2
ดูสิฉันเคารพ Joel ในหลายเรื่อง แต่นี่คือสิ่งที่เขาคาดเดา คำตอบของ Hans Passant มาจากนักประดิษฐ์ของ C โดยตรง
Billy ONeal

1
ใช่ แต่ถ้าสิ่งที่ Spolsky พูดนั้นเป็นความจริงเลยมันจะเป็นส่วนหนึ่งของ "ความสะดวกสบาย" ที่พวกเขาอ้างถึง นั่นเป็นส่วนหนึ่งว่าทำไมฉันจึงรวมคำตอบนี้ไว้ด้วย
BenK

AFAIK เป็นเพียงคำสั่งผู้ประกอบการสร้างลำดับของไบต์ตามด้วย.ASCIZ 0มันหมายถึงว่าสตริงที่ถูกยกเลิกเป็นศูนย์นั้นเป็นแนวคิดที่ดีในเวลานั้น ไม่ได้หมายความว่าสตริงที่สิ้นสุดการเป็นศูนย์นั้นเป็นสิ่งที่เกี่ยวข้องกับสถาปัตยกรรมของ PDP- * ยกเว้นว่าคุณสามารถเขียนลูปแบบแน่นซึ่งประกอบด้วยMOVB(คัดลอกไบต์) และBNE(สาขาถ้าไบต์สุดท้ายที่คัดลอกไม่ใช่ศูนย์)
Adrian W

มันควรจะแสดงให้เห็นว่า C เป็นภาษาเก่าป้อแป้เสื่อมสภาพ
purec

2

ไม่จำเป็นต้องมีเหตุผลแต่เป็นความแตกต่างกับการเข้ารหัสความยาว

  1. การเข้ารหัสความยาวแบบไดนามิกบางรูปแบบเหนือกว่าการเข้ารหัสความยาวแบบคงที่เท่าที่หน่วยความจำเกี่ยวข้องมันทั้งหมดขึ้นอยู่กับการใช้งาน เพียงแค่ดู UTF-8 เพื่อพิสูจน์ มันเป็นอาเรย์ของตัวละครที่สามารถขยายได้เพื่อเข้ารหัสตัวละครเดียว นี่ใช้บิตเดียวสำหรับแต่ละไบต์ที่ขยายเพิ่ม การเลิกจ้าง NUL ใช้ 8 บิต คำนำหน้าความยาวฉันคิดว่าสามารถเรียกความยาวไม่สิ้นสุดได้ด้วยการใช้ 64 บิต ความถี่ที่คุณพบกับกรณีของบิตพิเศษของคุณเป็นปัจจัยในการตัดสินใจ มีเพียง 1 สายที่มีขนาดใหญ่มากเท่านั้น? ใครจะสนใจถ้าคุณใช้ 8 หรือ 64 บิต สตริงเล็ก ๆ มากมาย (เช่นคำศัพท์ภาษาอังกฤษ)? ค่าใช้จ่ายส่วนนำหน้าของคุณจะเป็นเปอร์เซ็นต์ที่สูง

  2. สายความยาวนำหน้าช่วยให้การประหยัดเวลาคือไม่ได้เป็นสิ่งที่จริง ไม่ว่าข้อมูลที่คุณให้มานั้นจะต้องมีความยาวหรือไม่ก็ตามคุณกำลังนับในเวลาคอมไพล์หรือคุณได้รับข้อมูลแบบไดนามิกที่คุณต้องเข้ารหัสเป็นสตริงอย่างแท้จริง ขนาดเหล่านี้ถูกคำนวณ ณ จุดหนึ่งในอัลกอริทึม ตัวแปรที่แยกต่างหากเพื่อเก็บขนาดของสตริงยกเลิกสามารถจะให้ ซึ่งทำให้การเปรียบเทียบใน moot ประหยัดเวลา ในตอนท้ายหนึ่งมี NUL พิเศษในตอนท้าย ... แต่ถ้าการเข้ารหัสความยาวไม่รวม NUL นั้นก็ไม่มีความแตกต่างระหว่างทั้งสอง ไม่จำเป็นต้องทำการเปลี่ยนแปลงอัลกอริทึมเลย เพียง pre-pass คุณต้องออกแบบด้วยตัวเองแทนที่จะต้องคอมไพเลอร์ / รันไทม์ทำเพื่อคุณ C ส่วนใหญ่เกี่ยวกับการทำสิ่งต่าง ๆ ด้วยตนเอง

  3. ความยาวส่วนเติมหน้าเป็นทางเลือกเป็นจุดขาย ฉันไม่ต้องการข้อมูลเพิ่มเติมสำหรับอัลกอริทึมเสมอไปดังนั้นการกำหนดให้ทุกสตริงทำให้เวลาในการคำนวณล่วงหน้าของฉัน + ไม่สามารถลดลงต่ำกว่า O (n) ได้ (เช่นตัวสร้างตัวเลขสุ่มฮาร์ดแวร์ 1-128 ฉันสามารถดึงจาก "สตริงอนันต์" สมมติว่ามันสร้างตัวละครได้อย่างรวดเร็วดังนั้นความยาวสตริงของเราเปลี่ยนตลอดเวลา แต่การใช้ข้อมูลของฉันอาจไม่สนใจ ไบต์สุ่มจำนวนมากที่ฉันมีมันแค่ต้องการไบต์ที่ไม่ได้ใช้ถัดไปที่มีอยู่ทันทีที่สามารถรับได้หลังจากการร้องขอฉันอาจรอบนอุปกรณ์ แต่ฉันอาจมีบัฟเฟอร์ของอักขระอ่านล่วงหน้าการเปรียบเทียบความยาวคือ การคำนวณที่สิ้นเปลืองโดยไม่จำเป็นการตรวจสอบแบบ null มีประสิทธิภาพมากกว่า)

  4. ความยาวส่วนนำหน้าเป็นตัวป้องกันที่ดีสำหรับบัฟเฟอร์ล้นหรือไม่ ดังนั้นการใช้งานฟังก์ชั่นห้องสมุดและการใช้งานอย่างมีสติ ถ้าฉันส่งผ่านข้อมูลที่มีรูปแบบไม่ถูกต้อง บัฟเฟอร์ของฉันมีความยาว 2 ไบต์ แต่ฉันบอกฟังก์ชั่นว่าเป็น 7! ตัวอย่าง:ถ้าได้รับ ()มีจุดประสงค์เพื่อใช้กับข้อมูลที่รู้จักอาจมีการตรวจสอบบัฟเฟอร์ภายในที่ทดสอบบัฟเฟอร์ที่คอมไพล์แล้วและmalloc ()โทรและยังคงติดตามสเป็ค ถ้ามันตั้งใจจะใช้เป็นไพพ์สำหรับ STDIN ที่ไม่รู้จักเพื่อมาถึงบัฟเฟอร์ที่ไม่รู้จักอย่างชัดเจนว่าไม่มีใครรู้ว่าขนาดบัฟเฟอร์นั้นหมายถึงความยาว arg ไม่มีจุดหมายคุณต้องการสิ่งอื่นที่นี่เช่นการตรวจสอบนกขมิ้น สำหรับเรื่องนั้นคุณไม่สามารถเติมคำนำหน้าให้กับสตรีมและอินพุตบางอย่างได้ แต่คุณทำไม่ได้ ซึ่งหมายถึงการตรวจสอบความยาวจะต้องสร้างขึ้นในอัลกอริธึมไม่ใช่ส่วนที่มหัศจรรย์ของระบบการพิมพ์ TL; DR NUL ที่ถูกยกเลิกไม่จำเป็นที่จะไม่ปลอดภัย แต่มันก็จบลงด้วยการใช้งานในทางที่ผิด

  5. จุดเคาน์เตอร์ตัวนับ: การเลิกจ้าง NUL นั้นสร้างความรำคาญให้กับไบนารี คุณต้องทำ pre-length ที่นี่หรือแปลง NUL bytes ในบางวิธี: escape-codes, range remapping, ฯลฯ ... ซึ่งแน่นอนว่าหมายถึงหน่วยความจำเพิ่มเติมที่ใช้งาน / ลดข้อมูล / ปฏิบัติการเพิ่มเติมต่อไบต์ คำนำหน้าความยาวส่วนใหญ่ชนะสงครามที่นี่ อัพไซด์เพียงอย่างเดียวสำหรับการแปลงคือไม่ต้องเขียนฟังก์ชันเพิ่มเติมเพื่อให้ครอบคลุมสตริงคำนำหน้ายาว ซึ่งหมายความว่าในรูทีนย่อย O (n) ที่ได้รับการปรับให้เหมาะสมที่สุดของคุณคุณสามารถให้พวกมันทำหน้าที่เทียบเท่า O (n) ของพวกเขาโดยอัตโนมัติโดยไม่ต้องเพิ่มโค้ดอีก ข้อเสียคือแน่นอนเสียเวลา / หน่วยความจำ / การบีบอัดเมื่อใช้กับสตริงหนัก NULขึ้นอยู่กับจำนวนไลบรารีที่คุณทำซ้ำเพื่อทำงานกับข้อมูลไบนารีมันอาจจะเหมาะสมที่จะทำงานกับสตริงคำนำหน้ายาวเท่านั้น ที่กล่าวว่าหนึ่งยังสามารถทำเช่นเดียวกันกับสายยาวคำนำหน้า ... ความยาว -1 อาจหมายถึง NUL สิ้นสุดและคุณสามารถใช้ NUL- สิ้นสุดสตริงภายในสิ้นสุดความยาว

  6. Concat: "O (n + m) vs O (m)"ฉันสมมติว่าคุณอ้างถึง m เป็นความยาวทั้งหมดของสตริงหลังจากเชื่อมต่อกันเพราะพวกเขาทั้งคู่ต้องมีจำนวนขั้นต่ำในการดำเนินการ - บนสตริง 1 จะเกิดอะไรขึ้นถ้าคุณต้องจัดสรรใหม่?) และฉันสมมติว่า n เป็นจำนวนการดำเนินการในตำนานที่คุณไม่ต้องทำอีกต่อไปเพราะการคำนวณล่วงหน้า ถ้าเป็นเช่นนั้นคำตอบนั้นง่าย: คำนวณล่วงหน้า ถ้าคุณยืนยันว่าคุณมีหน่วยความจำเพียงพอที่จะไม่จำเป็นต้องจัดสรรใหม่และนั่นคือพื้นฐานของสัญกรณ์ O ขนาดใหญ่ดังนั้นคำตอบนั้นง่ายยิ่งขึ้น: ทำการค้นหาไบนารีบนหน่วยความจำที่จัดสรรสำหรับจุดสิ้นสุดของสตริง 1 ชัดเจนว่ามีขนาดใหญ่ แถบของศูนย์อนันต์หลังจากสตริง 1 เพื่อให้เราไม่ต้องกังวลเกี่ยวกับการจัดสรรใหม่ ที่นั่นได้อย่างง่ายดายเพื่อเข้าสู่ระบบ n (n) และฉันพยายามแทบจะไม่ ซึ่งถ้าคุณจำ log (n) นั้นมีขนาดใหญ่เท่ากับ 64 บนคอมพิวเตอร์จริงซึ่งก็เหมือนกับการพูดว่า O (64 + m) ซึ่งก็คือ O (m) (และใช่แล้วตรรกะนั้นถูกนำมาใช้ในการวิเคราะห์โครงสร้างข้อมูลจริงที่ใช้งานอยู่ในปัจจุบันซึ่งไม่ได้เป็นเรื่องที่น่ารังเกียจเลย

  7. Concat () / Len () อีกครั้ง : บันทึกผลลัพธ์ ง่าย. เปลี่ยนการคำนวณทั้งหมดเป็นการคำนวณล่วงหน้าหากเป็นไปได้ / จำเป็น นี่เป็นการตัดสินใจแบบอัลกอริทึม มันไม่ได้เป็นข้อ จำกัด ของภาษา

  8. การผ่านส่วนต่อท้ายของสตริงนั้นทำได้ง่ายขึ้น / เป็นไปได้ด้วย NUL ขึ้นอยู่กับว่ามีการนำคำนำหน้าความยาวมาใช้อย่างไรมันสามารถทำลายสตริงเดิมได้และบางครั้งก็ไม่สามารถทำได้ ต้องการสำเนาและส่ง O (n) แทน O (1)

  9. การส่งผ่านอาร์กิวเมนต์ / การยกเลิกการอ้างอิงมีค่าน้อยกว่าสำหรับการยกเลิก NUL เมื่อเทียบกับส่วนนำหน้าความยาว เห็นได้ชัดว่าคุณกำลังส่งผ่านข้อมูลน้อยลง หากคุณไม่ต้องการความยาวก็จะช่วยประหยัดพื้นที่ได้มากและช่วยเพิ่มประสิทธิภาพ

  10. คุณสามารถโกง มันเป็นเพียงตัวชี้ ใครบอกว่าคุณต้องอ่านมันเป็นสตริง? ถ้าคุณต้องการอ่านมันเป็นตัวละครเดียวหรือลอย ถ้าคุณต้องการทำสิ่งที่ตรงกันข้ามและอ่านการลอยเป็นสตริง? หากคุณระมัดระวังคุณสามารถทำสิ่งนี้ได้ด้วยการยกเลิก NUL คุณไม่สามารถทำสิ่งนี้ด้วยคำนำหน้ายาวได้มันเป็นประเภทข้อมูลที่แตกต่างจากตัวชี้โดยทั่วไป คุณมักจะต้องสร้างสตริงไบต์ต่อไบต์และรับความยาว แน่นอนถ้าคุณต้องการบางสิ่งที่เหมือนลอยทั้งหมด (อาจมี NUL อยู่ข้างใน) คุณจะต้องอ่านไบต์ต่อไบต์ แต่รายละเอียดจะเหลือไว้ให้คุณตัดสินใจ

TL; DRคุณใช้ข้อมูลไบนารี่หรือไม่? ถ้าไม่ใช่การยกเลิก NUL จะช่วยให้มีอัลกอริธึมอิสระมากขึ้น ถ้าใช่แล้วรหัสปริมาณเทียบกับความเร็ว / หน่วยความจำ / การบีบอัดเป็นปัญหาหลักของคุณ การผสมผสานระหว่างสองวิธีหรือการบันทึกช่วยจำอาจจะดีที่สุด


9เป็นคนนอกฐาน / ผิดแทน ความยาวการแก้ไขล่วงหน้าไม่มีปัญหานี้ Lenth ผ่านเป็นตัวแปรแยกกัน เรากำลังพูดถึง pre-fiix แต่ฉันถูกพาไป ยังเป็นเรื่องดีที่จะคิดดังนั้นฉันจะทิ้งมันไว้ : d
Black

1

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

พัฒนา Windows / COM จะคุ้นเคยกับBSTRชนิดซึ่งเป็นที่แน่นอนเช่นนี้ - ความยาวนำหน้าซีสตริงที่ข้อมูลตัวละครที่เกิดขึ้นจริงจะเริ่มต้นไม่ได้อยู่ที่ 0 ไบต์

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


-3

gcc ยอมรับรหัสด้านล่าง:

ถ่าน s [4] = "abcd";

และมันก็โอเคถ้าเราถือว่าเป็นอาร์เรย์ของตัวอักษร แต่ไม่ใช่สตริง นั่นคือเราสามารถเข้าถึงได้ด้วย s [0], s [1], s [2] และ s [3] หรือแม้กระทั่งกับ memcpy (dest, s, 4) แต่เราจะได้ตัวละครที่ยุ่งเหยิงเมื่อเราพยายามใส่ (s) หรือแย่กว่านั้นกับ strcpy (dest, s)


@Adrian W. นี่คือที่ถูกต้อง C. สตริงความยาวที่แน่นอนจะใส่ซองพิเศษและไม่ใส่ NUL โดยทั่วไปแล้วการปฏิบัติที่ไม่ฉลาด แต่อาจมีประโยชน์ในกรณีเช่นการเติมโครงสร้างส่วนหัวที่ใช้ FourCC "strings"
Kevin Thibedeau

คุณพูดถูก นี่คือ C ที่ถูกต้องจะรวบรวมและทำงานตามที่อธิบายไว้ kkaaii เหตุผลสำหรับ downvotes (ไม่ใช่ของฉัน ... ) อาจเป็นได้ว่าคำตอบนี้ไม่ได้ตอบคำถามของ OP ในทางใดทางหนึ่ง
Adrian W
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.