การใช้ตัวแปรโกลบอลในระบบสมองกลฝังตัว


17

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

ฉันต้องการคำแนะนำที่มีค่าจากพวกคุณในหัวข้อนี้เพื่อทำให้เฟิร์มแวร์ของฉันมีขนาดเล็กลง


คำถามนี้ไม่ซ้ำกับระบบฝังตัว ซ้ำกันสามารถพบได้ที่นี่
Lundin

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

@endolith staticขอบเขตไฟล์ไม่เหมือนกับ "global" โปรดดูคำตอบของฉันด้านล่าง
Lundin

คำตอบ:


31

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

  1. ใช้ตัวแปรสแตติกท้องถิ่นสำหรับสถานะถาวรที่คุณต้องการเข้าถึงภายในฟังก์ชั่นเดียว

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. ใช้โครงสร้างเพื่อเก็บตัวแปรที่เกี่ยวข้องไว้ด้วยกันเพื่อให้ชัดเจนว่าควรใช้ที่ใดและที่ไหน

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
  3. ใช้ตัวแปรสแตติกโกลบอลเพื่อทำให้ตัวแปรมองเห็นได้ภายในไฟล์ C ปัจจุบันเท่านั้น วิธีนี้ช่วยป้องกันการเข้าถึงรหัสโดยไม่ตั้งใจในไฟล์อื่น ๆ เนื่องจากการตั้งชื่อที่ขัดแย้งกัน

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */

ในฐานะโน้ตสุดท้ายหากคุณกำลังแก้ไขตัวแปรโกลบอลภายในรูทีนการอินเตอร์รัปต์และอ่านที่อื่น:

  • volatileทำเครื่องหมายตัวแปร
  • ตรวจสอบให้แน่ใจว่าเป็น atomic สำหรับ CPU (เช่น 8 บิตสำหรับ CPU 8 บิต)

หรือ

  • ใช้กลไกการล็อคเพื่อป้องกันการเข้าถึงตัวแปร

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

3
นั่นเป็นคำจำกัดความที่ค่อนข้างแคบของ ประเด็นของฉันคือการประกาศบางสิ่งที่ผันผวนไม่ได้ป้องกันความขัดแย้ง นอกจากนี้ตัวอย่างที่ 3 ของคุณไม่ใช่ความคิดที่ดี - การมีวงกลมแยกสองอันที่มีชื่อเดียวกันนั้นเป็นอย่างน้อยที่สุดที่จะทำให้โค้ดเข้าใจและบำรุงรักษาได้ยากขึ้น
John U

1
@JohnU คุณไม่ควรใช้สารระเหยเพื่อป้องกันสภาพการแข่งขันแน่นอนว่ามันจะไม่ช่วย คุณควรใช้สารระเหยเพื่อป้องกันข้อบกพร่องในการเพิ่มประสิทธิภาพคอมไพเลอร์ที่เป็นอันตรายที่พบบ่อยในคอมไพเลอร์ระบบฝังตัว
Lundin

2
@JohnU: การใช้volatileตัวแปรตามปกติคืออนุญาตให้ใช้รหัสในบริบทการดำเนินการหนึ่งเพื่อให้รหัสในบริบทการดำเนินการอื่นรู้ว่ามีบางอย่างเกิดขึ้น บนระบบ 8 บิตบัฟเฟอร์ที่จะเก็บพลังงานจำนวนสองไบต์ไม่เกิน 128 สามารถจัดการได้ด้วยไบต์ระเหยหนึ่งที่ระบุจำนวนไบต์ทั้งหมดที่ใส่ลงในบัฟเฟอร์ (mod 256) และ อีกชุดหนึ่งระบุจำนวนไบต์ที่ใช้งานตลอดชีวิตโดยมีเพียงบริบทการดำเนินการเพียงข้อเดียวเท่านั้นที่นำข้อมูลเข้าสู่บัฟเฟอร์และมีเพียงหนึ่งข้อมูลที่นำข้อมูลออกมา
supercat

2
@JohnU: อาจเป็นไปได้ที่จะใช้รูปแบบการล็อคหรือปิดการใช้งานอินเตอร์รัปต์ชั่วคราวเพื่อจัดการบัฟเฟอร์ แต่ก็ไม่จำเป็นหรือมีประโยชน์จริงๆ หากบัฟเฟอร์ต้องมีขนาด 128-255 ไบต์การเข้ารหัสจะต้องเปลี่ยนแปลงเล็กน้อยและหากต้องหยุดชะงักการปิดใช้งานการขัดจังหวะอาจเป็นสิ่งจำเป็น แต่ในบัฟเฟอร์ระบบขนาด 8 บิตจะเล็ก ระบบที่มีบัฟเฟอร์ขนาดใหญ่กว่าสามารถเขียนอะตอมได้มากกว่า 8 บิต
supercat

24

เหตุผลที่คุณไม่ต้องการใช้ตัวแปรทั่วโลกในระบบ 8 บิตเหมือนกันกับที่คุณไม่ต้องการใช้ตัวแปรเหล่านี้ในระบบอื่น ๆ : ทำให้เหตุผลเกี่ยวกับพฤติกรรมของโปรแกรมยาก

เฉพาะโปรแกรมเมอร์ที่ไม่ดีเท่านั้นที่จะถูกแฮงค์กับกฎอย่าง "ไม่ใช้ตัวแปรโกลบอล" โปรแกรมเมอร์ที่ดีเข้าใจเหตุผลของกฎแล้วปฏิบัติต่อกฎเช่นแนวทาง

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


1
สิ่งที่ @MichaelKaras พูด - การทำความเข้าใจว่าสิ่งเหล่านี้มีความหมายอย่างไรและส่งผลอย่างไรต่อสิ่งต่าง ๆ (และพวกเขากัดคุณได้อย่างไร) เป็นสิ่งสำคัญ
John U

5

คุณไม่ควรหลีกเลี่ยงการใช้ตัวแปรโกลบอล ("globals" โดยย่อ) แต่คุณควรใช้อย่างรอบคอบ ปัญหาการปฏิบัติกับการใช้มากเกินไปของกลม:

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

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


2
ธงไม่จำเป็นต้องเป็นโลก ISR สามารถเรียกใช้ฟังก์ชันที่มีตัวแปรแบบคงที่ได้ตัวอย่างเช่น
Phil

+1 ฉันไม่เคยได้ยินเรื่องหลอกลวงมาก่อน ฉันได้ลบย่อหน้านั้นออกจากคำตอบ วิธีการที่จะstaticธงกลายเป็นมองเห็นได้ด้วยmain()? คุณหมายถึงว่าฟังก์ชั่นเดียวกันกับที่มีstaticสามารถส่งคืนได้ในmain()ภายหลังหรือไม่?
Nick Alexeev

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

4
วิธีการบางอย่างในการหลีกเลี่ยง globals (เช่นการเข้าถึงฮาร์ดแวร์ผ่านไดร์เวอร์อุปกรณ์เท่านั้น) อาจไม่มีประสิทธิภาพมากเกินไปสำหรับสภาพแวดล้อมแบบ 8 บิตที่ใช้ทรัพยากรอย่างรุนแรง
Spehro Pefhany

1
@SpehroPefhany โปรแกรมเมอร์ที่ดีเข้าใจถึงเหตุผลที่อยู่เบื้องหลังกฎแล้วปฏิบัติกับกฎมากขึ้นเช่นแนวทาง เมื่อแนวทางขัดแย้งกันนักเขียนโปรแกรมที่ดีจะชั่งน้ำหนักอย่างระมัดระวัง
Phil

4

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

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

โปรดทราบว่าคงมีความหมายที่แตกต่างกันทั้งภายในและภายนอกฟังก์ชั่น /programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

/programming/5033627/static-variable-inside-of-a-function-in-c


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

4

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

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

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


ดังนั้นการรักษาตัวแปรระดับโลกที่จะไม่ขัดแย้งกันจะไม่เป็นปัญหา เช่น: Day_cntr, week_cntr ฯลฯ สำหรับการนับวันและสัปดาห์ตามลำดับและฉันเชื่อว่าไม่ควรใช้ตัวแปรทั่วโลกที่มีลักษณะคล้ายกับวัตถุประสงค์เดียวกันและจงใจกำหนดอย่างชัดเจนขอบคุณมากสำหรับการตอบสนองอย่างท่วมท้น :)
Rookie91

-1

ผู้คนจำนวนมากสับสนเกี่ยวกับเรื่องนี้ นิยามของตัวแปรทั่วโลกคือ:

สิ่งที่สามารถเข้าถึงได้จากทุกที่ในโปรแกรมของคุณ

นี่ไม่ใช่สิ่งเดียวกันกับตัวแปรขอบเขตไฟล์ซึ่งประกาศโดยคำstaticสำคัญ สิ่งเหล่านี้ไม่ใช่ตัวแปรระดับโลก แต่เป็นตัวแปรส่วนท้องถิ่น

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

คุณควรใช้ตัวแปรทั่วโลก? มีบางกรณีที่ไม่เป็นไร:

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

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


ทำไมต้องลงคะแนน?
m.Alin

2
ฉันคิดว่า "ตัวแปรทั่วโลก" สามารถใช้เพื่ออธิบายการจัดสรรให้กับกลุ่มส่วนกลาง (ไม่ใช่ข้อความสแต็คหรือฮีป) ในความหมายของไฟล์คงที่และฟังก์ชั่นตัวแปรคงที่ / สามารถ "ทั่วโลก" ในบริบทของคำถามนี้ค่อนข้างชัดเจนว่าทั่วโลกอ้างถึงขอบเขตไม่ใช่ส่วนการจัดสรร (แม้ว่าเป็นไปได้ที่ OP ไม่ทราบเรื่องนี้)
Paul A. Clayton

1
@ PaulA.Clayton ฉันไม่เคยได้ยินคำศัพท์ทางการที่เรียกว่า "เซ็กเมนต์หน่วยความจำระดับโลก" ตัวแปรของคุณจะจบลงหนึ่งในสถานที่ดังต่อไปนี้: ลงทะเบียนหรือสแต็ค (จัดสรรรันไทม์) กอง (จัดสรรรันไทม์) .dataส่วน (เริ่มต้นตัวแปรการจัดเก็บข้อมูลแบบคงที่อย่างชัดเจน) .bssส่วน (ตัวแปรการจัดเก็บข้อมูลแบบคงที่กลายเป็นศูนย์) .rodata (อ่าน ค่าคงที่เท่านั้น) หรือ. text (ส่วนหนึ่งของรหัส) หากพวกเขาลงไปที่อื่นนั่นคือการตั้งค่าเฉพาะโครงการ
Lundin

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