เหตุใดบางโปรแกรม C เขียนในไฟล์ต้นฉบับขนาดใหญ่?


88

ตัวอย่างเช่นเครื่องมือSysInternals "FileMon" จากอดีตมีไดรเวอร์โหมดเคอร์เนลที่มีซอร์สโค้ดทั้งหมดในไฟล์ 4,000 บรรทัด เช่นเดียวกันสำหรับโปรแกรม ping แรกที่เคยเขียน (~ 2,000 LOC)

คำตอบ:


143

การใช้หลายไฟล์ต้องใช้ค่าใช้จ่ายในการดูแลระบบเพิ่มเติมเสมอ ต้องทำการติดตั้งสคริปต์การสร้างและ / หรือ makefile ด้วยการรวบรวมและการเชื่อมโยงแยกขั้นตอนตรวจสอบให้แน่ใจว่าการพึ่งพาระหว่างไฟล์ต่าง ๆ ได้รับการจัดการอย่างถูกต้องเขียนสคริปต์ "zip" เพื่อให้ง่ายต่อการแจกจ่ายซอร์สโค้ดด้วยอีเมลหรือดาวน์โหลด บน. โดยทั่วไปแล้ว Modern IDEs ในปัจจุบันมักจะรับภาระจำนวนมาก แต่ฉันค่อนข้างมั่นใจในเวลาที่มีการเขียนโปรแกรม ping แรกไม่มี IDE ดังกล่าวพร้อมใช้งาน และสำหรับไฟล์ที่มีขนาดเล็กถึง ~ 4000 LOC หากไม่มี IDE ที่จัดการไฟล์ได้หลายไฟล์สำหรับคุณการแลกเปลี่ยนระหว่างค่าใช้จ่ายที่กล่าวถึงและประโยชน์จากการใช้ไฟล์หลาย ๆ ไฟล์อาจทำให้ผู้คนตัดสินใจเลือกวิธีการใช้ไฟล์เดียว


9
"และสำหรับไฟล์ที่มีขนาดเล็ก ~ 4000 LOC ... " ตอนนี้ฉันกำลังทำงานเป็น JS dev อยู่ เมื่อฉันมีไฟล์ยาวแค่ 400 บรรทัดฉันก็กังวลว่ามันจะใหญ่แค่ไหน! (แต่เรามีไฟล์หลายสิบไฟล์ในโครงการของเรา)
Kevin

36
@ เควิน: ผมบนศีรษะของฉันมีน้อยเกินไปผมหนึ่งในซุปของฉันมีจำนวนมากเกินไป ;-) AFAIK ในไฟล์ JS หลายไฟล์ไม่ก่อให้เกิดค่าใช้จ่ายในการบริหารที่มากเหมือนกับใน "C ไม่มี IDE สมัยใหม่"
Doc Brown

4
@Kevin JS เป็นสัตว์ร้ายที่ต่างออกไป JS จะถูกส่งไปยังผู้ใช้ปลายทางทุกครั้งที่ผู้ใช้โหลดเว็บไซต์และไม่ได้แคชไว้แล้วโดยเบราว์เซอร์ของพวกเขา C ต้องมีการส่งรหัสเพียงครั้งเดียวจากนั้นบุคคลที่อยู่อีกด้านหนึ่งจะรวบรวมและยังคงรวบรวมอยู่ (เห็นได้ชัดว่ามีข้อยกเว้น แต่เป็นกรณีที่ใช้โดยทั่วไป) นอกจากนี้เนื้อหาของ C ยังมีแนวโน้มที่จะเป็นรหัสดั้งเดิมเช่นเดียวกับโปรเจ็กต์ '4000 บรรทัดเป็นเรื่องปกติ' ที่ผู้คนกำลังอธิบายในความคิดเห็น
Pharap

5
@Kevin ไปดูกันว่า underscore.js (1,700 loc, หนึ่งไฟล์) และ myriad ของไลบรารีอื่น ๆ ที่กระจายกันนั้นเขียนอย่างไร Javascript จริงแล้วเกือบจะไม่ดีเท่ากับ C สำหรับการทำให้เป็นโมดูลและการปรับใช้
Voo

2
@Phap ฉันคิดว่าเขาหมายถึงการใช้บางอย่างเช่นWebpackก่อนที่จะใช้รหัส ด้วย Webpack คุณสามารถทำงานกับไฟล์หลายไฟล์แล้วคอมไพล์ไฟล์เหล่านั้นเป็นหนึ่งชุด
Brian McCutchon

81

เนื่องจาก C ไม่ดีในการทำให้เป็นโมดูล มันทำให้เกิดความยุ่งเหยิง (ไฟล์ส่วนหัวและ #includes ฟังก์ชัน extern ข้อผิดพลาดเกี่ยวกับลิงค์เวลา ฯลฯ ) และยิ่งคุณนำโมดูลเข้ามามากเท่าไหร่ก็ยิ่งมีความยุ่งยากมากขึ้นเท่านั้น

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


38
ฉันคิดว่ามันไม่ยุติธรรมที่จะอธิบายวิธีการแบบ C ว่า 'ผิดพลาด'; พวกเขาตัดสินใจอย่างสมเหตุสมผลและสมเหตุสมผลในเวลาที่พวกเขาทำ
Jack Aidley

14
ไม่มีสิ่งที่เป็นโมดูลที่ซับซ้อนโดยเฉพาะอย่างยิ่ง มันสามารถสร้างความซับซ้อนด้วยรูปแบบการเข้ารหัสที่ไม่ดี แต่ก็ไม่ยากที่จะเข้าใจหรือนำไปใช้และไม่มีใครสามารถจำแนกได้ว่าเป็น "ความผิดพลาด" เหตุผลที่แท้จริงตามคำตอบของ Snowman ก็คือการปรับให้เหมาะสมกับไฟล์ต้นฉบับหลาย ๆ ไฟล์นั้นไม่ดีในอดีตและไดรเวอร์ FileMon นั้นต้องการประสิทธิภาพสูง ตรงกันข้ามกับความคิดเห็นของ OP ไฟล์เหล่านี้ไม่ใช่ไฟล์ขนาดใหญ่โดยเฉพาะ
เกรแฮม

8
@ Graham ไฟล์ใด ๆ ที่มีขนาดใหญ่กว่า 1,000 บรรทัดของรหัสควรได้รับการปฏิบัติเสมือนเป็นกลิ่นรหัส
Mason Wheeler

11
@ JackAidley มันไม่ยุติธรรมเลยการมีบางสิ่งที่ผิดพลาดนั้นไม่ได้เกิดจากการรวมกันเป็นพิเศษกับการพูดว่าเป็นการตัดสินใจที่สมเหตุสมผลในเวลานั้น ข้อผิดพลาดนั้นหลีกเลี่ยงไม่ได้เนื่องจากข้อมูลที่ไม่สมบูรณ์และระยะเวลาที่ จำกัด และควรเรียนรู้จากการไม่ซ่อนหรืออัปยศใหม่เพื่อบันทึกใบหน้า
Jared Smith

8
ใครก็ตามที่อ้างว่าวิธีการของ C ไม่ใช่ความผิดพลาดล้มเหลวในการเข้าใจว่าไฟล์ C แบบสิบซับดูเหมือนจริง ๆ แล้วสามารถเป็นไฟล์หมื่นซับโดยมีส่วนหัวทั้งหมด #include: d ซึ่งหมายความว่าทุกไฟล์เดียวในโครงการของคุณมีประสิทธิภาพอย่างน้อยหมื่นบรรทัดไม่ว่าจำนวนบรรทัดจะได้รับจาก "wc -l" การสนับสนุนที่ดีกว่าสำหรับโมดุลริตี้จะช่วยลดเวลาในการแยกวิเคราะห์และการรวบรวมเป็นเศษเสี้ยวเล็ก ๆ ได้อย่างง่ายดาย
juhist

37

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

ตัวเชื่อมโยงสามารถดำเนินการเพิ่มประสิทธิภาพได้อย่างแน่นอนนอกเหนือจากสิ่งที่คอมไพเลอร์สามารถทำได้ แต่ไม่ใช่ทั้งหมด ตัวอย่างเช่น: ตัวเชื่อมโยงที่ทันสมัยใช้งานได้ดีในการขจัดฟังก์ชั่นที่ไม่ได้รับการยืนยัน พวกเขาอาจสามารถทำการปรับให้เหมาะสมอื่น ๆ ได้ แต่ไม่มีสิ่งใดที่คอมไพเลอร์สามารถทำได้ภายในฟังก์ชั่น

ตัวอย่างหนึ่งที่รู้จักกันดีของโมดูลรหัสเดียวคือ SQLite คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในหน้าการควบรวม SQLite

1. บทสรุปผู้บริหาร

ไฟล์ที่มาแยกกันมากกว่า 100 ไฟล์ถูกต่อกันเป็นไฟล์ขนาดใหญ่ไฟล์เดียวของรหัส C ชื่อ "sqlite3.c" และเรียกว่า "the amalgamation" การรวมมีทุกสิ่งที่แอปพลิเคชันต้องฝัง SQLite ไฟล์การควบรวมกิจการมีความยาวมากกว่า 180,000 บรรทัดและมีขนาด 6 เมกะไบต์ขึ้นไป

การรวมรหัสทั้งหมดสำหรับ SQLite เป็นไฟล์ขนาดใหญ่ทำให้ SQLite ง่ายต่อการปรับใช้ - มีเพียงไฟล์เดียวที่จะติดตาม และเนื่องจากรหัสทั้งหมดอยู่ในหน่วยการแปลเดียวคอมไพเลอร์สามารถปรับปรุงการเพิ่มประสิทธิภาพระหว่างโพรซีเดอร์ให้ดีขึ้นส่งผลให้รหัสเครื่องที่อยู่ระหว่าง 5% ถึง 10% เร็วขึ้น


15
แต่โปรดทราบว่าคอมไพเลอร์ C ที่ทันสมัยสามารถทำการเพิ่มประสิทธิภาพทั้งโปรแกรมของไฟล์ต้นฉบับหลายไฟล์ (แม้ว่าไม่ใช่ถ้าคุณคอมไพล์มันเป็นไฟล์ออบเจกต์แต่ละไฟล์ก่อน)
Davislor

10
@Davislor ดูสคริปต์สร้างทั่วไป: คอมไพเลอร์จะไม่ทำแบบนั้น

4
การเปลี่ยนสคริปต์การสร้างเป็นเรื่องง่าย$(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(CFILES)กว่าการย้ายทุกอย่างเป็นไฟล์ soudce ไฟล์เดียว คุณยังสามารถทำการคอมไพล์โปรแกรมทั้งหมดเป็นเป้าหมายทางเลือกสำหรับสคริปต์บิลด์แบบดั้งเดิมที่ข้ามการคอมไพล์ไฟล์ต้นฉบับที่ไม่มีการเปลี่ยนแปลงเหมือนกับวิธีที่ผู้คนอาจปิดการทำโปรไฟล์และการดีบักสำหรับเป้าหมายการผลิต คุณไม่มีตัวเลือกนั้นถ้าทุกอย่างอยู่ในแหล่งใหญ่ของแหล่งเดียว ไม่ใช่สิ่งที่คนคุ้นเคย แต่ไม่มีอะไรยุ่งยากเกี่ยวกับเรื่องนี้
Davislor

9
@Davislor การเพิ่มประสิทธิภาพโปรแกรมทั้งหมด / การเพิ่มประสิทธิภาพลิงก์เวลา (LTO) ยังทำงานเมื่อคุณ "รวบรวม" รหัสลงในไฟล์วัตถุแต่ละไฟล์ (ขึ้นอยู่กับสิ่งที่ "รวบรวม" หมายถึงคุณ) ตัวอย่างเช่น LTO ของ GCC จะเพิ่มการแยกวิเคราะห์โค้ดในไฟล์ออบเจกต์แต่ละไฟล์ในเวลารวบรวมและ ณ เวลาลิงก์จะใช้อันนั้นแทนโค้ดออบเจ็กต์ (ยังมีอยู่) เพื่อรวบรวมและสร้างโปรแกรมทั้งหมดอีกครั้ง ดังนั้นจึงใช้งานได้กับการตั้งค่าบิลด์ที่คอมไพล์ไฟล์ออบเจกต์แต่ละไฟล์ก่อนแม้ว่ารหัสเครื่องที่สร้างโดยการคอมไพล์เริ่มต้นจะถูกละเว้น
ฝัน

8
JsonCpp ทำเช่นนี้ทุกวันนี้เช่นกัน กุญแจสำคัญคือไฟล์ไม่ได้เป็นอย่างนี้ในระหว่างการพัฒนา
การแข่งขัน Lightness ใน Orbit

15

นอกเหนือจากปัจจัยด้านความเรียบง่ายที่ผู้ถูกกล่าวถึงกล่าวถึงแล้วโปรแกรม C หลายโปรแกรมถูกเขียนขึ้นโดยบุคคลหนึ่ง

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

เมื่อคนคนหนึ่งทำงานด้วยตัวเองนั่นไม่ใช่ปัญหา

โดยส่วนตัวแล้วฉันใช้ไฟล์หลาย ๆ ไฟล์โดยขึ้นอยู่กับฟังก์ชั่นเป็นเรื่องปกติ แต่นั่นเป็นเพียงฉัน


4
@OskarSkog แต่คุณจะไม่แก้ไขไฟล์ในเวลาเดียวกันกับตัวคุณในอนาคต
Loren Pechtel

2

เพราะ C89 ไม่มีinlineฟังก์ชั่น ซึ่งหมายความว่าการแบ่งไฟล์ของคุณออกเป็นฟังก์ชั่นทำให้เกิดโอเวอร์เฮดของการผลักดันค่าบนสแต็กและกระโดดไปมา นี่เป็นการเพิ่มค่าใช้จ่ายเล็กน้อยในการนำรหัสไปใช้ในคำสั่ง switch ขนาดใหญ่ 1 รายการ (วนรอบเหตุการณ์) แต่การวนรอบเหตุการณ์มักจะยากที่จะใช้อย่างมีประสิทธิภาพ (หรือถูกต้อง) มากกว่าโซลูชันที่ทำให้เป็นโมดูลมากขึ้น ดังนั้นสำหรับโครงการขนาดใหญ่ผู้คนยังคงเลือกที่จะทำเป็นโมดูล แต่เมื่อพวกเขามีความคิดในการออกแบบล่วงหน้าและสามารถควบคุมสถานะในคำสั่งสวิตช์ 1 พวกเขาเลือกที่

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


2
ฟังก์ชั่น C อาจเป็นแบบอินไลน์มากถึง 89 ในวันนี้อินไลน์เป็นสิ่งที่ไม่ควรใช้เลย - คอมไพเลอร์รู้ดีกว่าคุณในเกือบทุกสถานการณ์ และไฟล์ LOC 4k เหล่านั้นส่วนใหญ่ไม่ใช่ฟังก์ชั่นขนาดมหึมานั่นคือรูปแบบการเข้ารหัสที่น่ากลัวซึ่งจะไม่มีประโยชน์ด้านประสิทธิภาพที่เห็นได้ชัดเจนเช่นกัน
Voo

@Voo ฉันไม่รู้ว่าทำไมคุณถึงพูดถึงรูปแบบการเข้ารหัส ฉันไม่ได้เรียกร้องมัน ในความเป็นจริงฉันพูดถึงว่าในกรณีส่วนใหญ่รับประกันการแก้ปัญหาที่มีประสิทธิภาพน้อยลงเนื่องจากการใช้งานไม่เรียบร้อย ฉันยังบอกว่ามันเป็นความคิดที่ไม่ดีเพราะมันไม่ได้ปรับขนาด (สำหรับโครงการขนาดใหญ่) ต้องบอกว่าในลูปที่แน่นมาก (ซึ่งเป็นสิ่งที่เกิดขึ้นในรหัสเครือข่ายใกล้กับฮาร์ดแวร์) การผลักดันและเปิดค่า p กองบน / ปิดสแต็ก (เมื่อเรียกใช้ฟังก์ชัน) โดยไม่จำเป็น นี่ไม่ใช่ทางออกที่ดี แต่มันเป็นสิ่งที่ดีที่สุดในเวลานั้น
Dmitry Rubanovich

2
ข้อผูกมัด: คำหลักแบบอินไลน์มีเพียงเล็กน้อยที่เกี่ยวข้องกับการเพิ่มประสิทธิภาพแบบอินไลน์ มันไม่ได้เป็นคำแนะนำพิเศษสำหรับคอมไพเลอร์ในการเพิ่มประสิทธิภาพ แต่แทนที่จะเกี่ยวข้องกับการเชื่อมโยงกับสัญลักษณ์ที่ซ้ำกัน
hyde

@Dmitry ประเด็นคือการอ้างว่าเพราะไม่มีinlineคำหลักในคอมไพเลอร์ C89 ไม่สามารถอินไลน์ได้ซึ่งเป็นสาเหตุที่คุณต้องเขียนทุกอย่างในฟังก์ชั่นยักษ์หนึ่งอันไม่ถูกต้อง คุณไม่ควรใช้inlineเป็นเครื่องมือเพิ่มประสิทธิภาพการทำงานโดยทั่วไปคอมไพเลอร์จะรู้ดีกว่าคุณ แต่อย่างใด (และอาจเพิกเฉยต่อคำหลัก)
Voo

@Voo: โปรแกรมเมอร์และคอมไพเลอร์โดยทั่วไปจะรู้ว่าบางสิ่งอื่น ๆ ไม่ได้ inlineคำหลักที่มีความหมายลิงเกอร์ที่เกี่ยวข้องซึ่งมีความสำคัญมากกว่าคำถามที่ว่าหรือไม่ที่จะดำเนินการเพิ่มประสิทธิภาพในบรรทัด แต่การใช้งานบางรายที่มีคำสั่งอื่น ๆ ที่จะควบคุมในเยื่อบุและสิ่งดังกล่าวบางครั้งอาจเป็นสิ่งที่สำคัญมาก ในบางกรณีฟังก์ชั่นอาจดูเหมือนว่าใหญ่เกินไปที่จะคุ้มค่ากับการใช้งาน แต่การพับแบบคงที่อาจลดขนาดและเวลาในการประมวลผลให้แทบไม่เหลืออะไรเลย คอมไพเลอร์ที่ไม่ได้รับการเขยิบอย่างแรงที่จะสนับสนุนการเข้าแถวอาจไม่ ...
supercat

1

นี่นับเป็นตัวอย่างของวิวัฒนาการซึ่งฉันประหลาดใจยังไม่ได้กล่าวถึง

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

นิสัยที่ปัจจัยสิ่งแวดล้อมเหล่านี้นำไปสู่การดำเนินการพัฒนาอย่างต่อเนื่องและมีการปรับตัวช้าเมื่อเวลาผ่านไป

ในขณะที่กำไรจากการใช้ไฟล์เดียวจะคล้ายกับที่เราได้รับจากการใช้ SSD แทน HDD

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