Endianness 라는 개념이 있다.
보통 Big-endian과 Little-endian 두 가지가[각주:1] 있는데, 메모리에 여러 바이트 연속된 자료가 저장되는 순서를 말한다.
다른 표현으로는 바이트 오더(Byte Order)라고도 한다.
// Reverses the byte order in unsigned integer value.
// For example, 0x12345678 becomes 0x78563412.
__STATIC_FORCEINLINE uint32_t __REV(uint32_t value)
{
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
return __builtin_bswap32(value);
#else
uint32_t result;
__ASM volatile ("rev %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) );
return result;
#endif
}
// Reverses the byte order within each halfword of a word.
// For example, 0x12345678 becomes 0x34127856.
__STATIC_FORCEINLINE uint32_t __REV16(uint32_t value)
{
uint32_t result;
__ASM volatile ("rev16 %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) );
return result;
}
// Reverses the byte order in a 16-bit value and returns the signed 16-bit result.
// For example, 0x0080 becomes 0x8000.
__STATIC_FORCEINLINE int16_t __REVSH(int16_t value)
{
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
return (int16_t)__builtin_bswap16(value);
#else
int16_t result;
__ASM volatile ("revsh %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) );
return result;
#endif
}
회사에서 Cortex-M4[각주:2]로 장비를 만드는데, 이 환경에는 위와 같이 바이트 오더를 바꾸는 CPU 함수들이 있다.
이에 따라 프로토콜에서 바이트 오더가 바뀌어야 하면 적절한 함수만 호출하면 된다.
Big-endian과 Little-endian 외에 Middle-endian도 지원할 수 있도록 내장(intrinsic) 함수들을 만들어뒀다.
x86/x64 환경에서는 이런 CPU 명령이 있을리가 없다고 믿어 직접 만들기로 했다.
constexpr uint16_t __REVSH(uint16_t n) noexcept {
return ((n >> 8) | (n << 8));
}
그런데, 좀 예쁘지(?) 않아서 좀 바꿔보기로 했다.
이를 위한 매크로도 충실하게 준비되어 있다.
constexpr uint16_t __REVSH(uint16_t n) noexcept {
return MAKEWORD(LOBYTE(n), HIBYTE(n));
}
그리고는 정상적으로 동작하지 않았고, 망했다[...]
위 함수는 인자의 순서를 잘못 기술해서, 뒤집어지지 않고, 입력값을 그대로 출력해준다...
아래와 같이 작성해야 맞다.
constexpr uint16_t __REVSH(uint16_t n) noexcept {
return MAKEWORD(HIBYTE(n), LOBYTE(n));
}
여기에 시간을 허비하고나서 다시 생각하니, 근본적으로 접근 방법이 틀린 것이다.
이런 함수는 내가 만들 필요가 없이 만들어져 있는 것을 활용하면 된다.
일단 치사하게는(?) 아래와 같이 하는 방법도 있다.
#include <WinSock2.h>
uint16_t __REVSH(uint16_t n) {
return htons(n);
}
그런데, 이건 바람직한 방법이 아니다.
소켓을 사용하지 않는 경우도 있을 수가 있고, 근본적으로 htons()의 목적이 이게 아니기 때문이다.
이 함수의 목적은 Little/Endian 데이터를 무조건 Big-endian으로 바꾸는 것이라 미묘하게 다르다.
게다가, 성능도 느린 편이다.
이 정도 고민했을 때 뭔가 쌔한 느낌이 들었다.
아니, 설마 SIMD가 판치는 요즘 x86/x64 CPU에서 이런 기능이 없다고?
찾아보니 당연히 있었다[...]
80486 이전 CPU에서는 없던 명령이라는 친절한 설명도 있었고[...]
그리고, 역시 당연히 이를 위한 intrinsic 함수도 만들어져 있었다.
선언은 intrinc.h 및 stdlib.h에 되어있고...
unsigned short _byteswap_ushort(unsigned short value);
unsigned long _byteswap_ulong(unsigned long value);
unsigned __int64 _byteswap_uint64(unsigned __int64 value);
결론적으로, Cortex-M4 환경과 동일한 함수를 사용하려면 아래와 같이 선언하면 된다.
uint16_t __REVSH(uint16_t n) {
return _byteswap_ushort(n);
}
uint32_t __REV(uint32_t n) {
return _byteswap_ulong(n);
}
uint32_t __REV16(uint32_t n) {
return (((n >> 8) & 0x00ff00ff) | ((n << 8) & 0xff00ff00));
}
e의 연분수 전개시 특정 수렴값의 분자의 합 (0) | 2020.11.08 |
---|---|
제곱근의 연분수 전개시 반복되는 숫자가 홀수 개인 경우는? (0) | 2020.11.08 |
n 제곱의 자릿수가 n인 경우의 개수는? (0) | 2020.03.28 |
정공법으로 소수 판별 시 더 빠른 방법은? (0) | 2020.03.28 |
대각선에서 소수의 비율이 10% 이하가 되는 값 찾기 (0) | 2020.03.26 |