วิธีที่ดีที่สุดในการเขียนฟังก์ชั่นสำหรับซอฟต์แวร์ฝังตัวเพื่อให้ได้ประสิทธิภาพที่ดีขึ้นคืออะไร [ปิด]


13

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

void setCLK()
{
    // Code to set the clock
}

void setConfig()
{
    // Code to set the config
}

void setSomethingElse()
{
   // 1 line code to write something to a register.
}

จากนั้นมาฟังก์ชั่นอื่น ๆ ที่อยู่ด้านบนของมันที่ใช้รหัส 1 บรรทัดนี้ที่มีฟังก์ชั่นเพื่อตอบสนองวัตถุประสงค์อื่น ๆ ตัวอย่างเช่น:

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

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

ฉันค้นหาและทุกที่ที่พวกเขากล่าวว่ากฎง่ายๆของการเขียนโปรแกรมคือฟังก์ชั่นควรทำงานเพียงงานเดียว

ดังนั้นหากฉันเขียนโดยตรงกับโมดูลฟังก์ชัน InitModule ที่ตั้งนาฬิกาเพิ่มการกำหนดค่าที่ต้องการและทำอย่างอื่นโดยไม่ต้องเรียกฟังก์ชั่น มันเป็นวิธีที่ไม่ดีเมื่อเขียนซอฟต์แวร์ฝังตัว?


แก้ไข 2:

  1. ดูเหมือนว่าผู้คนจำนวนมากเข้าใจคำถามนี้ราวกับว่าฉันกำลังพยายามปรับโปรแกรมให้เหมาะสม ไม่มีผมมีความตั้งใจที่จะทำอะไรไม่ได้ ฉันปล่อยให้คอมไพเลอร์ทำมันเพราะมันจะเป็นไปได้เสมอ (ฉันหวังว่าจะไม่ได้!) ดีกว่าฉัน

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

โปรดพิจารณาให้สามารถอ่านได้กำหนดไว้ในคำตอบของ @Jonk


28
คุณไร้เดียงสามาก (ไม่ได้หมายถึงเป็นการดูถูก) หากคุณเชื่อว่าผู้แปลที่สมเหตุสมผลจะสุ่มเปลี่ยนรหัสตามที่เขียนไว้ในไบนารีตามที่เขียนไว้ คอมไพเลอร์ที่ทันสมัยส่วนใหญ่ค่อนข้างดีในการระบุเมื่อรูทีนนั้นดีกว่าและแม้ว่าเมื่อควรใช้ตำแหน่ง register vs RAM เพื่อเก็บตัวแปร ปฏิบัติตามกฎการเพิ่มประสิทธิภาพสองข้อ: 1) ไม่ปรับให้เหมาะสม 2) ไม่ได้เพิ่มประสิทธิภาพเลย ทำให้โค้ดของคุณสามารถอ่านและบำรุงรักษาได้และจากนั้นหลังจากทำโปรไฟล์ระบบการทำงานให้มองหาการปรับให้เหมาะสม
akohlsmith

10
@akohlsmith IIRC กฎสามข้อในการเพิ่มประสิทธิภาพคือ: 1) อย่า! 2) ไม่จริง ๆ อย่า! 3) โปรไฟล์ก่อนแล้วจึงจะปรับให้เหมาะสมถ้าคุณต้อง - Michael_A._Jackson
esoterik

3
เพียงจำไว้ว่า "การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด (หรืออย่างน้อยที่สุด) ในการเขียนโปรแกรม" - Knuth
Mawg กล่าวว่าการคืนสถานะโมนิก้า

1
@Mawg: คำผ่าตัดมีก่อนวัยอันควร (ตามวรรคถัดไปในบทความนั้นอธิบายประโยคถัดไปตามตัวอักษร: "แต่เราไม่ควรพลาดโอกาสของเราในการวิจารณ์ 3%") อย่าเพิ่มประสิทธิภาพจนกว่าคุณจะต้องการ - คุณจะไม่พบความช้า บิตจนกว่าคุณจะมีอะไรที่จะโพรไฟล์ - แต่ยังไม่ได้มีส่วนร่วมในแง่ร้ายเช่นโดยการใช้เครื่องมือที่ผิดอย่างโจ๋งครึ่มสำหรับงาน
cHao

1
@Mawg ฉันไม่รู้ว่าทำไมฉันถึงได้คำตอบ / ข้อเสนอแนะเกี่ยวกับการปรับให้เหมาะสมเนื่องจากฉันไม่เคยพูดถึงคำนั้นและฉันตั้งใจจะทำ คำถามนี้มีมากขึ้นเกี่ยวกับวิธีการเขียนฟังก์ชั่นใน Embedded Programming เพื่อให้ได้ประสิทธิภาพที่ดีขึ้น
MaNyYaCk

คำตอบ:


28

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

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

ค่าใช้จ่ายของการเรียกใช้ฟังก์ชันใน ISR อาจเหมือนกับการเรียกใช้ฟังก์ชันในระหว่างการเริ่มต้นในแง่ของการจัดเก็บและเวลา อย่างไรก็ตามความต้องการเวลาระหว่าง ISR นั้นอาจมีความสำคัญมากกว่า

นอกจากนี้เมื่อผู้อื่นสังเกตเห็นแล้วค่าใช้จ่าย (และความหมายของ 'ต้นทุน') ของการเรียกใช้ฟังก์ชันจะแตกต่างกันไปตามแพลตฟอร์มคอมไพเลอร์การตั้งค่าการปรับให้เหมาะสมของคอมไพเลอร์และข้อกำหนดของแอปพลิเคชัน จะมีความแตกต่างอย่างมากระหว่าง 8051 กับ cortex-m7 และเครื่องกระตุ้นหัวใจและสวิตช์ไฟ


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

11

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

ซึ่งนำเราไปสู่จุดต่ำสุด พื้นที่คำถามกว้าง ๆ ของคุณเป็นเรื่องถกเถียงมานานหลายทศวรรษแล้วและยังคงเป็นประเด็นถกเถียงกันมาจนถึงทุกวันนี้ อย่างน้อยเมื่อฉันอ่านคำถามของคุณดูเหมือนว่าฉันจะเป็นคำถามที่อิงตามความคิดเห็น (ตามที่คุณถาม)

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


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

  1. มีความสอดคล้องกับวิธีการของคุณเพื่อให้คนอื่นอ่านรหัสของคุณสามารถพัฒนาความเข้าใจว่าคุณเข้าใกล้กระบวนการเข้ารหัสของคุณอย่างไร การมีความไม่สอดคล้องกันอาจเป็นอาชญากรรมที่เลวร้ายที่สุด มันไม่เพียงทำให้คนอื่นยาก แต่มันทำให้มันยากสำหรับคุณที่จะกลับมาใช้รหัสในอีกหลายปีต่อมา
  2. ในระดับที่เป็นไปได้ให้ลองและจัดเรียงสิ่งต่าง ๆ เพื่อให้การเริ่มต้นของส่วนการทำงานต่างๆสามารถทำได้โดยไม่คำนึงถึงการสั่งซื้อ ในกรณีที่จำเป็นต้องมีการสั่งซื้อหากเป็นเพราะการมีเพศสัมพันธ์อย่างใกล้ชิดของฟังก์ชั่นย่อยที่เกี่ยวข้องอย่างสูงสองรายการให้พิจารณาการกำหนดค่าเริ่มต้นครั้งเดียวสำหรับทั้งคู่เพื่อให้สามารถเรียงลำดับใหม่ได้โดยไม่ทำให้เกิดอันตราย หากไม่สามารถทำได้ให้จัดทำเอกสารข้อกำหนดการเริ่มต้นการสั่งซื้อ
  3. ห่อหุ้มความรู้ในที่เดียวหากเป็นไปได้ ค่าคงที่ไม่ควรซ้ำซ้อนกันทุกจุดในรหัส สมการที่แก้สำหรับตัวแปรบางตัวควรมีอยู่ในที่เดียวและที่เดียว และอื่น ๆ หากคุณพบว่าตัวเองกำลังคัดลอกและวางเส้นบางชุดที่มีพฤติกรรมที่ต้องการในสถานที่ที่หลากหลายลองพิจารณาวิธีการที่จะรวบรวมความรู้นั้นไว้ในที่เดียวและใช้งานได้ตามที่ต้องการ ตัวอย่างเช่นถ้าคุณมีโครงสร้างที่ต้องเดินในทางที่เฉพาะเจาะจงให้ทำไม่ได้ทำซ้ำรหัส tree-walking ในทุก ๆ ที่ที่คุณต้องการวนซ้ำโหนดของต้นไม้ ให้จับวิธีการเดินต้นไม้ในที่เดียวและใช้แทน ด้วยวิธีนี้หากต้นไม้เปลี่ยนไปและวิธีการเดินเปลี่ยนไปคุณมีที่เดียวเท่านั้นที่ต้องกังวลและรหัสที่เหลือทั้งหมด "ทำงานได้ถูกต้อง"
  4. หากคุณกระจายกิจวัตรประจำวันของคุณไปยังกระดาษแผ่นใหญ่ที่มีลูกศรเชื่อมต่อพวกเขาตามที่ถูกเรียกโดยกิจวัตรอื่น ๆ คุณจะเห็นในแอปพลิเคชันใด ๆ จะมี "กลุ่ม" ของกิจวัตรที่มีลูกศรจำนวนมาก ระหว่างตัวเอง แต่มีลูกศรเพียงไม่กี่ตัวนอกกลุ่ม ดังนั้นจะมีขอบเขตตามธรรมชาติของรูทีนคู่ที่ใกล้ชิดและการเชื่อมต่อคู่อย่างหลวม ๆ ระหว่างกลุ่มอื่น ๆ ใช้ข้อเท็จจริงนี้เพื่อจัดระเบียบโค้ดของคุณเป็นโมดูล สิ่งนี้จะจำกัดความซับซ้อนของรหัสของคุณอย่างชัดเจน

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

ข้อ จำกัด เหล่านี้อาจมี (และมากกว่า) ของสิ่งเหล่านี้:

  • ข้อ จำกัด ด้านต้นทุนที่รุนแรงต้องใช้ MCU แบบดั้งเดิมที่มี RAM ขนาดเล็กและแทบไม่มีการนับพิน I / O สำหรับสิ่งเหล่านี้จะใช้กฎชุดใหม่ทั้งหมด ตัวอย่างเช่นคุณอาจต้องเขียนในรหัสการชุมนุมเพราะมีพื้นที่รหัสไม่มาก คุณอาจต้องใช้ตัวแปรแบบคงที่เท่านั้นเนื่องจากการใช้ตัวแปรท้องถิ่นมีค่าใช้จ่ายสูงและใช้เวลานานเกินไป คุณอาจต้องหลีกเลี่ยงการใช้รูทีนย่อยมากเกินไปเพราะ (เช่นชิ้นส่วน Microchip PIC บางส่วน) มีการลงทะเบียนฮาร์ดแวร์เพียง 4 รายการเท่านั้นเพื่อจัดเก็บที่อยู่ส่งคืนของรูทีนย่อย ดังนั้นคุณอาจต้อง "บี้" รหัสของคุณอย่างมาก เป็นต้น
  • ข้อ จำกัด ด้านพลังงานที่รุนแรงต้องใช้รหัสที่ออกแบบมาอย่างระมัดระวังเพื่อเริ่มและปิดส่วนใหญ่ของ MCU และวางข้อ จำกัด ที่รุนแรงเกี่ยวกับเวลาดำเนินการของรหัสเมื่อทำงานด้วยความเร็วเต็ม อีกครั้งนี้อาจต้องมีการเข้ารหัสประกอบบางครั้ง
  • ความต้องการเวลาที่รุนแรง ตัวอย่างเช่นมีบางครั้งที่ฉันต้องแน่ใจว่าการส่งสัญญาณของ open-drain 0 นั้นต้องใช้จำนวนรอบที่เท่ากันกับการส่งของ 1 และการสุ่มตัวอย่างที่บรรทัดเดียวกันนี้ก็ต้องดำเนินการด้วย ด้วยระยะสัมพัทธ์ที่แน่นอนกับเวลานี้ นี่หมายความว่า C ไม่สามารถใช้ที่นี่ได้ วิธีเดียวที่เป็นไปได้ในการรับประกันนั้นคือการสร้างรหัสชุดประกอบอย่างระมัดระวัง (และถึงแม้จะไม่ใช่ทุกการออกแบบของ ALU)

และอื่น ๆ (รหัสการเดินสายสำหรับเครื่องมือทางการแพทย์ที่มีความสำคัญต่อชีวิตนั้นมีทั้งโลกด้วยเช่นกัน)

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


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

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


แก้ไขโดย JasonS:

ฉันใช้ C มาตั้งแต่ปี 1978 และ C ++ ตั้งแต่ประมาณปี 1987 และฉันมีประสบการณ์มากมายในการใช้ทั้งสำหรับเมนเฟรมคอมพิวเตอร์มินิคอมพิวเตอร์และแอปพลิเคชั่นฝังตัว

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

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

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

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

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


C ++

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

  • ความเชี่ยวชาญเทมเพลตบางส่วน
  • vtables
  • วัตถุฐานเสมือน
  • กรอบการเปิดใช้งาน
  • กรอบการเปิดใช้งานผ่อนคลาย
  • การใช้พอยน์เตอร์อัจฉริยะในตัวสร้างและทำไม
  • การเพิ่มประสิทธิภาพของค่าตอบแทน

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

ลองมาดูที่ความหมายของ C ++ ข้อยกเว้นเพื่อรับรสชาติ

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

ใช้รหัสลำดับนี้ซึ่งเป็นส่วนหนึ่งของฟังก์ชั่นบางอย่างในหน่วยการคอมไพล์ :A

   .
   .
   foo ();
   String s;
   foo ();
   .
   .

เพื่อวัตถุประสงค์ในการอภิปรายรวบรวมหน่วยไม่ได้ใช้ 'try..catch' ได้ทุกที่ในแหล่งที่มาของ มันไม่ใช้ 'โยน' ในความเป็นจริงสมมติว่ามันไม่ได้ใช้แหล่งข้อมูลใด ๆ ที่ไม่สามารถคอมไพล์ด้วยคอมไพเลอร์ C ยกเว้นความจริงที่ว่ามันใช้การสนับสนุนไลบรารี C ++ และสามารถจัดการวัตถุเช่นสตริง รหัสนี้อาจเป็นไฟล์รหัสต้นฉบับ C ที่ได้รับการแก้ไขเล็กน้อยเพื่อใช้ประโยชน์จากคุณลักษณะบางอย่างของ C ++ เช่นคลาส StringA

นอกจากนี้สมมติว่า foo () เป็นโพรซีเดอร์ภายนอกที่ตั้งอยู่ในคอมไพล์ยูนิตและคอมไพเลอร์มีการประกาศสำหรับมัน แต่ไม่ทราบคำจำกัดความของมันB

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

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

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

ซึ่งแตกต่างจาก malloc ของ C, C ++ ใหม่ใช้ข้อยกเว้นในการส่งสัญญาณเมื่อไม่สามารถทำการจัดสรรหน่วยความจำแบบดิบได้ ดังนั้นจะ 'dynamic_cast' (ดูที่ Stroustrup ของภาษาที่ 3, ภาษา C ++ Programming, หน้า 384 และ 385 สำหรับข้อยกเว้นมาตรฐานใน C ++.) คอมไพเลอร์อาจอนุญาตให้พฤติกรรมนี้ถูกปิดใช้งาน แต่โดยทั่วไปคุณจะต้องเสียค่าโสหุ้ยเนื่องจากข้อยกเว้นที่เกิดขึ้นอย่างถูกต้องในการจัดการอารัมภบทและ epilogues ในรหัสที่สร้างขึ้นแม้ว่าข้อยกเว้นที่เกิดขึ้นจริงไม่ได้เกิดขึ้นและแม้ในขณะที่ฟังก์ชั่นการรวบรวมไม่ได้มีบล็อกการจัดการข้อยกเว้น (Stroustrup ได้คร่ำครวญเรื่องนี้ต่อสาธารณะ)

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

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

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

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

คอมไพเลอร์ C ++ สามารถสร้าง constructors, destructors, copy constructors และโอเปอเรเตอร์การมอบหมายสำหรับคุณโดยอัตโนมัติด้วยผลลัพธ์ที่ไม่ได้ตั้งใจ ต้องใช้เวลาในการรับความสะดวกด้วยรายละเอียดของสิ่งนี้

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

เนื่องจาก C ++ ไม่เรียกใช้ตัวทำลายของวัตถุที่สร้างขึ้นบางส่วนเมื่อมีข้อยกเว้นเกิดขึ้นในตัวสร้างวัตถุการจัดการข้อยกเว้นในตัวสร้างมักจะเรียกว่า "ตัวชี้อัจฉริยะ" เพื่อรับรองว่าชิ้นส่วนที่สร้างในตัวสร้างจะถูกทำลายอย่างถูกต้อง . (ดู Stroustrup, หน้า 367 และ 368) นี่เป็นปัญหาทั่วไปในการเขียนคลาสที่ดีใน C ++ แต่แน่นอนว่าหลีกเลี่ยงใน C เนื่องจาก C ไม่มีความหมายของการก่อสร้างและการทำลายในตัวการเขียนรหัสที่เหมาะสมเพื่อจัดการการก่อสร้าง ของ subobjects ภายในวัตถุหมายถึงการเขียนรหัสที่ต้องรับมือกับปัญหาความหมายเฉพาะใน C ++; ในคำอื่น ๆ "เขียนรอบ" พฤติกรรมความหมาย C ++

C ++ อาจคัดลอกวัตถุที่ส่งไปยังพารามิเตอร์ของวัตถุ ตัวอย่างเช่นในแฟรกเมนต์ต่อไปนี้การเรียก "rA (x);" อาจทำให้คอมไพเลอร์ C ++ สามารถเรียกใช้ Constructor สำหรับพารามิเตอร์ p เพื่อที่จะเรียกใช้ตัวสร้างการคัดลอกเพื่อถ่ายโอนวัตถุ x ไปยังพารามิเตอร์ p จากนั้นตัวสร้างอื่นสำหรับวัตถุส่งคืน (ไม่มีชื่อชั่วคราว) ของฟังก์ชัน rA ซึ่งแน่นอนคือ คัดลอกจากพารามิเตอร์ p ที่แย่กว่านั้นถ้าคลาส A มีวัตถุของตัวเองซึ่งต้องการการก่อสร้างสิ่งนี้สามารถทำให้กล้องโทรทรรศน์ทรุดโทรมได้ (โปรแกรมเมอร์ AC จะหลีกเลี่ยงขยะนี้ส่วนใหญ่เพิ่มประสิทธิภาพด้วยมือเนื่องจากโปรแกรมเมอร์ C ไม่มีไวยากรณ์ที่มีประโยชน์และต้องแสดงรายละเอียดทั้งหมดทีละรายการ)

    class A {...};
    A rA (A p) { return p; }
    // .....
    { A x; rA(x); }

ในที่สุดโน้ตย่อสำหรับโปรแกรมเมอร์ C longjmp () ไม่มีพฤติกรรมแบบพกพาใน C ++ (โปรแกรมเมอร์ C บางคนใช้สิ่งนี้เป็นกลไก "ยกเว้น") คอมไพเลอร์ C ++ บางคนจะพยายามตั้งค่าสิ่งต่างๆเพื่อล้างข้อมูลเมื่อใช้ longjmp แต่พฤติกรรมนั้นไม่ได้พกพาใน C ++ ถ้าคอมไพเลอร์ทำความสะอาดวัตถุที่สร้างขึ้นมันไม่ใช่แบบพกพา หากคอมไพเลอร์ไม่ได้ล้างพวกเขาแล้ววัตถุจะไม่ถูกทำลายถ้ารหัสออกจากขอบเขตของวัตถุที่สร้างขึ้นเป็นผลมาจาก longjmp และพฤติกรรมที่ไม่ถูกต้อง (หากการใช้ longjmp ใน foo () ไม่ออกจากขอบเขตพฤติกรรมอาจไม่ปกติ) สิ่งนี้ไม่ได้ใช้บ่อยเกินไปโดยโปรแกรมเมอร์ C ที่ฝังตัว แต่ควรทำให้พวกเขาตระหนักถึงปัญหาเหล่านี้ก่อนที่จะใช้พวกเขา


4
ฟังก์ชั่นประเภทนี้ที่ใช้เพียงครั้งเดียวจะไม่ถูกรวบรวมเป็นการเรียกใช้ฟังก์ชันรหัสจะถูกวางไว้ที่นั่นโดยไม่มีการเรียก
Dorian

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

1
@jonk - อีกหนึ่งเคล็ดลับที่คุณไม่ได้กล่าวถึงในคำตอบที่ดีคือการเขียนฟังก์ชั่นแมโครอย่างง่ายที่ดำเนินการเริ่มต้นหรือการกำหนดค่าเป็นรหัสอินไลน์ขยาย สิ่งนี้มีประโยชน์อย่างยิ่งในโปรเซสเซอร์ขนาดเล็กมากที่ความลึกการเรียก RAM / stack / function ถูก จำกัด
คุณ

@ ʎəʞouɐɪใช่ฉันไม่ได้พูดถึงมาโครใน C พวกนั้นเลิกใช้แล้วใน C ++ แต่การอภิปรายในประเด็นนั้นอาจมีประโยชน์ ฉันอาจพูดถึงมันถ้าฉันสามารถคิดสิ่งที่มีประโยชน์ในการเขียนเกี่ยวกับเรื่องนี้
jonk

1
@ จอน - ฉันไม่เห็นด้วยกับประโยคแรกของคุณอย่างสมบูรณ์ ตัวอย่างเช่นinline static void turnOnFan(void) { PORTAbits &= ~(1<<8); }ที่ถูกเรียกในสถานที่ต่าง ๆ เป็นผู้สมัครที่สมบูรณ์แบบ
Jason S

8

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

2) ประสิทธิภาพของโค้ดที่รันครั้งเดียวไม่สำคัญมาก ใส่ใจในสไตล์ไม่ใช่เพื่อประสิทธิภาพ

3) แม้แต่โค้ดในลูปที่แน่นก็ต้องถูกแก้ไขก่อนอื่น หากคุณประสบปัญหาประสิทธิภาพการทำงานให้ปรับให้เหมาะสมเมื่อรหัสถูกต้อง

4) หากคุณต้องการเพิ่มประสิทธิภาพคุณต้องวัด! ไม่สำคัญว่าคุณจะคิดหรือมีคนบอกคุณว่านั่นstatic inlineเป็นเพียงคำแนะนำสำหรับคอมไพเลอร์ คุณต้องดูว่าคอมไพเลอร์ทำอะไร คุณต้องวัดว่าการอินไลน์ทำให้ประสิทธิภาพดีขึ้นหรือไม่ ในระบบฝังตัวคุณต้องทำการวัดขนาดโค้ดเนื่องจากหน่วยความจำรหัสมักจะมีข้อ จำกัด นี่คือกฎที่สำคัญที่สุดที่แยกความแตกต่างด้านวิศวกรรมจากการคาดเดา หากคุณไม่ได้วัดมันก็ไม่ได้ช่วย วิศวกรรมกำลังวัด วิทยาศาสตร์กำลังเขียนมันลง;)


2
คำวิจารณ์เดียวที่ฉันมีจากโพสต์ที่ยอดเยี่ยมของคุณคือประเด็นที่ 2) มันเป็นความจริงที่ประสิทธิภาพของรหัสการเริ่มต้นไม่เกี่ยวข้อง - แต่ในสภาพแวดล้อมแบบฝังตัวขนาดอาจมีความสำคัญ (แต่นั่นไม่ได้แทนที่จุดที่ 1; เริ่มปรับขนาดให้เหมาะสมเมื่อคุณต้องการ - และไม่ก่อน)
Martin Bonner สนับสนุน Monica

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

5

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

หลังจากรวบรวมรหัสจะไม่มีการเรียกหลายครั้งแทนการอ่านได้จะดีขึ้นมาก

นอกจากนี้คุณจะต้องมีตัวอย่างเช่นรหัสเริ่มต้น ADC ในห้องสมุดเดียวกันกับฟังก์ชั่น ADC อื่น ๆ ที่ไม่ได้อยู่ในไฟล์ c หลัก

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

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

รหัสเช่นนี้:

function_used_just_once{
   code blah blah;
}
main{
  codeblah;
  function_used_just_once();
  code blah blah blah;
{

จะรวบรวมไปที่:

main{
 code blah;
 code blah blah;
 code blah blah blah;
}

โดยไม่ต้องใช้สายใด ๆ

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

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

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

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

ดังนั้นโปรดหยุดการลงคะแนนเท่านั้นเพราะในกรณีที่หายากผิดปกตินี่อาจไม่เป็นความจริง


15
ไม่ว่าจะเป็นรหัสแบบอินไลน์หรือไม่เป็นปัญหาเฉพาะการใช้งานผู้ขายคอมไพเลอร์; แม้การใช้คำหลักแบบอินไลน์ไม่รับประกันว่ารหัสแบบอินไลน์ มันเป็นคำใบ้สำหรับคอมไพเลอร์ คอมไพเลอร์ที่ดีแน่นอนจะอินไลน์ฟังก์ชั่นที่ใช้เพียงครั้งเดียวหากพวกเขารู้เกี่ยวกับพวกเขา มันมักจะไม่ทำเช่นนั้นหากมีวัตถุ "ระเหย" ในขอบเขตแม้ว่า
Peter Smith

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

2
@ ʎəʞouɐɪคุณกำลังชี้กรณีที่ไม่ค่อยเกิดขึ้นซึ่งเป็นไปไม่ได้และมันเป็นความคิดที่ดีที่จะไม่เรียกใช้ฟังก์ชันในตอนแรก ฉันไม่เคยเห็นคอมไพเลอร์เลยโง่มากที่จะใช้การโทรในตัวอย่างง่ายๆที่ได้รับจาก OP
Dorian

6
ในกรณีที่เรียกใช้ฟังก์ชั่นเหล่านี้เพียงครั้งเดียวการปรับการเรียกฟังก์ชั่นออกมาให้เหมาะสมจะไม่เป็นปัญหา ระบบจำเป็นต้องส่งเสียงสัญญาณทุกรอบนาฬิกาในระหว่างการตั้งค่าหรือไม่? เป็นกรณีที่มีการเพิ่มประสิทธิภาพทุกที่ - เขียนโค้ดสามารถอ่านได้และเพิ่มประสิทธิภาพเฉพาะในกรณีที่ profiling แสดงให้เห็นว่ามันเป็นสิ่งที่จำเป็น
Baldrickk

5
@Malters ฉันไม่ได้กังวลว่าคอมไพเลอร์จะทำอะไรที่นี่ - เพิ่มเติมว่าโปรแกรมเมอร์เข้าหามันอย่างไร มีทั้งไม่มีหรือประสิทธิภาพเล็กน้อยจากการทำลายการเริ่มต้นตามที่เห็นในคำถาม
Baldrickk

2

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

ไฟล์เดียว:

void dummy (unsigned int);

void setCLK()
{
    // Code to set the clock
    dummy(5);
}

void setConfig()
{
    // Code to set the configuration
    dummy(6);
}

void setSomethingElse()
{
   // 1 line code to write something to a register.
    dummy(7);
}

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

เลือกเป้าหมายและผู้รวบรวมเพื่อวัตถุประสงค์ในการสาธิต

Disassembly of section .text:

00000000 <setCLK>:
   0:    e92d4010     push    {r4, lr}
   4:    e3a00005     mov    r0, #5
   8:    ebfffffe     bl    0 <dummy>
   c:    e8bd4010     pop    {r4, lr}
  10:    e12fff1e     bx    lr

00000014 <setConfig>:
  14:    e92d4010     push    {r4, lr}
  18:    e3a00006     mov    r0, #6
  1c:    ebfffffe     bl    0 <dummy>
  20:    e8bd4010     pop    {r4, lr}
  24:    e12fff1e     bx    lr

00000028 <setSomethingElse>:
  28:    e92d4010     push    {r4, lr}
  2c:    e3a00007     mov    r0, #7
  30:    ebfffffe     bl    0 <dummy>
  34:    e8bd4010     pop    {r4, lr}
  38:    e12fff1e     bx    lr

0000003c <initModule>:
  3c:    e92d4010     push    {r4, lr}
  40:    e3a00005     mov    r0, #5
  44:    ebfffffe     bl    0 <dummy>
  48:    e3a00006     mov    r0, #6
  4c:    ebfffffe     bl    0 <dummy>
  50:    e3a00007     mov    r0, #7
  54:    ebfffffe     bl    0 <dummy>
  58:    e8bd4010     pop    {r4, lr}
  5c:    e12fff1e     bx    lr

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

void dummy (unsigned int);

static void setCLK()
{
    // Code to set the clock
    dummy(5);
}

static void setConfig()
{
    // Code to set the configuration
    dummy(6);
}

static void setSomethingElse()
{
   // 1 line code to write something to a register.
    dummy(7);
}

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

ลบออกทันทีที่มีการแทรก

Disassembly of section .text:

00000000 <initModule>:
   0:    e92d4010     push    {r4, lr}
   4:    e3a00005     mov    r0, #5
   8:    ebfffffe     bl    0 <dummy>
   c:    e3a00006     mov    r0, #6
  10:    ebfffffe     bl    0 <dummy>
  14:    e3a00007     mov    r0, #7
  18:    ebfffffe     bl    0 <dummy>
  1c:    e8bd4010     pop    {r4, lr}
  20:    e12fff1e     bx    lr

แต่ความจริงคือเมื่อคุณทำกับผู้ขายชิปหรือห้องสมุด BSP

Disassembly of section .text:

00000000 <_start>:
   0:    e3a0d902     mov    sp, #32768    ; 0x8000
   4:    eb000010     bl    4c <initModule>
   8:    eafffffe     b    8 <_start+0x8>

0000000c <dummy>:
   c:    e12fff1e     bx    lr

00000010 <setCLK>:
  10:    e92d4010     push    {r4, lr}
  14:    e3a00005     mov    r0, #5
  18:    ebfffffb     bl    c <dummy>
  1c:    e8bd4010     pop    {r4, lr}
  20:    e12fff1e     bx    lr

00000024 <setConfig>:
  24:    e92d4010     push    {r4, lr}
  28:    e3a00006     mov    r0, #6
  2c:    ebfffff6     bl    c <dummy>
  30:    e8bd4010     pop    {r4, lr}
  34:    e12fff1e     bx    lr

00000038 <setSomethingElse>:
  38:    e92d4010     push    {r4, lr}
  3c:    e3a00007     mov    r0, #7
  40:    ebfffff1     bl    c <dummy>
  44:    e8bd4010     pop    {r4, lr}
  48:    e12fff1e     bx    lr

0000004c <initModule>:
  4c:    e92d4010     push    {r4, lr}
  50:    ebffffee     bl    10 <setCLK>
  54:    ebfffff2     bl    24 <setConfig>
  58:    ebfffff6     bl    38 <setSomethingElse>
  5c:    e8bd4010     pop    {r4, lr}
  60:    e12fff1e     bx    lr

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

ทำไมถึงทำเช่นนี้? บางส่วนเป็นชุดของอาจารย์กฎที่จะสอนหรือยังคงสอนให้การจัดระดับรหัสง่ายขึ้น ฟังก์ชั่นจะต้องพอดีกับหน้ากระดาษ (ย้อนกลับไปเมื่อคุณพิมพ์ผลงานลงบนกระดาษ) อย่าทำอย่างนั้นอย่าทำอย่างนั้น ฯลฯ มีหลายอย่างที่ทำให้ไลบรารีมีชื่อสามัญสำหรับเป้าหมายที่แตกต่างกัน หากคุณมีไมโครคอนโทรลเลอร์หลายสิบตระกูลบางตัวใช้อุปกรณ์ต่อพ่วงร่วมกันและบางอันไม่อาจมีรสชาติของ UART ที่แตกต่างกันสามหรือสี่อย่างในครอบครัว GPIO ต่าง ๆ คอนโทรลเลอร์ SPI ฯลฯ คุณสามารถมีฟังก์ชั่น gpio_init () ทั่วไป get_timer_count () ฯลฯ และนำ abstractions เหล่านั้นกลับมาใช้ใหม่สำหรับอุปกรณ์ต่อพ่วงต่างๆ

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

นี่เป็นคำถามที่อิงตามความคิดเห็นเป็นอย่างมากและข้างต้นแสดงให้เห็นถึงสามวิธีที่สำคัญที่จะเกิดขึ้น เป็นเส้นทางที่ดีที่สุดที่มีความคิดเห็นอย่างเคร่งครัด ทำงานทั้งหมดในฟังก์ชั่นเดียวใช่หรือไม่? คำถามตามความคิดเห็นบางคนพึ่งพาประสิทธิภาพบางคนนิยามความเป็นโมดูล่าร์และรุ่นที่อ่านง่ายที่สุด ปัญหาที่น่าสนใจกับสิ่งที่ผู้คนจำนวนมากเรียกว่าการอ่านได้นั้นเจ็บปวดมาก เพื่อ "ดู" รหัสที่คุณต้องเปิด 50-10,000 ไฟล์ในคราวเดียวและลองดูฟังก์ชันเชิงเส้นเพื่อดำเนินการตามลำดับเพื่อดูว่าเกิดอะไรขึ้น ฉันพบว่าตรงข้ามกับความสามารถในการอ่าน แต่คนอื่น ๆ พบว่าสามารถอ่านได้เพราะแต่ละรายการพอดีกับหน้าจอ / หน้าต่างแก้ไขและสามารถใช้งานได้ทั้งหมดหลังจากที่พวกเขาจดจำฟังก์ชันที่ถูกเรียกและ / หรือมีตัวแก้ไขที่สามารถโผล่เข้าออก แต่ละฟังก์ชั่นภายในโครงการ

นั่นเป็นอีกหนึ่งปัจจัยใหญ่เมื่อคุณเห็นทางออกที่หลากหลาย โปรแกรมแก้ไขข้อความ, IDE, ฯลฯ นั้นมีความเป็นส่วนตัวมากและมันเกินกว่า vi vs Emacs ประสิทธิภาพการเขียนโปรแกรมบรรทัดต่อวัน / เดือนเพิ่มขึ้นหากคุณสะดวกสบายและมีประสิทธิภาพด้วยเครื่องมือที่คุณใช้ คุณสมบัติของเครื่องมือสามารถ / จะตั้งใจหรือไม่พึ่งพาวิธีการที่แฟน ๆ ของเครื่องมือเขียนโค้ดนั้น และหากบุคคลหนึ่งกำลังเขียนไลบรารีเหล่านี้โครงการในระดับหนึ่งจะสะท้อนถึงนิสัยเหล่านี้ แม้ว่ามันจะเป็นทีม แต่นักพัฒนานำหรือนิสัย / ความชอบของหัวหน้าอาจถูกบังคับให้เข้าสู่ส่วนที่เหลือของทีม

มาตรฐานการเข้ารหัสที่มีการกำหนดค่าส่วนบุคคลจำนวนมากฝังอยู่ในพวกเขา vi ศาสนามากเทียบกับ Emacs อีกครั้งแท็บกับช่องว่างการเรียงตัวของวงเล็บเป็นต้นและการเล่นเหล่านี้เป็นวิธีการออกแบบห้องสมุดในระดับหนึ่ง

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

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

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

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


4
จริงๆ? คุณใช้คำว่า "some target" และ "some compiler" อะไร
Dorian

ดูเหมือนว่าฉันจะเป็น ARM8 32/64 บิตบางทีอาจจะมาจาก raspbery PI จากนั้นก็เป็นไมโครคอนโทรลเลอร์ คุณอ่านประโยคแรกในคำถามหรือไม่
Dorian

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

หากมีคนสงสัยว่าคอมไพเลอร์ใดที่สามารถปรับให้เหมาะสมกับช่องว่างของไฟล์: คอมไพเลอร์ IAR สนับสนุนการรวบรวมไฟล์หลายไฟล์ หากคุณโยนไฟล์ c / cpp ทั้งหมดไปด้วยในครั้งเดียวคุณจะพบกับไฟล์ปฏิบัติการที่มีฟังก์ชั่นเดียว: main ประโยชน์ด้านประสิทธิภาพนั้นค่อนข้างลึกซึ้ง
อาร์เซนอล

3
@Arsenal แน่นอน gcc สนับสนุนการอินไลน์แม้ในหน่วยการคอมไพล์ถ้าเรียกอย่างถูกต้อง ดูgcc.gnu.org/onlinedocs/gcc/Optimize-Options.htmlและค้นหาตัวเลือก -flto
ปีเตอร์ - Reinstate Monica

1

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


น่าเศร้าที่มันเป็นความจริงสำหรับโปรแกรมเมอร์ส่วนใหญ่ในปัจจุบัน
Dorian

0

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


ขอบคุณสำหรับการตอบกลับของคุณ. ฉันอาจเปลี่ยนสไตล์หากต้องการประสิทธิภาพ แต่คำถามในที่นี้คือความสามารถในการอ่านรหัสมีผลต่อประสิทธิภาพหรือไม่
MaNyYaCk

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

@Humpawumpa - หากคุณกำลังเขียนสำหรับไมโครคอนโทรลเลอร์ที่มี RAM เพียง 256 หรือ 64 ไบต์แล้วการเรียกใช้ฟังก์ชันหลายสิบชั้นนั้นไม่ได้เป็นการเสียสละเพียงเล็กน้อย
uɐɪ

ใช่ แต่นี่เป็นสองขั้ว ... โดยปกติแล้วคุณจะมีมากกว่า 256 ไบต์และใช้เลเยอร์น้อยกว่าหนึ่งโหล - หวังว่า
po.pe

0

static inlineถ้าทำงานจริงๆไม่เพียงสิ่งเดียวที่มีขนาดเล็กมากให้พิจารณาการทำมัน

เพิ่มลงในไฟล์ส่วนหัวแทนไฟล์ C และใช้คำstatic inlineเพื่อกำหนด:

static inline void setCLK()
{
    //code to set the clock
}

ทีนี้หากฟังก์ชั่นนั้นยาวขึ้นเล็กน้อยเช่นมีมากกว่า 3 บรรทัดมันอาจเป็นความคิดที่ดีที่จะหลีกเลี่ยงstatic inlineและเพิ่มลงในไฟล์. c ท้ายที่สุดระบบฝังตัวมีหน่วยความจำ จำกัด และคุณไม่ต้องการเพิ่มขนาดรหัสมากเกินไป

นอกจากนี้หากคุณกำหนดฟังก์ชั่นfile1.cและการใช้งานfile2.cคอมไพเลอร์จะไม่อินไลน์โดยอัตโนมัติ อย่างไรก็ตามหากคุณกำหนดfile1.hเป็นstatic inlineฟังก์ชันโอกาสคอมไพเลอร์ของคุณจะอินไลน์

static inlineฟังก์ชั่นเหล่านี้มีประโยชน์อย่างมากในการเขียนโปรแกรมประสิทธิภาพสูง ฉันพบว่าพวกเขาเพิ่มประสิทธิภาพโค้ดบ่อยครั้งด้วยปัจจัยที่เกินสาม


"เช่นมีมากกว่า 3 บรรทัด" - การนับบรรทัดไม่มีส่วนเกี่ยวข้องกับมัน ค่าใช้จ่ายแบบอินไลน์มีทุกสิ่งที่ต้องทำ ฉันสามารถเขียนฟังก์ชั่น 20-line ที่เหมาะสำหรับการทำ inline และฟังก์ชั่น 3-line ที่น่ากลัวสำหรับ inlining (เช่น functionA () ที่เรียก functionB () 3 ครั้ง functionB () ที่เรียก functionC () 3 ครั้งและ สองระดับอื่น ๆ )
Jason S

นอกจากนี้หากคุณกำหนดฟังก์ชั่นfile1.cและการใช้งานfile2.cคอมไพเลอร์จะไม่อินไลน์โดยอัตโนมัติ เท็จ ดูเช่น-fltoใน gcc หรือเสียงดังกราว
berendi - ประท้วง

0

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

ตัวอย่างเช่นหากมีระบบแบบแกนเดียวที่มีรูทีนการบริการขัดจังหวะ [เรียกใช้โดยเห็บตัวจับเวลาหรืออะไรก็ตาม]:

volatile uint32_t *magic_write_ptr,magic_write_count;
void handle_interrupt(void)
{
  if (magic_write_count)
  {
    magic_write_count--;
    send_data(*magic_write_ptr++)
  }
}

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

void wait_for_background_write(void)
{
  while(magic_write_count)
    ;
}
void start_background_write(uint32_t *dat, uint32_t count)
{
  wait_for_background_write();
  background_write_ptr = dat;
  background_write_count = count;
}

จากนั้นเรียกใช้รหัสดังกล่าวโดยใช้:

uint32_t buff[16];

... write first set of data into buff
start_background_write(buff, 16);
... do some stuff unrelated to buff
wait_for_background_write();

... write second set of data into buff
start_background_write(buff, 16);
... etc.

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

มาตรฐานจะละเว้นปัญหาเกี่ยวกับคุณภาพของการใช้งานโดยพิจารณาว่ามีวิธีที่เหมาะสมหลายประการที่การจัดการด้านบนสามารถจัดการได้:

  1. การใช้งานที่มีคุณภาพโดยเฉพาะสำหรับเขตข้อมูลเช่นการบดตัวเลขระดับสูงอาจสมเหตุสมผลคาดว่ารหัสที่เขียนขึ้นสำหรับเขตข้อมูลดังกล่าวจะไม่ประกอบด้วยโครงสร้างดังกล่าวข้างต้น

  2. การใช้งานที่มีคุณภาพอาจปฏิบัติต่อการเข้าถึงvolatileวัตถุทั้งหมดราวกับว่าพวกเขาอาจก่อให้เกิดการกระทำที่จะเข้าถึงวัตถุใด ๆ ที่มองเห็นได้ในโลกภายนอก

  3. การใช้งานที่เรียบง่าย แต่มีคุณภาพเหมาะสมสำหรับการใช้งานระบบฝังตัวนั้นอาจใช้กับการเรียกฟังก์ชั่นทั้งหมดที่ไม่ได้ทำเครื่องหมายว่า "อินไลน์" ราวกับว่าพวกเขาสามารถเข้าถึงวัตถุใด ๆ ที่สัมผัสกับโลกภายนอกแม้ว่ามันจะไม่ปฏิบัติvolatileตามที่อธิบายไว้ใน 2

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

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


"การทำให้แน่ใจว่าฟังก์ชั่น I / O อยู่ในหน่วยการรวบรวมที่แยกต่างหาก" ... ไม่ใช่วิธีที่แน่นอนในการป้องกันปัญหาการปรับให้เหมาะสมเช่นนี้ อย่างน้อย LLVM และฉันเชื่อว่า GCC จะทำการปรับให้เหมาะสมทั้งโปรแกรมในหลาย ๆ กรณีดังนั้นสามารถตัดสินใจที่จะอินไลน์ฟังก์ชัน IO ของคุณแม้ว่าพวกเขาจะอยู่ในหน่วยรวบรวมที่แยกต่างหาก
Jules

@Jules: การใช้งานบางอย่างไม่เหมาะสำหรับการเขียนซอฟต์แวร์ฝังตัว การปิดใช้งานการเพิ่มประสิทธิภาพโปรแกรมทั้งหมดอาจเป็นวิธีที่มีค่าใช้จ่ายน้อยที่สุดในการบังคับให้ gcc หรือเสียงดังกราวให้ทำงานเป็นการใช้งานคุณภาพที่เหมาะสมสำหรับจุดประสงค์นั้น
supercat

@Jules: การใช้งานที่มีคุณภาพสูงขึ้นสำหรับการฝังตัวหรือการเขียนโปรแกรมระบบควรกำหนดค่าให้มีความหมายที่เหมาะสมสำหรับวัตถุประสงค์นั้นโดยไม่ต้องปิดการใช้งานการเพิ่มประสิทธิภาพทั้งโปรแกรมอย่างสมบูรณ์ (เช่นโดยมีตัวเลือกในการจัดการการvolatileเข้าถึง การเข้าถึงวัตถุอื่นโดยพลการ) แต่ด้วยเหตุผลใดก็ตามที่ gcc และเสียงดังกราวด์จะปฏิบัติต่อปัญหาคุณภาพของการนำไปใช้เป็นคำเชิญให้ทำงานในแบบไร้ประโยชน์
supercat

1
แม้แต่การใช้งาน "คุณภาพสูงสุด" ก็ไม่สามารถแก้ไขรหัสรถบั๊กได้ หากbuffไม่ได้ประกาศvolatileไว้จะไม่ถือว่าเป็นตัวแปรระเหยการเข้าใช้อาจถูกจัดลำดับใหม่หรือปรับให้เหมาะสมที่สุดหากไม่ได้ใช้ในภายหลัง กฎง่ายๆคือ: ทำเครื่องหมายตัวแปรทั้งหมดที่อาจจะมีการเข้าถึงนอกไหลของโปรแกรมปกติ (เท่าที่เห็นโดยคอมไพเลอร์) volatileเป็น เนื้อหาของการbuffเข้าถึงในตัวจัดการขัดจังหวะหรือไม่ ใช่. volatileแล้วมันควรจะเป็น
berendi - ประท้วง

@berendi: คอมไพเลอร์สามารถเสนอการรับประกันนอกเหนือจากที่มาตรฐานและคอมไพเลอร์คุณภาพต้องการ การติดตั้งอิสระที่มีคุณภาพสำหรับการใช้งานระบบฝังตัวจะช่วยให้โปรแกรมเมอร์สามารถสังเคราะห์การสร้าง mutex ซึ่งเป็นสิ่งสำคัญที่รหัสทำ เมื่อmagic_write_countเป็นศูนย์หน่วยเก็บข้อมูลจะถูกครอบครองโดยสายหลัก เมื่อไม่ใช่ศูนย์มันเป็นเจ้าของโดยตัวจัดการขัดจังหวะ การbuffระเหยจะต้องว่าทุกที่ทุกฟังก์ชั่นที่ดำเนินการกับมันใช้volatileตัวชี้ -qualified ซึ่งจะทำให้เสียการเพิ่มประสิทธิภาพมากขึ้นกว่าที่มีคอมไพเลอร์ ...
SuperCat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.