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이 왜 빼는지 궁금합니다.0x10000u
uint32_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;
}
로 됩니다.movbe
clang
,gcc
버전이 덜 최적입니다. 어셈블리를 참조하십시오.
답변해주신 모든 분들께 감사드립니다.다음은 집합적인 작업을 요약하는 내용입니다.
- C 표준 7.20.1.1 정확한 너비 정수형: 유형
uint8_t
,int16_t
그리고.uint16_t
는 덧셈 비트 없이 2의 보 표현을 사용해야 하므로 표현의 실제 비트는 함수 이름으로 지정된 순서대로 배열에 있는 2바이트의 것임이 분명합니다. - 없는 을 16 합니다.
(unsigned)data[0] | ((unsigned)data[1] << 8)
(little endian 버전의 경우) 는 단일 명령어로 컴파일되며 부호 없는 16비트 값을 산출합니다. - C 표준 6.3.1.3 부호 및 비부호 정수: 유형 값 변환
uint16_t
int16_t
값이 대상 유형의 범위에 있지 않으면 구현을 정의한 동작을 가집니다.표현이 정확하게 정의된 유형에 대해서는 특별한 규정이 없습니다. - 이 , 할 수
INT_MAX
를 빼서 합니다.0x10000
. zwol이 제시하는 모든 값에 대해 이를 수행하면 다음의 범위를 벗어난 값이 생성될 수 있습니다.int16_t
동일한 구현이 정의된 동작을 사용할 수 있습니다. 0x8000
비트는 컴파일러가 비효율적인 코드를 생성하도록 명시적으로 유발합니다.- 구현이 정의된 행동이 없는 보다 효율적인 전환은 연합을 통한 유형 처벌을 사용하지만, 이 접근 방식의 정의에 관한 논쟁은 C 표준의 위원회 수준에서도 여전히 열려 있습니다.
- 유형 펀닝은 다음을 사용하여 정의된 동작과 함께 휴대용으로 수행될 수 있습니다.
memcpy
.
포인트 2와 7을 결합하면 gcc와 clang을 모두 사용하여 단일 명령어에 효율적으로 컴파일할 수 있는 휴대용 및 완전한 정의 솔루션이 제공됩니다.
#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;
}
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_t
uint16_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
'source' 카테고리의 다른 글
메모장++를 사용하여 XSD에 대한 XML 유효성 검사 (0) | 2023.10.14 |
---|---|
C#로 셀 배경 변경 (0) | 2023.10.14 |
jQuery로 DOM 요소를 파괴하는 방법? (0) | 2023.10.14 |
문자열에서 XML을 읽고 몇 가지 필드 가져오기 - XML을 읽는 문제 (0) | 2023.10.14 |
python 스크립트가 실행 중인지 확인합니다. (0) | 2023.10.14 |