Programing

C ++ 클래스 메모리 구조에서 "스페이서"를 어떻게 만듭니 까?

lottogame 2020. 8. 30. 19:39
반응형

C ++ 클래스 메모리 구조에서 "스페이서"를 어떻게 만듭니 까?


문제

A의 낮은 수준의 베어 메탈 (bare-metal) 임베디드 환경, 나는 액세스 메모리 위치에 사용자를 금지하기 위해, C ++ 구조 내에서 어떤 이름없이, 메모리에 빈 공간을 만들고 싶습니다.

지금 uint32_t :96;은 세 단어를 편리하게 대신 할 추악한 비트 필드를 배치하여 달성 했지만 GCC (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 다른 것 위에 약 100 줄을 추가하지 않을 것입니다.

정확한 경고 메시지는 다음과 같습니다. 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이 경고를 제거 플래그 가 없음을 알려줍니다 .


인접한 여러 개의 익명 비트 필드를 사용하십시오. 그래서 대신 :

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

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네임 스페이스로 인해 자동 완성이 이루어 지며 더미 패딩이 필요하지 않습니다. 심지어 각 레지스터의 주소를 볼 수 있으므로 무슨 일이 일어나고 있는지 더 명확합니다. 컴파일러의 패딩 동작에 전혀 의존 할 필요가 없습니다.


임베디드 시스템 영역에서는 구조를 사용하거나 레지스터 주소에 대한 포인터를 정의하여 하드웨어를 모델링 할 수 있습니다.

컴파일러는 정렬 목적으로 멤버 사이에 패딩을 추가 할 수 있으므로 구조 별 모델링은 권장되지 않습니다 (임베디드 시스템의 많은 컴파일러에는 구조 패킹을위한 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는 구조를 사용함).


당신이 정말로 이것을 위해 클래스를 사용하고 싶지 않다는 것이 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");

( = 7 바이트를 보여주는 Godbolt 컴파일러 탐색기의offsetof(GPIO, b))


@Clifford 및 @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의 답변을 확장하려면 항상 익명의 비트 필드를 매크로 아웃 할 수 있습니다.

그래서 대신

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당신이 가지고있는 바이트 만큼 많은 변형 이 필요할 것입니다 :( 그래도 당신의 구조체에 단일 선언을 가질 수 있습니다.


큰 스페이서를 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)
};

좀 더 구조를 도입하는 것이 유익하다고 생각합니다. 이는 차례로 스페이서 문제를 해결할 수 있습니다.

변형 이름

While flat namespaces are nice, the issue is that you end up with a motley collection of fields and no simple way of passing all related fields together. Furthermore, by using anonymous structs in an anonymous union you cannot pass references to the structs themselves, or use them as template parameters.

As a first step, I would, therefore, consider breaking out the 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 ...

And finally, the global header:

// Gpio.h
#pragma once

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

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

Now, I can write a void special_map0(Gpio:: Map0 volatile& map);, as well as get a quick overview of all available architectures at a glance.

Simple Spacers

With the definition split in multiple headers, the headers are individually much more manageable.

Therefore, my initial approach to exactly meet your requirements would be to stick with repeated std::uint32_t:32;. Yes, it adds a few 100s lines to the existing 8k lines, but since each header is individually smaller, it may not be as bad.

If you are willing to consider more exotic solutions, though...

Introducing $.

It is a little-known fact that $ is a viable character for C++ identifiers; it's even a viable starting character (unlike digits).

A $ appearing in the source code would likely raise eyebrows, and $$$$ is definitely going to attract attention during code reviews. This is something that you can easily take advantage of:

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

You can even put together a simple "lint" as a pre-commit hook or in your CI which looks for $$$$ in the committed C++ code and reject such commits.


Although I agree structs should not be used for MCU I/O port access, original question can be answered this way:

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

You may need to replace __attribute__((packed)) with #pragma pack or similar depending on your compiler syntax.

Mixing private and public members in a struct normally results in that memory layout is no longer guaranteed by C++ standard. However if all non-static members of a struct are private, it is still considered POD / standard layout, and so are structs that embed them.

For some reason gcc produces a warning if a member of an anonymous struct is private so I had to give it a name. Alternatively, wrapping it into yet another anonymous struct also gets rid of the warning (this may be a bug).

Note that spacer member is not itself private, so data can still be accessed this way:

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

However such an expression looks like an obvious hack, and hopefully would not be used without a really good reason, let alone as a mistake.


Anti-solution.

DO NOT DO THIS: Mix private and public fields.

Maybe a macro with a counter to generate uniqie variable names will be useful?

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

참고URL : https://stackoverflow.com/questions/53109888/how-do-i-create-a-spacer-in-a-c-class-memory-structure

반응형