source

2바이트를 부호 있는 16비트 정수로 변환하는 올바른 방법은 무엇입니까?

ittop 2023. 10. 14. 10:37
반응형

2바이트를 부호 있는 16비트 정수로 변환하는 올바른 방법은 무엇입니까?

답변에서 즈월은 다음과 같이 주장했습니다.

외부 소스에서 2바이트의 데이터를 16비트 부호 정수로 변환하는 올바른 방법은 다음과 같은 도우미 기능을 사용하는 것입니다.

#include <stdint.h>

int16_t be16_to_cpu_signed(const uint8_t data[static 2]) {
    uint32_t val = (((uint32_t)data[0]) << 8) | 
                   (((uint32_t)data[1]) << 0);
    return ((int32_t) val) - 0x10000u;
}

int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
    uint32_t val = (((uint32_t)data[0]) << 0) | 
                   (((uint32_t)data[1]) << 8);
    return ((int32_t) val) - 0x10000u;
}

위의 함수 중 어느 것이 적절한지는 배열에 약간의 엔디언 표현이 포함되어 있는지 또는 큰 엔디언 표현이 포함되어 있는지에 따라 달라집니다.엔디안니스는 여기서 문제가 되지 않습니다. zwol이 왜 빼는지 궁금합니다.0x10000uuint32_t한 값int32_t.

왜 이것이 올바른 방법입니까?

반환 유형으로 변환할 때 구현이 정의된 동작을 어떻게 방지합니까?

할 수 이한 캐스팅이 합니까? 2 의합니까? 의합니까?return (uint16_t)val;

이 순진한 해결책의 문제점은 무엇입니까?

int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
    return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}

한다면int은 16합니다 합니다. 만약에 있는 식의 값이return다의 .int16_t.

첫 도 와 . 예를 들어 만약int32_t에 대한 유형의 디프입니다.int둘 다 , 입니다 입니다.0xFF는 , UINT_MAX될 때 정의 동작을 합니다.int16_t.

당신이 링크하는 답변은 몇가지 주요한 문제가 있습니다.

이는 일반적인 2의 보어 대신 부호 비트 또는 1의 보어 표현을 사용하는 플랫폼에서도 사용할 수 있습니다.입력 바이트는 2의 보어에 있는 것으로 가정됩니다.

int le16_to_cpu_signed(const uint8_t data[static 2]) {
    unsigned value = data[0] | ((unsigned)data[1] << 8);
    if (value & 0x8000)
        return -(int)(~value) - 1;
    else
        return value;
}

지점 때문에 다른 옵션보다 가격이 비쌀 것 같습니다.

입니다에 대한 어떠한 수 .int다와 이 있습니다.unsigned연단에 표현할 수 있습니다.o.int대상 유형에 맞는 모든 숫자에 대해 산술 값을 보존하는 데 필요합니다.반전은 16비트 숫자의 상위 비트가 0이 되도록 보장하기 때문에 값이 적합합니다.에 단 하나의n.-그리고 1의 뺄셈은 2의 보어 부정에 대한 일반적인 규칙을 적용합니다.라에 INT16_MIN만약 그것이 그 안에 들어가지 않는다면, 여전히 넘칠 수 있습니다.int 이 합니다.elong사용해야 합니다.

질문의 원래 버전과 다른 점은 돌아오는 시간에 나타납니다. 뺄 수 있는 에.0x10000고 2다로 .int16_t, 이 입니다가 있습니다.if서명된 덮어쓰기(정의되지 않음)를 방지합니다.

현재 실제로 사용되는 거의 모든 플랫폼은 2의 보완 표현을 사용합니다.에 표준 t가 stdint.h는을 입니다.int32_t, 2의 보어를 사용해야 합니다.이 접근 방식이 도움이 되는 것은 정수 데이터 유형이 전혀 없는 일부 스크립트 언어의 경우입니다. 위에 표시된 플로트 작업을 수정하면 올바른 결과를 얻을 수 있습니다.

다른 - -union:

union B2I16
{
   int16_t i;
   byte    b[2];
};

프로그램 중:

...
B2I16 conv;

conv.b[0] = first_byte;
conv.b[1] = second_byte;
int16_t result = conv.i;

first_byte그리고.second_byte 또는 될 수 있습니다. little는 big endian다이 방법은 더 나은 방법이 아니라 대안 중 하나입니다.

산술 연산자가 이동하고 bitwise-또는 식에서(uint16_t)data[0] | ((uint16_t)data[1] << 8)다보다 int이, ㅇuint16_t다로 됩니다.int(또는unsigned한다면sizeof(uint16_t) == sizeof(int)입니다.) 그러나 하위 2바이트만 값을 포함하므로 정확한 답을 얻을 수 있습니다.

빅 엔디언에서 리틀 엔디언으로 변환하기 위한 또 다른 페달적으로 정확한 버전은 다음과 같습니다.

#include <string.h>
#include <stdint.h>

int16_t be16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
    memcpy(&r, data, sizeof r);
    return __builtin_bswap16(r);
}

memcpy됩니다의 하는 데 됩니다.int16_t그것이 표준에 부합하는 방법입니다.이 버전은 또한 하나의 명령어로 컴파일됩니다. 어셈블리를 참조하십시오.

및 잘 () )에만하는 또 .#include <endian.h>표준이 아닙니다. 코드는 ):

#include <endian.h>
#include <stdint.h>
#include <string.h>

static inline void swap(uint8_t* a, uint8_t* b) {
    uint8_t t = *a;
    *a = *b;
    *b = t;
}
static inline void reverse(uint8_t* data, int data_len) {
    for(int i = 0, j = data_len / 2; i < j; ++i)
        swap(data + i, data + data_len - 1 - i);
}

int16_t be16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
#if __BYTE_ORDER == __LITTLE_ENDIAN
    uint8_t data2[sizeof r];
    memcpy(data2, data, sizeof data2);
    reverse(data2, sizeof data2);
    memcpy(&r, data2, sizeof r);
#else
    memcpy(&r, data, sizeof r);
#endif
    return r;
}

로 됩니다.movbeclang,gcc버전이 덜 최적입니다. 어셈블리를 참조하십시오.

답변해주신 모든 분들께 감사드립니다.다음은 집합적인 작업을 요약하는 내용입니다.

  1. C 표준 7.20.1.1 정확한 너비 정수형: 유형uint8_t,int16_t그리고.uint16_t는 덧셈 비트 없이 2의 보 표현을 사용해야 하므로 표현의 실제 비트는 함수 이름으로 지정된 순서대로 배열에 있는 2바이트의 것임이 분명합니다.
  2. 없는 을 16 합니다.(unsigned)data[0] | ((unsigned)data[1] << 8)(little endian 버전의 경우) 는 단일 명령어로 컴파일되며 부호 없는 16비트 값을 산출합니다.
  3. C 표준 6.3.1.3 부호비부호 정수: 유형 값 변환uint16_tint16_t값이 대상 유형의 범위에 있지 않으면 구현을 정의한 동작을 가집니다.표현이 정확하게 정의된 유형에 대해서는 특별한 규정이 없습니다.
  4. 이 , 할 수 INT_MAX를 빼서 합니다.0x10000. zwol이 제시하는 모든 값에 대해 이를 수행하면 다음의 범위를 벗어난 값이 생성될 수 있습니다.int16_t동일한 구현이 정의된 동작을 사용할 수 있습니다.
  5. 0x8000비트는 컴파일러가 비효율적인 코드를 생성하도록 명시적으로 유발합니다.
  6. 구현이 정의된 행동이 없는 보다 효율적인 전환은 연합을 통한 유형 처벌을 사용하지만, 이 접근 방식의 정의에 관한 논쟁은 C 표준의 위원회 수준에서도 여전히 열려 있습니다.
  7. 유형 펀닝은 다음을 사용하여 정의된 동작과 함께 휴대용으로 수행될 수 있습니다.memcpy.

포인트 2와 7을 결합하면 gccclang을 모두 사용하여 단일 명령어에 효율적으로 컴파일할 수 있는 휴대용 및 완전한 정의 솔루션이 제공됩니다.

#include <stdint.h>
#include <string.h>

int16_t be16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
    uint16_t u = (unsigned)data[1] | ((unsigned)data[0] << 8);
    memcpy(&r, &u, sizeof r);
    return r;
}

int16_t le16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
    uint16_t u = (unsigned)data[0] | ((unsigned)data[1] << 8);
    memcpy(&r, &u, sizeof r);
    return r;
}

64비트 어셈블리:

be16_to_cpu_signed(unsigned char const*):
        movbe   ax, WORD PTR [rdi]
        ret
le16_to_cpu_signed(unsigned char const*):
        movzx   eax, WORD PTR [rdi]
        ret

"를 "" 에 하는 것은 요?int16_tuint16_t?

int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
    return (int16_t)data[0] | ((int16_t)data[1] << 8);
}

그러면 서명되지 않은 인트를 서명된 인트에 캐스팅하는 문제(그리고 서명된 인트 범위를 벗어날 수도 있음)를 처리할 필요가 없습니다.

언급URL : https://stackoverflow.com/questions/60864450/what-is-the-correct-way-to-convert-2-bytes-to-a-signed-16-bit-integer

반응형