2016-11-07 2 views
13

이 작업은 Arduino에서 I2C로 STM32로 데이터를 전송하는 것입니다.C와 C++에서 열거 형을 가진 구조체가 다른 이유는 무엇입니까?

내가 같은 가지고 다른 측면에서
enum PhaseCommands { 
    PHASE_COMMAND_TIMESYNC = 0x01, 
    PHASE_COMMAND_SETPOWER = 0x02, 
    PHASE_COMMAND_CALIBRATE = 0x03 
}; 

enum PhaseTargets { 
    PHASE_CONTROLLER = 0x01, 
    // RESERVED = 0x02, 
    PHASE_LOAD1 = 0x03, 
    PHASE_LOAD2 = 0x04 
}; 

struct saatProtoExec { 
    PhaseTargets target; 
    PhaseCommands commandName; 
    uint32_t  commandBody; 
} phaseCommand; 

uint8_t phaseCommandBufferSize = sizeof(phaseCommand); 

phaseCommand.target = PHASE_LOAD1; 
phaseCommand.commandName = PHASE_COMMAND_SETPOWER; 
phaseCommand.commandBody = (uint32_t)50; 

는 C를 사용하여 정의 :

그래서 나는 구조체와 열거 형은 C++을 사용하여 아두 이노에 정의있어

typedef enum { 
    COMMAND_TIMESYNC = 0x01, 
    COMMAND_SETPOWER = 0x02, 
    COMMAND_CALIBRATE = 0x03 
} MasterCommands; 

typedef enum { 
    CONTROLLER = 0x01, 
    // RESERVED = 0x02, 
    LOAD1 = 0x03, 
    LOAD2 = 0x04 
} Targets; 

struct saatProtoExec { 
    Targets   target; 
    MasterCommands commandName; 
    uint32_t  commandBody; 
} execCommand; 

uint8_t execBufferSize = sizeof(execCommand); 

execCommand.target = LOAD1; 
execCommand.commandName = COMMAND_SETPOWER; 
execCommand.commandBody = 50; 

을 그리고 난이 구조체 비교 바이트 - 바이트 단위 :

===================== 
BYTE | C++ | C 
===================== 
Byte 0 -> 0x3 -> 0x3 
Byte 1 -> 0x0 -> 0x2 
Byte 2 -> 0x2 -> 0x0 
Byte 3 -> 0x0 -> 0x0 
Byte 4 -> 0x32 -> 0x32 
Byte 5 -> 0x0 -> 0x0 
Byte 6 -> 0x0 -> 0x0 
Byte 7 -> 0x0 -> 0x0 

왜 바이트 1과 2가 다른가요?

+3

나는 열거 크기 + 패딩을 말하고 싶지만 ... –

+4

동일한 컴파일러가 같은 플래그로 사용됩니까? 두 표준 모두 열거 형이라면 크기에 대해서는 아무 것도 말하지 않으므로 서로 다른 것을 허용합니다. 여전히 한 컴파일러에서 동일해야합니다. –

+0

@NorbertLange 아니요, 컴파일러가 다릅니다. 하나는 Arduino 네이티브이고, 두 번째는 KEK MDK-ARM입니다. – Bulkin

답변

24

이것은 정말 좋은 생각입니다.

C에서 C++로가는 것은 말할 것도없고, C의 두 구현 사이에서 동일한 구조의 이진 표현에 의존해서는 안됩니다!

구조의 외부 표현의 바이트 수준에서 제어하려면 적절한 serialization/deserialization 코드를 사용해야합니다.

즉, 패딩 때문일 수 있습니다. 외부 링크를 통해 패딩 (이는 호스트 CPU를 행복하게 유지하기 위해 컴파일러가 추가 한 것)을 보내는 것으로 끝나는 것은이 접근 방식이 얼마나 깨어진 것의 또 다른 신호입니다.

+0

네, 맞습니다. 나는 그렇게 생각했다. 그러나 그것에 관해 적당한 의견을 얻고 싶다. – Bulkin

9

C 버전에서는 분명히 sizeof(Targets) == 1입니다. 그리고 구조체의 두 번째 필드가 2 바이트 정렬 된 것처럼 보이므로 정의되지 않은 내용이있는 패딩 바이트가 있습니다.

이제 C++에서 sizeof(PhaseTargets)1 또는 2 일 수 있습니다. 1 (가능성이 높음)이 모두 양호하고 동일한 패딩 공간이있는 경우 다른 쓰레기 값이 발생했습니다. 2 ... 글쎄, 잘못된 enum 값을 갖게됩니다!

구조체를 초기화하는 쉬운 방법은 변수 정의에 있습니다. 아직 값이 없다면 0을 추가하면 모든 struct가 0으로 초기화됩니다. 그 완료 할 수없는 경우

struct saatProtoExec execCommand = {0}; 

, 당신은 사용하기 전에 0으로 memset() 수 있습니다.

휴대용 대안 구조체의 필드를 적절한 크기의 정수로 선언하고 enum 형식을 상수 컬렉션처럼 사용할 수 있습니다.

struct saatProtoExec { 
    uint8_t   target; 
    uint8_t   commandName; 
    uint8_t   padding[2]; 
    uint32_t  commandBody; 
} execCommand; 
+0

음, 좋은 생각입니다. 다른 방법으로 사용하겠습니다. 하지만 패딩없이 데이터를 보내고받는 사람에게 구문 분석하는 것이 더 나을 것 같습니다. – Bulkin

+2

이 대안은 같은 endianess와 32 비트 aligment 이하의 아키텍처에서만 이식 가능합니다 (이 특정 질문의 경우 인 것 같습니다). 그러나 [unwind 's answer] (http://stackoverflow.com/a/40465574/3951057)에서 뾰족하게 만들어진 직렬화를 사용하면 실제로는 이식성이 뛰어나고 대역폭 효율성도 향상 될 수 있습니다. –

+1

그 구조체는 이식성이있을 수있는 싸울 기회를 제공하지만 보장 할 수는 없습니다. 4 개의 64 비트 단어로 쉽게 구현 될 수 있습니다. – gnasher729

2

이미 말했듯이 특정 형식의 구조에 의존해서는 안됩니다. 그러나 변환이 필요없고 표현이 주어진 아키텍처에서 런타임에 호환 가능하고 효율적이면 더 효율적일 수 있으므로 직렬화 대신 구조를 사용하면 편리합니다. 구조를 사용하는 경우

따라서, 여기에 몇 가지 조언은 다음과 같습니다

  • 적절한 프라그 마를 사용하거나 레이아웃이 프로젝트 옵션에 의존하지 않도록 속성.
  • 최종 크기가 예상 한 크기인지 확인하는 검사를 추가하십시오.C++에서는 static_assert을 사용할 수 있습니다.
  • 또한 endianness가 예상 됨으로 확인하십시오.
  • 형식을 확인하기위한 단위 테스트가 있으면 좋은 생각입니다.

어떤 경우이든 일반 코드 및 직렬화에 동일한 struct을 사용하지 않는 것이 좋습니다. 때로는 일련 화되지 않았거나 조정이 필요한 추가 구성원이있는 것이 유용합니다.

또한 중요한 점은 추가 필드를 추가하고 이전 데이터를 변환해야하거나 일부 버그를 발견하여 데이터를 수정해야 할 수도 있다는 사실을 계획하는 것입니다.

이전 소프트웨어에서 새 파일을 열면 어떻게 될지 고려해야 할 수도 있습니다. 많은 경우에, 아마 거절 될 것입니다.

+0

음, 그렇습니다. 지금은 구조체를 전송할 아이디어를 남기고 있습니다. 수신자가 예상하는대로 4 바이트의 두 부분으로 데이터를 전송합니다. 이것은 나에게 같은 플랫폼과 컴파일러로 만들어진 Struct을 보낼 수 있도록 나에게 보낸 사람과 같은 STM32를 사용하는 목표를 제공합니다. – Bulkin