ฉันจะสร้าง "ตัวเว้นวรรค" ในโครงสร้างหน่วยความจำคลาส C ++ ได้อย่างไร


94

ปัญหา

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

ตอนนี้ฉันทำได้สำเร็จแล้วโดยการใส่uint32_t :96;บิตฟิลด์ที่น่าเกลียดซึ่งจะแทนที่สามคำได้อย่างสะดวก แต่มันจะขึ้นคำเตือนจาก GCC (Bitfield ใหญ่เกินไปที่จะใส่ใน uint32_t) ซึ่งค่อนข้างถูกต้อง

แม้ว่าจะใช้งานได้ดี แต่ก็ไม่ค่อยสะอาดนักเมื่อคุณต้องการแจกจ่ายห้องสมุดที่มีคำเตือนหลายร้อยคำ ...

ฉันจะทำอย่างถูกต้องได้อย่างไร?

เหตุใดจึงมีปัญหาตั้งแต่แรก

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

ตัวอย่างง่ายๆสำหรับอุปกรณ์ต่อพ่วงที่เรียบง่ายมีดังต่อไปนี้: อินพุต / เอาท์พุตวัตถุประสงค์ทั่วไป (GPIO)

union
{

    struct
    {
        GPIO_MAP0_MODER;
        GPIO_MAP0_OTYPER;
        GPIO_MAP0_OSPEEDR;
        GPIO_MAP0_PUPDR;
        GPIO_MAP0_IDR;
        GPIO_MAP0_ODR;
        GPIO_MAP0_BSRR;
        GPIO_MAP0_LCKR;
        GPIO_MAP0_AFR;
        GPIO_MAP0_BRR;
        GPIO_MAP0_ASCR;
    };
    struct
    {
        GPIO_MAP1_CRL;
        GPIO_MAP1_CRH;
        GPIO_MAP1_IDR;
        GPIO_MAP1_ODR;
        GPIO_MAP1_BSRR;
        GPIO_MAP1_BRR;
        GPIO_MAP1_LCKR;
        uint32_t :32;
        GPIO_MAP1_AFRL;
        GPIO_MAP1_AFRH;
        uint32_t :64;
    };
    struct
    {
        uint32_t :192;
        GPIO_MAP2_BSRRL;
        GPIO_MAP2_BSRRH;
        uint32_t :160;
    };
};

โดยที่ทั้งหมดGPIO_MAPx_YYYคือมาโครกำหนดเป็นuint32_t :32หรือประเภทรีจิสเตอร์ (โครงสร้างเฉพาะ)

ที่นี่คุณจะเห็นสิ่งuint32_t :192;ที่ทำงานได้ดี แต่มันทำให้เกิดคำเตือน

สิ่งที่ฉันได้พิจารณาแล้ว:

ฉันอาจจะแทนที่มันด้วยหลาย ๆ อันuint32_t :32;(6 ที่นี่) แต่ฉันมีบางกรณีที่รุนแรงซึ่งฉันมีuint32_t :1344;(42) (ท่ามกลางคนอื่น ๆ ) ดังนั้นฉันจึงไม่อยากเพิ่มบรรทัดอีกประมาณหนึ่งร้อยบรรทัดจาก 8k อื่น ๆ แม้ว่าการสร้างโครงสร้างจะเป็นสคริปต์ก็ตาม

ข้อความเตือนที่แน่นอนมีดังนี้: width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type(ฉันชอบความร่มรื่น)

ฉันไม่อยากจะแก้ปัญหานี้โดยเพียงแค่ลบคำเตือน แต่ใช้ไฟล์

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop

อาจจะเป็นวิธีการแก้ปัญหา ... TheRightFlagถ้าฉันพบ อย่างไรก็ตามในขณะที่ชี้ให้เห็นในกระทู้นี้ , gcc/cp/class.cมีส่วนรหัสเศร้านี้:

warning_at (DECL_SOURCE_LOCATION (field), 0,
        "width of %qD exceeds its type", field);

ซึ่งบอกเราว่าไม่มี-Wxxxธงที่จะลบคำเตือนนี้ ...


26
คุณได้พิจารณาchar unused[12];และอื่น ๆ ?
MM

3
ฉันก็แค่ระงับคำเตือน [class.bit] / 1รับประกันพฤติกรรมของuint32_t :192;.
NathanOliver

3
@NathanOliver ฉันก็ยินดีเช่นกัน แต่ดูเหมือนว่าคำเตือนนี้จะไม่สามารถระงับได้ (ใช้ GCC) หรือฉันไม่พบวิธีการทำเช่นนั้น ยิ่งไปกว่านั้นมันก็ยังไม่ใช่วิธีที่สะอาด (แต่ก็น่าพอใจทีเดียว) ฉันจัดการเพื่อค้นหาแฟล็ก "-W" ที่ถูกต้อง แต่ไม่สามารถใช้เฉพาะกับไฟล์ของฉันเอง (ฉันไม่ต้องการให้ผู้ใช้ลบคำเตือนประเภทนี้สำหรับงานของเขา)
J Faucher

3
BTW คุณสามารถเขียน:42*32แทน:1344
MM

1
ลองใช้วิธีนี้เพื่อระงับคำเตือนไหม gcc.gnu.org/onlinedocs/gcc/…
Hitobat

คำตอบ:


36

ใช้หลายบิตฟิลด์ที่ไม่ระบุชื่อที่อยู่ติดกัน แทนที่จะเป็น:

    uint32_t :160;

ตัวอย่างเช่นคุณมี:

    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;

หนึ่งรายการสำหรับการลงทะเบียนแต่ละรายการที่คุณต้องการไม่ระบุชื่อ

หากคุณมีช่องว่างขนาดใหญ่ให้เติมอาจชัดเจนและมีข้อผิดพลาดน้อยกว่าที่จะใช้มาโครเพื่อทำซ้ำช่องว่าง 32 บิตเดียว ตัวอย่างเช่นกำหนด:

#define REPEAT_2(a) a a
#define REPEAT_4(a) REPEAT_2(a) REPEAT_2(a)
#define REPEAT_8(a) REPEAT_4(a) REPEAT_4(a)
#define REPEAT_16(a) REPEAT_8(a) REPEAT_8(a)
#define REPEAT_32(a) REPEAT_16(a) REPEAT_16(a)

จากนั้นสามารถเพิ่มพื้นที่ 1344 (42 * 32 บิต) ได้ดังนี้:

struct
{
    ...
    REPEAT_32(uint32_t :32;) 
    REPEAT_8(uint32_t :32;) 
    REPEAT_2(uint32_t :32;)
    ...
};

ขอบคุณสำหรับคำตอบ. ฉันได้พิจารณาแล้วว่าอย่างไรก็ตามมันจะเพิ่มมากกว่า 200 บรรทัดในไฟล์บางไฟล์ของฉัน ( uint32_t :1344;อยู่ในสถานที่) ดังนั้นฉันจึงไม่ต้องไปทางนี้ ...
J Faucher

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

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

@JFaucher - แก้ไขแล้ว ไฟล์การแมปการลงทะเบียน I / O จำเป็นต้องมีขนาดใหญ่เนื่องจากมีการลงทะเบียนจำนวนมากโดยปกติคุณจะเขียนเพียงครั้งเดียวและการบำรุงรักษาไม่ใช่ปัญหาเนื่องจากฮาร์ดแวร์มีค่าคงที่ ยกเว้นการ "ซ่อน" การลงทะเบียนที่คุณกำลังดำเนินการบำรุงรักษาด้วยตัวคุณเองหากคุณต้องการเข้าถึงในภายหลัง คุณทราบแน่นอนว่าอุปกรณ์ STM32 ทั้งหมดมีส่วนหัวของแผนที่ที่ลงทะเบียนโดยผู้ขายอยู่แล้ว? มันจะมีโอกาสเกิดข้อผิดพลาดน้อยกว่ามากที่จะใช้สิ่งนั้น
Clifford

2
ฉันเห็นด้วยกับคุณและเพื่อความยุติธรรมฉันคิดว่าฉันจะใช้วิธีใดวิธีหนึ่งในสองวิธีที่แสดงในคำตอบของคุณ ฉันแค่อยากจะแน่ใจว่า C ++ ไม่ได้มีทางออกที่ดีกว่าก่อนที่จะทำเช่นนั้น ฉันทราบดีว่า ST ให้ส่วนหัวเหล่านั้นอย่างไรก็ตามสิ่งเหล่านี้สร้างขึ้นจากการใช้มาโครจำนวนมากและการดำเนินการระดับบิต โครงการของฉันคือการสร้าง C ++ ที่เทียบเท่ากับส่วนหัวซึ่งจะมีโอกาสเกิดข้อผิดพลาดน้อยกว่า (ใช้คลาส enum บิตฟิลด์และอื่น ๆ ) นั่นเป็นเหตุผลที่เราใช้สคริปต์เพื่อ "แปล" ส่วนหัว CMSIS เป็นโครงสร้าง C ++ ของเรา (และพบข้อผิดพลาดบางอย่างในไฟล์ ST btw)
J Faucher

45

แล้ว C ++ เป็นอย่างไรบ้าง?

namespace GPIO {

static volatile uint32_t &MAP0_MODER = *reinterpret_cast<uint32_t*>(0x4000);
static volatile uint32_t &MAP0_OTYPER = *reinterpret_cast<uint32_t*>(0x4004);

}

int main() {
    GPIO::MAP0_MODER = 42;
}

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


1
สิ่งนี้อาจปรับให้เหมาะสมน้อยกว่าโครงสร้างสำหรับการเข้าถึงการลงทะเบียน MMIO หลายรายการจากฟังก์ชันเดียวกัน ด้วยตัวชี้ไปยังที่อยู่ฐานในรีจิสเตอร์คอมไพลเลอร์สามารถใช้คำสั่งโหลด / จัดเก็บที่มีการเคลื่อนย้ายได้ทันทีเช่นldr r0, [r4, #16]ในขณะที่คอมไพเลอร์มีแนวโน้มที่จะพลาดการเพิ่มประสิทธิภาพนั้นโดยแต่ละที่อยู่ที่ประกาศแยก GCC อาจโหลดที่อยู่ GPIO แต่ละรายการลงในทะเบียนแยกกัน (จากพูลตามตัวอักษรแม้ว่าบางส่วนสามารถแสดงเป็นแบบหมุนได้ทันทีในการเข้ารหัส Thumb)
Peter Cordes

4
ปรากฎว่าความกังวลของฉันไม่มีมูล ARM GCC เพิ่มประสิทธิภาพด้วยวิธีนี้เช่นกัน godbolt.org/z/ztB7hi . แต่ทราบว่าคุณต้องการไม่ได้static volatile uint32_t &MAP0_MODER ตัวแปรไม่ได้รวบรวม ( หลีกเลี่ยงการจัดเก็บข้อมูลแบบคงที่สำหรับตัวชี้และเป็นสิ่งที่คุณต้องการสำหรับ MMIO เพื่อหลีกเลี่ยงการกำจัดการจัดเก็บที่ตายแล้วหรือการเพิ่มประสิทธิภาพของการเขียน / อ่านกลับ)inlineinlinestaticvolatile
Peter Cordes

1
@PeterCordes: ตัวแปรอินไลน์เป็นคุณลักษณะใหม่ของ C ++ 17 แต่คุณพูดถูกstaticก็ทำได้ดีไม่แพ้กันสำหรับกรณีนี้ ขอบคุณสำหรับการกล่าวถึงvolatileฉันจะเพิ่มคำตอบของฉัน (และเปลี่ยนแบบอินไลน์เป็นแบบคงที่ดังนั้นจึงใช้ได้กับ C ++ 17 ก่อนหน้า)
geza

2
นี่ไม่ใช่พฤติกรรมที่กำหนดไว้อย่างชัดเจนดูหัวข้อ Twitter นี้และอาจเป็นประโยชน์
Shafik Yaghmour

1
@JFaucher: สร้างเนมสเปซให้มากที่สุดเท่าที่คุณมีโครงสร้างและใช้ฟังก์ชันแบบสแตนด์อโลนในเนมสเปซนั้น GPIOA::togglePin()ดังนั้นคุณจะต้อง
geza

20

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

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

ตัวอย่าง:

uint16_t * const UART1 = (uint16_t *)(0x40000);
const unsigned int UART_STATUS_OFFSET = 1U;
const unsigned int UART_TRANSMIT_REGISTER = 2U;
uint16_t * const UART1_STATUS_REGISTER = (UART1 + UART_STATUS_OFFSET);
uint16_t * const UART1_TRANSMIT_REGISTER = (UART1 + UART_TRANSMIT_REGISTER);

คุณยังสามารถใช้สัญกรณ์อาร์เรย์:

uint16_t status = UART1[UART_STATUS_OFFSET];  

หากคุณต้องใช้โครงสร้าง IMHO วิธีที่ดีที่สุดในการข้ามที่อยู่คือการกำหนดสมาชิกและไม่สามารถเข้าถึงได้:

struct UART1
{
  uint16_t status;
  uint16_t reserved1; // Transmit register
  uint16_t receive_register;
};

ในโครงการหนึ่งของเราเรามีทั้งค่าคงที่และโครงสร้างจากผู้ขายที่แตกต่างกัน (ผู้ขาย 1 ใช้ค่าคงที่ในขณะที่ผู้ขาย 2 ใช้โครงสร้าง)


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

คุณยังสามารถมีได้โดยการสร้างstaticสมาชิกที่อยู่ด้านบนของโครงสร้างโดยสมมติว่าการเติมข้อความอัตโนมัติสามารถแสดงสมาชิกแบบคงที่ได้ หากไม่เป็นเช่นนั้นก็สามารถเป็นฟังก์ชันสมาชิกแบบอินไลน์ได้เช่นกัน
Phil1970

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

1
@ นาธาเนียล: ไม่งั้น; หากชั้นเรียนมีสมาชิกข้อมูลทั้งแบบpublicและprivateไม่คงที่แสดงว่าไม่ใช่ประเภทเค้าโครงมาตรฐานดังนั้นจึงไม่มีการรับประกันการสั่งซื้อที่คุณคิด (และฉันค่อนข้างมั่นใจว่ากรณีการใช้งานของ OP ต้องใช้ประเภทเลย์เอาต์มาตรฐาน)
ruakh

1
อย่าลืมvolatileการประกาศเหล่านั้น BTW สำหรับการลงทะเบียน I / O ที่แมปหน่วยความจำ
Peter Cordes

13

geza มีสิทธิ์ที่คุณไม่ต้องการใช้คลาสสำหรับสิ่งนี้

แต่ถ้าคุณต้องยืนยันวิธีที่ดีที่สุดในการเพิ่มสมาชิกที่ไม่ได้ใช้ของความกว้างnไบต์ก็ทำได้ง่ายๆ:

char unused[n];

หากคุณเพิ่ม pragma เฉพาะการนำไปใช้งานเพื่อป้องกันการเพิ่มช่องว่างโดยพลการให้กับสมาชิกของชั้นเรียนสิ่งนี้สามารถใช้ได้


สำหรับ GNU C / C ++ (gcc, clang และอื่น ๆ ที่รองรับนามสกุลเดียวกัน) หนึ่งในตำแหน่งที่ถูกต้องในการใส่แอตทริบิวต์คือ:

#include <stddef.h>
#include <stdint.h>
#include <assert.h>  // for C11 static_assert, so this is valid C as well as C++

struct __attribute__((packed)) GPIO {
    volatile uint32_t a;
    char unused[3];
    volatile uint32_t b;
};

static_assert(offsetof(struct GPIO, b) == 7, "wrong GPIO struct layout");

(ตัวอย่างบนคอมไพเลอร์คอมไพเลอร์ Godboltแสดงoffsetof(GPIO, b)= 7 ไบต์)


9

หากต้องการขยายคำตอบของ @ Clifford's และ @Adam Kotwasinski:

#define REP10(a)        a a a a a a a a a a
#define REP1034(a)      REP10(REP10(REP10(a))) REP10(a a a) a a a a

struct foo {
        int before;
        REP1034(unsigned int :32;)
        int after;
};
int main(void){
        struct foo bar;
        return 0;
}

ฉันได้รวมคำแนะนำของคุณไว้ในคำตอบของฉันตามข้อกำหนดเพิ่มเติมในความคิดเห็น เครดิตที่ครบกำหนดเครดิต
Clifford

7

หากต้องการขยายคำตอบของ Clifford คุณสามารถมาโครบิตฟิลด์ที่ไม่ระบุชื่อได้ตลอดเวลา

ดังนั้นแทนที่จะเป็น

uint32_t :160;

ใช้

#define EMPTY_32_1 \
 uint32_t :32
#define EMPTY_32_2 \
 uint32_t :32;     \ // I guess this also can be replaced with uint64_t :64
 uint32_t :32
#define EMPTY_32_3 \
 uint32_t :32;     \
 uint32_t :32;     \
 uint32_t :32
#define EMPTY_UINT32(N) EMPTY_32_ ## N

แล้วก็ใช้แบบ

struct A {
  EMPTY_UINT32(3);
  /* which resolves to EMPTY_32_3, which then resolves to real declarations */
}

น่าเสียดายที่คุณจะต้องใช้EMPTY_32_Xตัวแปรให้มากที่สุดเท่าที่คุณมี :( แต่ยังอนุญาตให้คุณมีการประกาศเดียวในโครงสร้างของคุณ


5
การใช้มาโคร Boost CPP ฉันคิดว่าคุณสามารถใช้การเรียกซ้ำเพื่อหลีกเลี่ยงการสร้างมาโครที่จำเป็นทั้งหมดด้วยมือ
Peter Cordes

3
คุณสามารถเรียงซ้อนได้ (สูงสุดถึงขีด จำกัด การเรียกซ้ำของตัวประมวลผลล่วงหน้า แต่โดยปกติจะเพียงพอ) ดังนั้น#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1และ#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1ฯลฯ
Miral

บางที @PeterCordes แต่แท็กระบุว่าอาจต้องใช้ความเข้ากันได้ของบูธ C และ C ++
Clifford

2
C และ C ++ ใช้ตัวประมวลผลล่วงหน้า C เดียวกัน ฉันไม่เห็นปัญหาอื่นนอกจากอาจทำให้ส่วนหัวการเพิ่มที่จำเป็นสำหรับ C พวกเขาใส่สิ่ง CPP-macro ไว้ในส่วนหัวแยกต่างหาก
Peter Cordes

1

เพื่อกำหนดตัวเว้นวรรคขนาดใหญ่เป็นกลุ่ม 32 บิต

#define M_32(x)   M_2(M_16(x))
#define M_16(x)   M_2(M_8(x))
#define M_8(x)    M_2(M_4(x))
#define M_4(x)    M_2(M_2(x))
#define M_2(x)    x x

#define SPACER int : 32;

struct {
    M_32(SPACER) M_8(SPACER) M_4(SPACER)
};

1

ฉันคิดว่ามันจะเป็นประโยชน์ในการแนะนำโครงสร้างเพิ่มเติม ซึ่งอาจช่วยแก้ปัญหาของตัวเว้นวรรคได้

ตั้งชื่อตัวแปร

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

ในขั้นตอนแรกฉันจะพิจารณาแยกstruct :

// GpioMap0.h
#pragma once

// #includes

namespace Gpio {
struct Map0 {
    GPIO_MAP0_MODER;
    GPIO_MAP0_OTYPER;
    GPIO_MAP0_OSPEEDR;
    GPIO_MAP0_PUPDR;
    GPIO_MAP0_IDR;
    GPIO_MAP0_ODR;
    GPIO_MAP0_BSRR;
    GPIO_MAP0_LCKR;
    GPIO_MAP0_AFR;
    GPIO_MAP0_BRR;
    GPIO_MAP0_ASCR;
};
} // namespace Gpio

// GpioMap1.h
#pragma once

// #includes

namespace Gpio {
struct Map1 {
    // fields
};
} // namespace Gpio

// ... others headers ...

และสุดท้ายส่วนหัวส่วนกลาง:

// Gpio.h
#pragma once

#include "GpioMap0.h"
#include "GpioMap1.h"
// ... other headers ...

namespace Gpio {
union Gpio {
    Map0 map0;
    Map1 map1;
    // ... others ...
};
} // namespace Gpio

ตอนนี้ฉันสามารถเขียนไฟล์ void special_map0(Gpio:: Map0 volatile& map);และดูภาพรวมคร่าวๆของสถาปัตยกรรมที่มีอยู่ทั้งหมดได้อย่างรวดเร็ว

ตัวเว้นวรรคแบบธรรมดา

ด้วยการแบ่งคำจำกัดความในส่วนหัวหลาย ๆ ส่วนหัวแต่ละส่วนจึงสามารถจัดการได้มากขึ้น

ดังนั้นแนวทางเริ่มต้นของฉันเพื่อให้ตรงกับความต้องการของคุณคือการทำซ้ำ std::uint32_t:32;ดังนั้นวิธีการเริ่มต้นของฉันให้ตรงตอบสนองความต้องการของคุณจะติดด้วยซ้ำใช่มันเพิ่มเส้น 100s สองสามเส้นใน 8k บรรทัดที่มีอยู่ แต่เนื่องจากแต่ละส่วนหัวมีขนาดเล็กลงทีละรายการจึงอาจไม่เลวร้ายนัก

หากคุณยินดีที่จะพิจารณาวิธีแก้ปัญหาที่แปลกใหม่มากขึ้นแม้ว่า ...

ขอแนะนำ $.

เป็นความจริงที่ไม่ค่อยมีใครรู้ว่า$เป็นอักขระที่ใช้งานได้สำหรับตัวระบุ C ++ มันเป็นอักขระเริ่มต้นที่ทำงานได้ (ไม่เหมือนตัวเลข)

การ$ปรากฏในซอร์สโค้ดอาจทำให้คิ้วขมวดและ$$$$จะดึงดูดความสนใจในระหว่างการตรวจสอบโค้ด นี่คือสิ่งที่คุณสามารถใช้ประโยชน์จาก:

#define GPIO_RESERVED(Index_, N_) std::uint32_t $$$$##Index_[N_];

struct Map3 {
    GPIO_RESERVED(0, 6);
    GPIO_MAP2_BSRRL;
    GPIO_MAP2_BSRRH;
    GPIO_RESERVED(1, 5);
};

คุณยังสามารถรวม "ผ้าสำลี" แบบธรรมดาเป็นตะขอก่อนคอมมิตหรือใน CI ของคุณซึ่งมองหา$$$$ในรหัส C ++ ที่มีการผูกมัดและปฏิเสธข้อตกลงดังกล่าว


1
โปรดจำไว้ว่ากรณีการใช้งานเฉพาะของ OP ใช้สำหรับอธิบายการลงทะเบียน I / O ที่แมปหน่วยความจำไปยังคอมไพลเลอร์ มันไม่สมเหตุสมผลเลยที่จะคัดลอกโครงสร้างทั้งหมดตามค่า (และสมาชิกแต่ละคนGPIO_MAP0_MODERก็น่าจะvolatileชอบ) แม้ว่าการอ้างอิงหรือการใช้เทมเพลตพารามิเตอร์ของสมาชิกที่ไม่ระบุชื่อก่อนหน้านี้อาจเป็นประโยชน์ และสำหรับกรณีทั่วไปของโครงสร้างช่องว่างภายในให้แน่ใจ แต่กรณีการใช้งานอธิบายว่าเหตุใด OP จึงไม่เปิดเผยตัวตน
Peter Cordes

คุณอาจใช้$$$padding##Index_[N_];เพื่อทำให้ชื่อฟิลด์อธิบายตัวเองได้มากขึ้นในกรณีที่เคยมีการเติมข้อความอัตโนมัติหรือเมื่อทำการดีบั๊ก (หรือzz$$$paddingจะจัดเรียงตามGPIO...ชื่อเพราะจุดรวมของแบบฝึกหัดนี้ตาม OP นั้นดีกว่าการเติมข้อความอัตโนมัติสำหรับชื่อตำแหน่ง I / O ที่แมปหน่วยความจำ)
Peter Cordes

@PeterCordes: ฉันสแกนคำตอบอีกครั้งเพื่อตรวจสอบและไม่เคยเห็นการกล่าวถึงการคัดลอกเลย ฉันลืมvolatileคุณสมบัติในการอ้างอิงแม้ว่าจะได้รับการแก้ไขแล้ว สำหรับการตั้งชื่อ; ฉันจะปล่อยให้มันขึ้นอยู่กับ OP มีหลายรูปแบบ (ช่องว่างภายใน, สงวนไว้, ... ) และแม้แต่คำนำหน้า "ดีที่สุด" สำหรับการเติมข้อความอัตโนมัติอาจขึ้นอยู่กับ IDE ที่มีอยู่แม้ว่าฉันจะพอใจกับแนวคิดในการปรับแต่งการเรียง
Matthieu M.

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

1
@PeterCordes: ฉันคิดว่าจะส่งต่อโดยอ้างอิงตามที่แสดงในภายหลัง ฉันคิดว่ามันอึดอัดที่โครงสร้างของ OP ป้องกันไม่ให้สร้าง "โมดูล" ที่สามารถพิสูจน์ได้แบบคงที่เพื่อเข้าถึงสถาปัตยกรรมเฉพาะ (โดยการอ้างอิงถึงเฉพาะstruct) และunionท้ายที่สุดก็ถูกแพร่กระจายไปทุกหนทุกแห่งแม้ในบิตเฉพาะสถาปัตยกรรมซึ่ง อาจสนใจคนอื่นน้อยลง
Matthieu M.

0

แม้ว่าฉันยอมรับว่าไม่ควรใช้โครงสร้างสำหรับการเข้าถึงพอร์ต MCU I / O แต่คำถามดั้งเดิมสามารถตอบได้ด้วยวิธีนี้:

struct __attribute__((packed)) test {
       char member1;
       char member2;
       volatile struct __attribute__((packed))
       {
       private:
              volatile char spacer_bytes[7];
       }  spacer;
       char member3;
       char member4;
};

คุณอาจต้องแทนที่__attribute__((packed))ด้วย#pragma packหรือคล้ายกันขึ้นอยู่กับไวยากรณ์ของคอมไพเลอร์ของคุณ

การผสมสมาชิกส่วนตัวและสาธารณะในโครงสร้างโดยปกติจะส่งผลให้เค้าโครงหน่วยความจำนั้นไม่ได้รับการรับรองโดยมาตรฐาน C ++ อีกต่อไป แต่ถ้าทุกคนไม่ใช่สมาชิกคงที่ของ struct เป็นส่วนตัวก็ถือว่ายังคง POD / รูปแบบมาตรฐานและเพื่อให้มี structs ที่ฝังไว้

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

โปรดทราบว่าspacerสมาชิกไม่ได้เป็นส่วนตัวดังนั้นจึงยังสามารถเข้าถึงข้อมูลได้ด้วยวิธีนี้:

(char*)(void*)&testobj.spacer;

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


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

ขอบคุณแก้ไขแล้ว
Jack White

-1

น้ำยาป้องกัน.

อย่าทำสิ่งนี้: ผสมฟิลด์ส่วนตัวและฟิลด์สาธารณะ

บางทีมาโครที่มีตัวนับเพื่อสร้างชื่อตัวแปร uniqie จะมีประโยชน์หรือไม่?

#define CONCAT_IMPL( x, y ) x##y
#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y )
#define RESERVED MACRO_CONCAT(Reserved_var, __COUNTER__) 


struct {
    GPIO_MAP1_CRL;
    GPIO_MAP1_CRH;
    GPIO_MAP1_IDR;
    GPIO_MAP1_ODR;
    GPIO_MAP1_BSRR;
    GPIO_MAP1_BRR;
    GPIO_MAP1_LCKR;
private:
    char RESERVED[4];
public:
    GPIO_MAP1_AFRL;
    GPIO_MAP1_AFRH;
private:
    char RESERVED[8];
};

4
คิดไม่ดี; คุณเพียงแค่สูญเสียสมาชิกสั่งซื้อบนลงด้านล่าง
Lightness Races ใน Orbit

3
ตกลง. ถ้าไม่มีใครสนใจฉันจะทิ้งคำตอบไว้ว่าจะไม่ทำอะไร
Robert Andrzejuk

4
@NicHartley เมื่อพิจารณาจากจำนวนคำตอบเราใกล้เคียงกับคำถาม "การวิจัย" ในการวิจัยความรู้เรื่องอับจนยังคงเป็นความรู้หลีกเลี่ยงไม่ให้ผู้อื่นใช้ทางที่ผิด +1 สำหรับความกล้าหาญ
Oliv

1
@Oliv และฉัน -1 เพราะ OP ต้องการบางอย่างคำตอบนี้ละเมิดข้อกำหนดดังนั้นจึงเป็นคำตอบที่ไม่ดี ฉันไม่ได้ตัดสินคุณค่าในเชิงบวกหรือเชิงลบต่อบุคคลใด ๆ อย่างชัดเจนในการแสดงความคิดเห็น - เพียงแค่คำตอบ ฉันคิดว่าเราทั้งคู่ยอมรับได้ว่ามันแย่ สิ่งที่พูดเกี่ยวกับบุคคลนั้นเป็นเรื่องนอกประเด็นสำหรับไซต์นี้ (แม้ว่า IMO ทุกคนที่เต็มใจที่จะใช้เวลาในการมีส่วนร่วมในความคิดนั้นกำลังทำสิ่งที่ถูกต้องแม้ว่าความคิดนั้นจะไม่
คดีของ Fund Monica

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