Làm cách nào để tạo một “khoảng đệm” trong cấu trúc bộ nhớ lớp C ++?


94

Vấn đề

Trong ngữ cảnh nhúng trần kim loại cấp thấp , tôi muốn tạo một khoảng trống trong bộ nhớ, trong cấu trúc C ++ và không có bất kỳ tên nào, để cấm người dùng truy cập vào vị trí bộ nhớ đó.

Ngay bây giờ, tôi đã đạt được điều đó bằng cách đặt một uint32_t :96;bitfield xấu xí sẽ thuận tiện thay thế cho ba từ, nhưng nó sẽ đưa ra cảnh báo từ GCC (Bitfield quá lớn để vừa với uint32_t), điều này khá hợp pháp.

Mặc dù nó hoạt động tốt, nhưng nó không được sạch sẽ cho lắm khi bạn muốn phân phối một thư viện với hàng trăm cảnh báo đó ...

Làm thế nào để làm điều đó đúng cách?

Tại sao có vấn đề ngay từ đầu?

Dự án mà tôi đang thực hiện bao gồm việc xác định cấu trúc bộ nhớ của các thiết bị ngoại vi khác nhau của toàn bộ dòng vi điều khiển (STMicroelectronics STM32). Để làm như vậy, kết quả là một lớp chứa liên hợp của một số cấu trúc xác định tất cả các thanh ghi, tùy thuộc vào bộ vi điều khiển được nhắm mục tiêu.

Một ví dụ đơn giản cho một thiết bị ngoại vi khá đơn giản như sau: Một Đầu vào / Đầu ra Mục đích Chung (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;
    };
};

Trong đó tất cả đều GPIO_MAPx_YYYlà macro, được định nghĩa là uint32_t :32hoặc kiểu thanh ghi (cấu trúc chuyên dụng).

Ở đây, bạn thấy cái uint32_t :192;nào hoạt động tốt, nhưng nó kích hoạt cảnh báo.

Những gì tôi đã xem xét cho đến nay:

Tôi có thể đã thay thế nó bằng một số uint32_t :32;(6 ở đây), nhưng tôi có một số trường hợp cực đoan mà tôi có uint32_t :1344;(42) (trong số những người khác). Vì vậy, tôi không muốn thêm khoảng một trăm dòng trên 8k dòng khác, mặc dù việc tạo cấu trúc đã được viết sẵn.

Thông điệp cảnh báo chính xác là một cái gì đó như: width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type(Tôi chỉ thích nó mờ ám như thế nào).

Tôi thà không giải quyết việc này bằng cách đơn giản loại bỏ các cảnh báo, nhưng việc sử dụng

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

có thể là một giải pháp ... nếu tôi tìm thấy TheRightFlag. Tuy nhiên, như đã chỉ ra trong chủ đề này , gcc/cp/class.cvới phần mã đáng buồn này:

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

Điều này cho chúng ta biết rằng không có -Wxxxcờ nào để xóa cảnh báo này ...


26
bạn đã đồng ý char unused[12];và như vậy?
MM

3
Tôi chỉ muốn ngăn chặn cảnh báo. [class.bit] / 1 đảm bảo hành vi của uint32_t :192;.
NathanOliver

3
@NathanOliver Tôi cũng rất vui, nhưng có vẻ như cảnh báo này không thể ngăn chặn được (Sử dụng GCC) hoặc tôi không tìm thấy cách thực hiện. Hơn nữa, nó vẫn không phải là một cách làm sạch sẽ (nhưng nó sẽ khá hài lòng). Tôi quản lý để tìm ra đúng "-W" cờ nhưng không quản lý để áp dụng nó chỉ trên các tập tin của riêng tôi (Tôi không muốn người dùng để loại bỏ loại này cảnh báo cho công việc của mình)
J Faucher

3
BTW bạn có thể viết :42*32thay vì:1344
MM

1
Hãy thử điều này để ngăn chặn cảnh báo? gcc.gnu.org/onlineocs/gcc/…
Hitobat

Câu trả lời:


36

Sử dụng nhiều trường bit ẩn danh liền kề. Vì vậy, thay vì:

    uint32_t :160;

ví dụ, bạn có:

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

Một cho mỗi đăng ký bạn muốn ẩn danh.

Nếu bạn có khoảng trống lớn để lấp đầy, nó có thể rõ ràng hơn và ít bị lỗi hơn khi sử dụng macro để lặp lại không gian 32 bit duy nhất. Ví dụ, đã cho:

#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)

Sau đó, một không gian 1344 (42 * 32 bit) có thể được thêm vào do đó:

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

Cảm ơn vì câu trả lời. Tôi đã cho rằng, tuy nhiên nó sẽ làm tăng thêm hơn 200 dòng trên một số tác phẩm của tôi ( uint32_t :1344;là ở nơi này) vì vậy tôi không muốn phải đi theo cách này ...
J Faucher

1
@JFaucher Đã thêm một giải pháp khả thi cho yêu cầu số lượng dòng của bạn. Nếu bạn có những yêu cầu như vậy, bạn có thể đề cập đến chúng trong câu hỏi để tránh nhận được những câu trả lời không đáp ứng được chúng.
Clifford

Cảm ơn vì đã chỉnh sửa và xin lỗi vì đã không nêu rõ số dòng. Ý của tôi là mã của tôi đã rất khó để đi sâu vào vì có rất nhiều dòng và tôi muốn tránh thêm quá nhiều dòng. Do đó, tôi đang hỏi liệu ai đó có biết cách "sạch" hoặc "chính thức" để tránh sử dụng trường bit ẩn danh liền kề (ngay cả khi cách đó hoạt động tốt). Tuy nhiên, cách tiếp cận vĩ mô có vẻ tốt đối với tôi. Nhân tiện, trong ví dụ của bạn, bạn không có không gian 36 * 32 bit sao?
J Faucher

@JFaucher - đã sửa. Các tệp ánh xạ thanh ghi I / O nhất thiết phải lớn do số lượng thanh ghi lớn - thông thường bạn ghi một lần và việc bảo trì không phải là vấn đề vì phần cứng là một hằng số. Ngoại trừ bằng cách "ẩn" các sổ đăng ký, bạn đang thực hiện công việc bảo trì cho chính mình nếu sau này bạn cần truy cập chúng. Tất nhiên bạn có biết rằng tất cả các thiết bị STM32 đều đã có tiêu đề bản đồ đăng ký do nhà cung cấp cung cấp? Nó sẽ ít bị lỗi hơn khi sử dụng nó.
Clifford

2
Tôi đồng ý với bạn và công bằng mà nói, tôi nghĩ rằng tôi sẽ đi theo một trong hai phương pháp được hiển thị trong câu trả lời của bạn. Tôi chỉ muốn chắc chắn rằng C ++ không cung cấp giải pháp tốt hơn trước khi làm như vậy. Tôi biết rõ rằng ST cung cấp các tiêu đề đó, tuy nhiên chúng được xây dựng dựa trên việc sử dụng rộng rãi các macro và các phép toán bit. Dự án của tôi là xây dựng một C ++ tương đương với các tiêu đề đó sẽ ít bị lỗi hơn (sử dụng các lớp enum, trường bit, v.v.). Đó là lý do tại sao chúng tôi sử dụng một kịch bản để "dịch" các tiêu đề CMSIS vào cấu trúc của chúng tôi C ++ (và tìm thấy một số sai sót trong ST file btw)
J Faucher

45

Làm thế nào về một C ++ - cách?

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;
}

Bạn nhận được tự động hoàn thành vì GPIOkhông gian tên và không cần đệm giả. Thậm chí, nó còn rõ ràng hơn những gì đang xảy ra, vì bạn có thể thấy địa chỉ của từng thanh ghi, bạn không cần phải dựa vào hành vi đệm của trình biên dịch nữa.


1
Điều này có thể tối ưu hóa kém hơn một cấu trúc để truy cập vào nhiều thanh ghi MMIO từ cùng một chức năng. Với một con trỏ đến địa chỉ cơ sở trong một thanh ghi, trình biên dịch có thể sử dụng các lệnh tải / lưu trữ với các chuyển vị tức thì ldr r0, [r4, #16], trong khi các trình biên dịch có nhiều khả năng bỏ lỡ sự tối ưu hóa đó với từng địa chỉ được khai báo riêng biệt. GCC có thể sẽ tải từng địa chỉ GPIO vào một thanh ghi riêng biệt. (Từ một nhóm theo nghĩa đen, mặc dù một số trong số chúng có thể được biểu diễn dưới dạng xoay vòng ngay lập tức trong mã hóa Ngón tay cái.)
Peter Cordes

4
Hóa ra lo lắng của tôi là vô căn cứ; ARM GCC cũng tối ưu hóa theo cách này. godbolt.org/z/ztB7hi . Nhưng lưu ý rằng bạn muốn static volatile uint32_t &MAP0_MODER, không phải inline. Một inlinebiến không biên dịch. ( statictránh có bất kỳ bộ lưu trữ tĩnh nào cho con trỏ và đó volatilelà chính xác những gì bạn muốn đối với MMIO để tránh loại bỏ lưu trữ chết hoặc tối ưu hóa ghi / đọc lại.)
Peter Cordes

1
@PeterCordes: biến nội tuyến là một tính năng mới của C ++ 17. Nhưng bạn nói đúng, staticcũng làm tốt cho trường hợp này. Cảm ơn bạn đã đề cập volatile, tôi sẽ thêm nó vào câu trả lời của mình (và thay đổi nội tuyến thành tĩnh, vì vậy nó hoạt động cho trước C ++ 17).
geza

2
Đây không phải là nghiêm ngặt được xác định rõ hành vi nhìn thấy chủ đề twitter nàycó lẽ là một trong những này hữu ích
Shafik Yaghmour

1
@JFaucher: tạo càng nhiều không gian tên như cấu trúc bạn có và sử dụng các hàm độc lập trong không gian tên đó. Vì vậy, bạn sẽ có GPIOA::togglePin().
geza

20

Trong lĩnh vực hệ thống nhúng, bạn có thể mô hình hóa phần cứng bằng cách sử dụng cấu trúc hoặc bằng cách xác định con trỏ đến địa chỉ thanh ghi.

Mô hình hóa theo cấu trúc không được khuyến khích vì trình biên dịch được phép thêm phần đệm giữa các thành viên cho mục đích liên kết (mặc dù nhiều trình biên dịch cho hệ thống nhúng có pragma để đóng gói cấu trúc).

Thí dụ:

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);

Bạn cũng có thể sử dụng ký hiệu mảng:

uint16_t status = UART1[UART_STATUS_OFFSET];  

Nếu bạn phải sử dụng cấu trúc, IMHO, thì phương pháp tốt nhất để bỏ qua địa chỉ là xác định một thành viên và không truy cập nó:

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

Trong một trong những dự án của chúng tôi, chúng tôi có cả hằng số và cấu trúc từ các nhà cung cấp khác nhau (nhà cung cấp 1 sử dụng hằng số trong khi nhà cung cấp 2 sử dụng cấu trúc).


Cảm ơn câu trả lời của bạn. Tuy nhiên, tôi chọn sử dụng cách tiếp cận cấu trúc để giúp người dùng dễ dàng thực hiện công việc khi họ có tính năng tự động hoàn thành (Chỉ bạn mới có các thuộc tính phù hợp được hiển thị) và tôi không muốn "hiển thị" cho người dùng các vị trí dành riêng như chỉ ra trong một bình luận của bài viết đầu tiên của tôi.
J Faucher

Bạn vẫn có thể có điều đó bằng cách tạo các staticthành viên địa chỉ ở trên của một cấu trúc giả định rằng tính năng tự động hoàn thành có thể hiển thị các thành viên tĩnh. Nếu không, nó cũng có thể là các hàm thành viên nội tuyến.
Phil1970

@JFaucher Tôi không phải là người sử dụng hệ thống nhúng và chưa thử nghiệm điều này, nhưng vấn đề tự động hoàn thành sẽ không được giải quyết bằng cách khai báo thành viên được bảo lưu là riêng tư? (Bạn có thể khai báo các thành viên tư nhân trong một cấu trúc, và bạn có thể sử dụng public:private:nhiều lần như bạn muốn, để có được thứ tự đúng của các trường.)
Nathaniel

1
@Nathaniel: Không phải vậy; nếu một lớp có cả thành viên dữ liệu tĩnh publicprivatekhông tĩnh, thì nó không phải là kiểu bố cục tiêu chuẩn , vì vậy nó không cung cấp các đảm bảo về thứ tự mà bạn đang nghĩ đến. (Và tôi khá chắc chắn rằng trường hợp sử dụng của OP yêu cầu kiểu bố cục tiêu chuẩn.)
ruakh

1
Đừng quên volatiletrên các khai báo đó, BTW, cho các thanh ghi I / O được ánh xạ bộ nhớ.
Peter Cordes

13

geza đúng rằng bạn thực sự không muốn sử dụng các lớp cho việc này.

Nhưng, nếu bạn muốn nhấn mạnh, cách tốt nhất để thêm một phần tử không sử dụng của chiều rộng n byte, chỉ đơn giản là làm như vậy:

char unused[n];

Nếu bạn thêm một pragma dành riêng cho việc triển khai để ngăn việc thêm phần đệm tùy ý vào các thành viên của lớp, điều này có thể hoạt động.


Đối với GNU C / C ++ (gcc, clang và những thứ khác hỗ trợ cùng một phần mở rộng), một trong những vị trí hợp lệ để đặt thuộc tính là:

#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");

(ví dụ trên trình khám phá trình biên dịch Godbolt hiển thị offsetof(GPIO, b)= 7 byte.)


9

Để mở rộng câu trả lời của @ Clifford và @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;
}

Tôi đã kết hợp một biến thể của đề xuất của bạn trong câu trả lời của tôi theo các yêu cầu khác trong một nhận xét. Tín dụng có tín dụng đến hạn.
Clifford

7

Để mở rộng câu trả lời của Clifford, bạn luôn có thể lấy macro ra các trường bit ẩn danh.

Vì vậy, thay vì

uint32_t :160;

sử dụng

#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

Và sau đó sử dụng nó như

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

Thật không may, bạn sẽ cần nhiều EMPTY_32_Xbiến thể với số byte bạn có :( Tuy nhiên, nó cho phép bạn có các khai báo duy nhất trong cấu trúc của mình.


5
Sử dụng các macro Boost CPP, tôi nghĩ bạn có thể sử dụng đệ quy để tránh phải tạo thủ công tất cả các macro cần thiết.
Peter Cordes

3
Bạn có thể phân tầng chúng (lên đến giới hạn đệ quy của bộ xử lý trước, nhưng điều đó thường là dư dả). Vì vậy #define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1, #define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1v.v.
Miral

Có lẽ là @PeterCordes, nhưng các thẻ chỉ ra rằng có lẽ cần có khả năng tương thích booth C và C ++.
Clifford,

2
C và C ++ sử dụng cùng một bộ tiền xử lý C; Tôi không thấy có vấn đề gì ngoài việc có thể tạo tiêu đề tăng cường cần thiết cho C. Họ đặt nội dung macro CPP trong một tiêu đề riêng.
Peter Cordes

1

Để xác định một khoảng đệm lớn dưới dạng các nhóm 32 bit.

#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

Tôi nghĩ sẽ có lợi nếu giới thiệu thêm một số cấu trúc; do đó có thể giải quyết vấn đề về bộ đệm.

Đặt tên cho các biến thể

Mặc dù không gian tên phẳng rất hay, nhưng vấn đề là bạn phải kết thúc với một bộ sưu tập các trường phức tạp và không có cách nào đơn giản để chuyển tất cả các trường liên quan lại với nhau. Hơn nữa, bằng cách sử dụng các cấu trúc ẩn danh trong một liên hợp ẩn danh, bạn không thể chuyển các tham chiếu đến chính các cấu trúc hoặc sử dụng chúng làm tham số mẫu.

Do đó, bước đầu tiên, tôi sẽ xem xét việc phá vỡ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 ...

Và cuối cùng, tiêu đề chung:

// Gpio.h
#pragma once

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

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

Bây giờ, tôi có thể viết void special_map0(Gpio:: Map0 volatile& map);, cũng như có được một cái nhìn tổng quan về tất cả các kiến ​​trúc có sẵn trong nháy mắt.

Dấu cách đơn giản

Với định nghĩa được chia thành nhiều tiêu đề, các tiêu đề riêng lẻ dễ quản lý hơn nhiều.

Do đó, cách tiếp cận ban đầu của tôi để đáp ứng chính xác yêu cầu của bạn là gắn bó với việc lặp lại std::uint32_t:32;. Có, nó thêm một vài dòng 100s vào dòng 8k hiện có, nhưng vì mỗi tiêu đề nhỏ hơn riêng lẻ, nó có thể không tệ bằng.

Tuy nhiên, nếu bạn sẵn sàng xem xét các giải pháp kỳ lạ hơn ...

Giới thiệu $.

Một thực tế ít được biết đến $là một ký tự khả thi cho các mã định danh C ++; nó thậm chí còn là một ký tự bắt đầu khả thi (không giống như các chữ số).

Một sự $xuất hiện trong mã nguồn có thể sẽ khiến bạn phải kinh ngạc và $$$$chắc chắn sẽ thu hút sự chú ý trong quá trình đánh giá mã. Đây là thứ mà bạn có thể dễ dàng tận dụng:

#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);
};

Bạn thậm chí có thể đặt cùng nhau một "lint" đơn giản làm móc cam kết trước hoặc trong CI của bạn để tìm kiếm $$$$mã C ++ đã cam kết và từ chối các cam kết đó.


1
Hãy nhớ rằng trường hợp sử dụng cụ thể của OP là để mô tả các thanh ghi I / O được ánh xạ bộ nhớ tới trình biên dịch. Sẽ không bao giờ có ý nghĩa nếu sao chép toàn bộ cấu trúc theo giá trị. (Và mỗi thành viên như thế GPIO_MAP0_MODERcó lẽ là volatile.) Tuy nhiên, việc sử dụng tham chiếu hoặc tham số mẫu của các thành viên ẩn danh trước đây có thể hữu ích. Và đối với trường hợp chung của cấu trúc đệm, chắc chắn. Nhưng use-case giải thích tại sao OP lại để chúng ẩn danh.
Peter Cordes

Bạn có thể sử dụng $$$padding##Index_[N_];để làm cho tên trường dễ hiểu hơn trong trường hợp nó xuất hiện trong chế độ tự động hoàn thành hoặc khi gỡ lỗi. (Hoặc zz$$$paddingđể sắp xếp nó theo GPIO...tên, bởi vì toàn bộ điểm của bài tập này theo OP là tự động hoàn thành đẹp hơn cho các tên vị trí I / O được ánh xạ bộ nhớ.)
Peter Cordes

@PeterCordes: Tôi đã quét lại câu trả lời để kiểm tra và chưa bao giờ thấy đề cập đến việc sao chép. Tuy nhiên, tôi đã quên định tính volatiletrên tham chiếu, đã được sửa chữa. Đối với việc đặt tên; Tôi sẽ để nó cho OP. Có nhiều biến thể (đệm, dành riêng, ...) và thậm chí tiền tố "tốt nhất" để tự động hoàn thành có thể phụ thuộc vào IDE hiện có, mặc dù tôi đánh giá cao ý tưởng điều chỉnh việc sắp xếp.
Matthieu M.

Tôi đang đề cập đến " và không có cách nào đơn giản để chuyển tất cả các trường liên quan lại với nhau ", nghe giống như gán cấu trúc và phần còn lại của câu về việc đặt tên cho các thành viên cấu trúc của liên minh.
Peter Cordes

1
@PeterCordes: Tôi đã nghĩ đến việc chuyển qua tài liệu tham khảo, như được minh họa ở phần sau. Tôi thấy thật khó xử khi cấu trúc của OP ngăn họ tạo "mô-đun" có thể được chứng minh tĩnh để chỉ truy cập vào một kiến ​​trúc cụ thể (bằng cách tham chiếu đến cấu trúc cụ thể struct) và unioncuối cùng nó sẽ được truyền đi khắp nơi ngay cả trong các bit dành riêng cho kiến ​​trúc. có thể quan tâm ít hơn đến những người khác.
Matthieu M.

0

Mặc dù tôi đồng ý rằng không nên sử dụng cấu trúc để truy cập cổng I / O MCU, câu hỏi ban đầu có thể được trả lời theo cách này:

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

Bạn có thể cần thay thế __attribute__((packed))bằng #pragma packhoặc tương tự tùy thuộc vào cú pháp trình biên dịch của bạn.

Việc trộn các thành viên private và public trong một cấu trúc thường dẫn đến việc bố trí bộ nhớ không còn được đảm bảo bởi tiêu chuẩn C ++. Tuy nhiên, nếu tất cả các thành viên không tĩnh của một cấu trúc là riêng tư, thì nó vẫn được coi là POD / bố cục tiêu chuẩn và các cấu trúc nhúng chúng cũng vậy.

Vì lý do nào đó mà gcc đưa ra cảnh báo nếu một thành viên của cấu trúc ẩn danh là riêng tư, vì vậy tôi phải đặt tên cho nó. Ngoài ra, gói nó vào một cấu trúc ẩn danh khác cũng sẽ loại bỏ cảnh báo (đây có thể là một lỗi).

Lưu ý rằng spacerbản thân thành viên không phải là riêng tư, vì vậy dữ liệu vẫn có thể được truy cập theo cách này:

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

Tuy nhiên, một biểu thức như vậy trông giống như một vụ hack rõ ràng và hy vọng sẽ không được sử dụng mà không có lý do thực sự chính đáng, chứ đừng nói là một sai lầm.


1
Người dùng không thể khai báo số nhận dạng trong bất kỳ không gian tên nào chứa dấu gạch dưới kép ở bất kỳ vị trí nào trong tên (trong C ++ hoặc chỉ ở đầu trong C); làm như vậy làm cho mã không hợp lệ. Những cái tên này được dành riêng cho việc triển khai và do đó trên lý thuyết có thể xung đột với tên của bạn theo những cách kinh khủng và tinh vi. Dù sao, trình biên dịch không có nghĩa vụ giữ lại mã của bạn nếu nó chứa chúng. Những cái tên như vậy không phải là cách nhanh chóng để lấy tên 'nội bộ' cho mục đích sử dụng của riêng bạn.
underscore_d

Cảm ơn, đã sửa nó.
Jack White

-1

Giải pháp chống.

KHÔNG LÀM VIỆC NÀY: Trộn các trường riêng tư và công khai.

Có thể một macro có bộ đếm để tạo tên biến uniqie sẽ hữu ích?

#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];
};


3
Đồng ý. Nếu không ai phiền, tôi sẽ để lại câu trả lời là không nên làm gì.
Robert Andrzejuk

4
@NicHartley Xem xét số lượng câu trả lời, chúng ta gần giống với một câu hỏi "nghiên cứu". Trong nghiên cứu, kiến ​​thức của những người không hiểu vẫn là kiến ​​thức, nó tránh cho người khác đi theo con đường sai lầm. +1 cho bản lĩnh.
Oliv

1
@Oliv Và tôi đã -1 bởi vì OP yêu cầu một cái gì đó, câu trả lời này đã vi phạm yêu cầu, và do đó nó là một câu trả lời tồi. Tôi rõ ràng không đưa ra bất kỳ đánh giá giá trị nào, tích cực hay tiêu cực, về người đó, trong cả hai nhận xét - chỉ dựa trên câu trả lời. Tôi nghĩ cả hai chúng ta đều có thể đồng ý rằng điều đó thật tệ. Những gì nói về người đó là lạc đề đối với trang web này. (Mặc dù IMO, bất cứ ai sẵn sàng dành chút thời gian để đóng góp ý tưởng đều đang làm điều gì đó đúng đắn, ngay cả khi ý tưởng đó không thành công)
Fund Monica's Kiện

2
Vâng, đó là câu trả lời sai. Nhưng tôi sợ một số người có thể đi đến cùng ý tưởng. Vì nhận xét và liên kết mà tôi vừa học được điều gì đó, đó không phải là điểm tiêu cực đối với tôi.
Robert Andrzejuk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.