วิธีที่ Dennis Ritchie และภาษา C ช่วยกำหนด Unix และยังขับเคลื่อนเคอร์เนล อุปกรณ์ฝังตัว และซอฟต์แวร์ความเร็วสูง—รวมถึงสิ่งที่ควรรู้เรื่องการพกพา ประสิทธิภาพ และความปลอดภัย

C เป็นหนึ่งในเทคโนโลยีที่หลายคนแทบไม่ต้องสัมผัสโดยตรง แต่แทบทุกคนพึ่งพามัน หากคุณใช้โทรศัพท์ แล็ปท็อป เราเตอร์ รถ สมาร์ทวอทช์ หรือแม้แต่เครื่องชงกาแฟที่มีหน้าจอ มีโอกาสสูงที่ C จะอยู่ในสแต็กบางจุด—ทำให้เครื่องเริ่มต้น พูดคุยกับฮาร์ดแวร์ หรือทำงานได้เร็วพอจนรู้สึกว่า “ทันที”
สำหรับผู้สร้าง C ยังคงเป็นเครื่องมือใช้งานได้จริงเพราะให้การผสมที่หาได้ยากระหว่างการควบคุมและการพกพา มันสามารถรันใกล้ชิดกับเครื่อง (ทำให้จัดการหน่วยความจำและฮาร์ดแวร์ได้โดยตรง) แต่ก็ย้ายข้าม CPU และระบบปฏิบัติการต่างๆ ได้โดยไม่ต้องเขียนใหม่มากนัก การผสมนี้แทบหาทดแทนยาก
ร่องรอยที่ใหญ่ที่สุดของ C ปรากฏในสามด้าน:
แม้ว่าแอปจะเขียนด้วยภาษาระดับสูง ส่วนของฐานรากหรือโมดูลที่ไวต่อประสิทธิภาพมักย้อนกลับไปยัง C
ชิ้นนี้เชื่อมโยงจุดระหว่าง Dennis Ritchie, เป้าหมายดั้งเดิมของ C, และเหตุผลที่มันยังปรากฏในผลิตภัณฑ์สมัยใหม่ เราจะครอบคลุม:
นี่คือเรื่องของ C โดยเฉพาะ ไม่ใช่ “ภาษาระดับต่ำทั้งหมด” C++ และ Rust อาจถูกยกมาเปรียบเทียบ แต่น้ำหนักอยู่ที่ C ว่าเป็นอะไร ทำไมมันถูกออกแบบเช่นนั้น และทำไมทีมยังเลือกใช้มันในระบบจริง
Dennis Ritchie (1941–2011) เป็นนักวิทยาการคอมพิวเตอร์ชาวอเมริกันที่รู้จักกันดีที่สุดจากงานที่ AT&T’s Bell Labs ซึ่งเป็นองค์กรวิจัยที่มีบทบาทสำคัญในคอมพิวเตอร์และโทรคมนาคมยุคแรก
ที่ Bell Labs ในปลายทศวรรษ 1960 และ 1970 Ritchie ทำงานกับ Ken Thompson และผู้อื่นในการวิจัยระบบปฏิบัติการซึ่งนำไปสู่ Unix Thompson สร้างเวอร์ชันแรกของ Unix; Ritchie กลายเป็นผู้ร่วมสร้างสำคัญเมื่อระบบพัฒนาจนสามารถดูแล ปรับปรุง และแบ่งปันได้อย่างกว้างขวางในวงวิชาการและอุตสาหกรรม
Ritchie ยังสร้างภาษาโปรแกรม C โดยต่อยอดแนวคิดจากภาษาก่อนหน้าที่ใช้ที่ Bell Labs C ถูกออกแบบให้ใช้งานได้จริงสำหรับการเขียนซอฟต์แวร์ระบบ: ให้โปรแกรมเมอร์ควบคุมหน่วยความจำและการแทนข้อมูลโดยตรง ในขณะที่ยังอ่านง่ายและพกพาได้มากกว่าการเขียนทุกอย่างเป็นแอสเซมบลี
การผสมนี้สำคัญเพราะ Unix ถูกเขียนใหม่ด้วย C นี่ไม่ใช่การเขียนใหม่เพราะความสวยงาม—มันทำให้ Unix ย้ายไปฮาร์ดแวร์ใหม่ได้ง่ายและขยายต่อได้ ความสำเร็จนี้สร้างลูปตอบรับที่ทรงพลัง: Unix ให้กรณีการใช้งานที่เข้มงวดสำหรับ C และ C ทำให้ Unix ถูกนำไปใช้เกินกว่าเครื่องเดียวได้ง่ายขึ้น
ร่วมกัน Unix และ C ช่วยกำหนดแนวคิด “การเขียนโปรแกรมระบบ” อย่างที่เรารู้: การสร้างระบบปฏิบัติการ ไลบรารีแกน และเครื่องมือด้วยภาษาที่ใกล้เครื่องแต่ไม่ผูกกับโปรเซสเซอร์เดียว อิทธิพลของพวกมันปรากฏในระบบปฏิบัติการรุ่นหลัง เครื่องมือสำหรับนักพัฒนา และคอนเวนชันที่วิศวกรหลายคนยังเรียนรู้ในวันนี้—ไม่ใช่เพราะตำนาน แต่เพราะแนวทางนี้ใช้ได้ผลเมื่อขยายขนาด
ระบบปฏิบัติการยุคแรกมักเขียนด้วยภาษาแอสเซมบลี ซึ่งให้การควบคุมฮาร์ดแวร์เต็มที่ แต่ก็ทำให้การเปลี่ยนแปลงช้า เกิดข้อผิดพลาดง่าย และผูกติดกับโปรเซสเซอร์หนึ่งตัว ทุกการเพิ่มฟีเจอร์เล็กๆ อาจต้องโค้ดระดับต่ำหลายหน้า และการย้ายระบบไปเครื่องอื่นมักหมายถึงการเขียนใหม่ส่วนใหญ่ตั้งแต่ต้น
Dennis Ritchie ไม่ได้ประดิษฐ์ C ในสุญญากาศ มันเติบโตมาจากภาษาระบบที่เรียบง่ายก่อนหน้าใน Bell Labs
C ถูกสร้างให้แมปชัดเจนกับสิ่งที่คอมพิวเตอร์ทำจริง: ไบต์ในหน่วยความจำ การคำนวณบนรีจิสเตอร์ และการกระโดดผ่านโค้ด นั่นคือเหตุผลที่ชนิดข้อมูลเรียบง่าย การเข้าถึงหน่วยความจำโดยชัดเจน และโอเปอเรเตอร์ที่ตรงกับคำสั่ง CPU เป็นหัวใจของภาษา คุณสามารถเขียนโค้ดที่สูงพอจะจัดการโค้ดเบสใหญ่ แต่ยังตรงพอที่จะควบคุมการจัดวางในหน่วยความจำและประสิทธิภาพได้
"พกพาได้" หมายถึงคุณสามารถนำซอร์ส C เดียวกันไปคอมไพล์บนเครื่องอื่นแล้วด้วยการเปลี่ยนแปลงเล็กน้อยจะได้พฤติกรรมเหมือนเดิม แทนที่จะเขียนระบบปฏิบัติการใหม่สำหรับโปรเซสเซอร์ทุกตัว ทีมสามารถเก็บโค้ดส่วนใหญ่ไว้และเปลี่ยนแค่ส่วนเล็กๆ ที่ขึ้นกับฮาร์ดแวร์ การผสมนี้—โค้ดที่ใช้ร่วมกันมาก ส่วนที่ขึ้นกับเครื่องน้อย—คือข้อค้นพบที่ช่วยให้ Unix แพร่หลาย
ความเร็วของ C ไม่ใช่เวทมนตร์—มันเป็นผลจากการแมปตรงกับสิ่งที่เครื่องทำ และมี "งานเพิ่มเติม" น้อยที่แทรกระหว่างโค้ดของคุณกับ CPU
โดยทั่วไป C ถูก คอมไพล์ นั่นหมายความว่าคุณเขียนซอร์สที่อ่านได้สำหรับมนุษย์ แล้ว คอมไพเลอร์ แปลให้เป็น โค้ดเครื่อง: คำสั่งดิบที่โปรเซสเซอร์ของคุณรัน
ในทางปฏิบัติ คอมไพเลอร์สร้างไฟล์เอ็กซีคิวเทเบิล (หรือออบเจกต์ไฟล์ที่ต่อเป็นไฟล์เดียว) ประเด็นสำคัญคือผลลัพธ์สุดท้ายไม่ได้ถูกตีความทีละบรรทัดระหว่างรันไทม์—มันอยู่ในรูปแบบที่ CPU เข้าใจแล้ว ซึ่งลดโอเวอร์เฮด
C ให้บล็อกก่อสร้างเรียบง่าย: ฟังก์ชัน ลูป จำนวนเต็ม อาเรย์ และ pointer เพราะภาษามีขนาดเล็กและชัดเจน คอมไพเลอร์มักสร้างโค้ดเครื่องที่ตรงไปตรงมา
โดยทั่วไปไม่มีรันไทม์บังคับที่ทำงานเบื้องหลัง เช่น ติดตามทุกออบเจกต์ แทรกการตรวจสอบแอบแฝง หรือจัดการเมตาดาตุซับซ้อน เมื่อต้องการลูป คุณมักจะได้ลูปจริงๆ เมื่อต้องการเข้าถึงอาเรย์ คุณมักจะได้การเข้าถึงหน่วยความจำโดยตรง ความ คาดเดาได้ นี้เป็นเหตุผลสำคัญที่ C ทำงานได้ดีในส่วนที่ต้องการประสิทธิภาพ
C ใช้ การจัดการหน่วยความจำด้วยตนเอง ซึ่งหมายความว่าโปรแกรมร้องขอหน่วยความจำโดยชัดเจน (เช่น malloc) และปล่อยโดยชัดเจน (เช่น free) สิ่งนี้มีอยู่เพราะซอฟต์แวร์ระดับระบบมักต้องการการควบคุมละเอียดว่า เมื่อใด หน่วยความจำถูกจัดสรร เท่าใด และ นานเท่าใด โดยมีโอเวอร์เฮดแอบแฝงขั้นต่ำ
ข้อแลกเปลี่ยนชัดเจน: การควบคุมมากขึ้นอาจหมายถึงความเร็วและประสิทธิภาพมากขึ้น แต่ก็มาพร้อมความรับผิดชอบ หากคุณลืมปล่อยหน่วยความจำ ปล่อยซ้ำ หรือใช้หน่วยความจำหลังจากปล่อยแล้ว บั๊กอาจร้ายแรงและบางครั้งเกี่ยวข้องกับความปลอดภัย
ระบบปฏิบัติการอยู่ที่ขอบระหว่างซอฟต์แวร์กับฮาร์ดแวร์ เคอร์เนลต้องจัดการหน่วยความจำ จัดคิว CPU จัดการการขัดจังหวะ พูดคุยกับอุปกรณ์ และให้ system call ที่ทุกอย่างพึ่งพา งานเหล่านี้ไม่ใช่นามธรรม—มันเกี่ยวกับการอ่านและเขียนตำแหน่งหน่วยความจำเฉพาะ การทำงานกับรีจิสเตอร์ CPU และตอบสนองต่อเหตุการณ์ที่มาถึงในเวลาที่ไม่สะดวก
ไดร์เวอร์และเคอร์เนลต้องการภาษาที่แสดง "ทำสิ่งนี้ให้ตรงๆ" โดยไม่มีงานแอบแฝง ในทางปฏิบัติหมายความว่า:
C เหมาะเพราะโมเดลพื้นฐานคือไบต์ ที่อยู่ และการควบคุมแบบง่าย ไม่มีรันไทม์บังคับ garbage collector หรือระบบออบเจกต์ที่เคอร์เนลต้องโฮสต์ก่อนจะบูตได้
งานของ Unix และระบบยุคแรกทำให้แนวทางที่ Dennis Ritchie ช่วยกำหนดได้รับความนิยม: ใช้ภาษาที่พกพาได้สำหรับส่วนใหญ่ของ OS แต่เก็บ "ขอบฮาร์ดแวร์" ให้บาง โมเดิร์นเคอร์เนลหลายตัวยังคงทำตามรูปแบบนี้ แม้ต้องใช้แอสเซมบลีบ้าง (โค้ดบูต สลับบริบท) C มักยังรับผิดชอบส่วนใหญ่ของการใช้งาน
C ยังครองไลบรารีแกน—ส่วนประกอบอย่างไลบรารีมาตรฐาน C โค้ดเครือข่ายพื้นฐาน และชิ้นส่วนรันไทม์ระดับต่ำที่ภาษาระดับสูงมักพึ่งพา หากคุณใช้ Linux, BSD, macOS, Windows หรือ RTOS คุณแทบจะต้องพึ่งโค้ด C โดยที่อาจไม่รู้ตัว
แรงดึงดูดของ C ในงาน OS ไม่ใช่เรื่องวินเทจแต่เป็นเรื่องเศรษฐศาสตร์วิศวกรรม:
Rust, C++ และภาษาอื่นๆ ถูกใช้ในบางส่วนของระบบปฏิบัติการ และพวกมันนำข้อดีจริงมา แต่ C ยังคงเป็นตัวหารร่วม: ภาษาที่เคอร์เนลหลายตัวถูกเขียนด้วย อินเทอร์เฟซระดับต่ำจำนวนมากคาดหวัง C เป็นฐานที่ภาษาระบบอื่นต้องทำงานร่วมด้วย
"ฝังตัว" มักหมายถึงคอมพิวเตอร์ที่คุณไม่คิดว่าเป็นคอมพิวเตอร์: ไมโครคอนโทรลเลอร์ในเทอร์โมสตัท ลำโพงอัจฉริยะ เราเตอร์ รถ อุปกรณ์การแพทย์ เซนเซอร์โรงงาน และเครื่องใช้มากมาย ระบบเหล่านี้มักรันงานเดียวเป็นเวลาหลายปีอย่างเงียบ ๆ ภายใต้ข้อจำกัดด้านต้นทุน พลังงาน และหน่วยความจำ
เป้าหมายฝังตัวหลายตัวมีหน่วยความจำเป็นกิโลไบต์ (ไม่ใช่กิกะไบต์) และพื้นที่แฟลชจำกัดสำหรับโค้ด บางชุดรันด้วยแบตเตอรี่และต้องนอนหลับส่วนใหญ่ของเวลา บางชุดมีเส้นตายเรียลไทม์—หากลูปควบคุมมอเตอร์ช้าไปไม่กี่มิลลิวินาที ฮาร์ดแวร์อาจทำงานผิดพลาด
ข้อจำกัดเหล่านี้กำหนดทุกรายการตัดสินใจ: ขนาดโปรแกรม ความถี่การตื่น และความคาดเดาได้ของเวลา
C มักสร้างไบนารีขนาดเล็กพร้อมโอเวอร์เฮดรันไทม์น้อย ไม่มี VM บังคับ และมักหลีกเลี่ยงการจัดสรรแบบไดนามิกทั้งหมดได้ สิ่งนี้มีความหมายเมื่อคุณพยายามยัดเฟิร์มแวร์เข้าแฟลชที่จำกัดหรือรับประกันว่าอุปกรณ์จะไม่ "หยุดชะงัก" โดยไม่คาดคิด
สำคัญเท่าเทียมกันคือ C ทำให้สื่อสารกับฮาร์ดแวร์ง่าย ชิปฝังตัวเปิดเผยเพริฟเฟอรัล—พิน GPIO ตัวจับเวลา บัส UART/SPI/I2C—ผ่านรีจิสเตอร์แมปหน่วยความจำ โมเดลของ C แมปได้อย่างเป็นธรรมชาติ: คุณสามารถอ่านและเขียนที่อยู่เฉพาะ ควบคุมบิตเดี่ยว และทำได้โดยมีนามธรรมแทรกน้อยมาก
โค้ดฝังตัว C มักเป็น:
ไม่ว่าจะอย่างไร คุณจะเห็นโค้ดที่ทำงานกับรีจิสเตอร์ฮาร์ดแวร์ (มักมี volatile) บัฟเฟอร์ขนาดคงที่ และการจับจังหวะอย่างระมัดระวัง สไตล์ที่ "ใกล้เครื่อง" นี้เป็นเหตุผลหลักที่ทำให้ C เป็นตัวเลือกเริ่มต้นสำหรับเฟิร์มแวร์ที่ต้องเล็ก ประหยัดพลังงาน และเชื่อถือได้ภายใต้เส้นตาย
"ต้องการประสิทธิภาพ" คือสถานการณ์ที่เวลาและทรัพยากรเป็นส่วนของผลิตภัณฑ์: มิลลิวินาทีมีผลต่อประสบการณ์ผู้ใช้ รอบซีพียูมีผลต่อค่าใช้จ่ายของเซิร์ฟเวอร์ และการใช้หน่วยความจำมีผลต่อการที่โปรแกรมจะรันได้หรือไม่ ในที่เหล่านี้ C ยังคงเป็นตัวเลือกเริ่มต้นเพราะให้ทีมควบคุมการจัดวางข้อมูลในหน่วยความจำ การจัดตารางงาน และสิ่งที่คอมไพเลอร์อนุญาตให้ปรับแต่งได้
คุณมักพบ C อยู่ในแกนกลางของระบบที่ทำงานปริมาณมากหรือมีงบประมาณความหน่วงต่ำ:
โดเมนเหล่านี้ไม่ใช่ "เร็ว" ตลอดทั้งโปรแกรม แต่มักมีลูปภายในเล็กๆ ที่ครอบงำเวลาในการรัน
ทีมมักไม่เขียนผลิตภัณฑ์ทั้งชิ้นใน C เพียงเพื่อให้มันเร็วขึ้น แต่จะโปรไฟล์ หา hot path (ส่วนเล็กๆ ที่ใช้เวลาส่วนใหญ่) และปรับจูนจุดนั้น
C ช่วยเพราะ hot path มักถูกจำกัดด้วยรายละเอียดระดับต่ำ: รูปแบบการเข้าถึงหน่วยความจำ พฤติกรรมแคช การพยากรณ์สาขา และโอเวอร์เฮดการจัดสรร เมื่อคุณปรับโครงสร้างข้อมูล หลีกเลี่ยงการคัดลอกที่ไม่จำเป็น และควบคุมการจัดสรร ผลลัพธ์ด้านความเร็วอาจโดดเด่น—โดยไม่ต้องแตะส่วนที่เหลือของแอปพลิเคชัน
ผลิตภัณฑ์สมัยใหม่มักเป็น "หลายภาษา": Python, Java, JavaScript หรือ Rust สำหรับส่วนใหญ่ของโค้ด และ C สำหรับแกนสำคัญ
วิธีการบูรณาการทั่วไปรวมถึง:
โมเดลนี้รักษาความเป็นไปได้ในการพัฒนา: คุณได้การวนซ้ำอย่างรวดเร็วในภาษาระดับสูง และประสิทธิภาพที่คาดเดาได้ในจุดที่สำคัญ ข้อแลกเปลี่ยนคือความระมัดระวังรอบขอบเขต—การแปลงข้อมูล กฎการเป็นเจ้าของ และการจัดการข้อผิดพลาด—เพราะการข้ามเส้น FFI ควรมีประสิทธิภาพและปลอดภัย
C ยังคงสำคัญเพราะรวมการควบคุมระดับต่ำ (หน่วยความจำ, การจัดวางข้อมูล, การเข้าถึงฮาร์ดแวร์) กับความสามารถในการพกพาได้กว้าง ซึ่งทำให้เป็นตัวเลือกที่ใช้งานได้จริงสำหรับโค้ดที่ต้องบูตเครื่อง ทำงานภายใต้ข้อจำกัดเข้มงวด หรือให้ประสิทธิภาพที่คาดเดาได้
C ยังคงใช้อย่างแพร่หลายใน:
แม้ว่าส่วนใหญ่ของแอปจะเขียนด้วยภาษาระดับสูง แต่พื้นฐานที่สำคัญมักพึ่งพาโค้ด C อยู่เสมอ
Dennis Ritchie สร้างภาษา C ที่ Bell Labs เพื่อให้การเขียนซอฟต์แวร์ระบบเป็นไปได้จริง: ใกล้ชิดกับเครื่อง แต่พกพาได้และดูแลรักษาได้ง่ายกว่าการเขียนด้วยภาษาแอสเซมบลี จุดพิสูจน์สำคัญคือการ เขียน Unix ใหม่ด้วย C ซึ่งทำให้ Unix ย้ายไปยังฮาร์ดแวร์ใหม่และขยายต่อได้ง่ายขึ้นตามกาลเวลา
พูดง่ายๆ ว่า "พกพาได้" หมายถึงคุณสามารถคอมไพล์ซอร์สโค้ด C เดียวกันบน CPU/ระบบปฏิบัติการต่างกันแล้วได้พฤติกรรมที่สอดคล้องกันโดยแทบไม่ต้องแก้โค้ด ส่วนใหญ่ของโค้ดจะใช้ร่วมกันได้ และแยกส่วนที่ขึ้นกับฮาร์ดแวร์ออกเป็นโมดูลเล็กๆ
C มักเร็วเพราะมันแมปตรงกับการทำงานของเครื่องและมีค่าโอเวอร์เฮดรันไทม์น้อย คอมไพเลอร์มักสร้างโค้ดเครื่องที่เรียบง่ายสำหรับลูป การคำนวณ และการเข้าถึงหน่วยความจำ ซึ่งช่วยในส่วนที่เป็นวงในที่รันบ่อยและต้องการเวลาตอบสนองต่ำ
โปรแกรม C ส่วนใหญ่ใช้ การจัดการหน่วยความจำด้วยตนเอง:
mallocfreeวิธีนี้ช่วยให้ควบคุมได้ว่าเมื่อใดและเท่าใดที่หน่วยความจำจะถูกใช้ ซึ่งมีค่าสำหรับเคอร์เนล เฟิร์มแวร์ และจุดร้อนของประสิทธิภาพ แต่ข้อแลกเปลี่ยนคือความผิดพลาดอาจทำให้โปรแกรมล่มหรือเกิดช่องโหว่ด้านความปลอดภัยได้
เคอร์เนลและไดร์เวอร์ต้องการ:
C เหมาะเพราะให้การเข้าถึงระดับต่ำพร้อมกับ toolchain ที่มั่นคงและไบนารีที่คาดเดาได้
เป้าหมายฝังตัวมักมี RAM/แฟลชขนาดเล็ก ขีดจำกัดพลังงานเข้มงวด และบางครั้งต้องการเวลาตอบสนองแบบเรียลไทม์ C เหมาะเพราะสร้างไบนารีขนาดเล็ก ไม่มีรันไทม์หนัก และสื่อสารกับอุปกรณ์ผ่านรีจิสเตอร์แมปหน่วยความจำและการขัดจังหวะได้โดยตรง
วิธีทั่วไปคือเก็บส่วนใหญ่ของผลิตภัณฑ์ไว้ในภาษาระดับสูง แล้วเอาเฉพาะ hot path มาเขียนด้วย C วิธีการรวมได้แก่:
จุดสำคัญคือต้องรักษาขอบเขตให้มีประสิทธิภาพ และกำหนดกฎการเป็นเจ้าของข้อมูลและการจัดการข้อผิดพลาดให้ชัดเจน
การทำให้ C ปลอดภัยขึ้นในทางปฏิบัติหมายถึงการผสมวินัยกับเครื่องมือ:
-Wall -Wextra) และแก้ไขวิธีเหล่านี้ลดข้อบกพร่องทั่วไปได้มาก แม้จะไม่สามารถขจัดความเสี่ยงทั้งหมดได้