C/C++에서 부호 없는 좌회전 전 마스킹이 너무 편집증적인가요?
이 질문은 C/C++에서 암호화 알고리즘(예를 들어 SHA-1)을 구현하고 휴대용 플랫폼 진단 코드를 작성하여 정의되지 않은 동작을 철저히 피함으로써 발생합니다.
표준화된 암호 알고리즘에 의해 다음과 같은 구현이 요구된다고 가정합니다.
b = (a << 31) & 0xFFFFFFFF
어디에a
그리고.b
부호 없는 32비트 정수입니다.그 결과 최하위 32비트 이상의 비트는 모두 폐기됩니다.
첫 번째 순진한 근사치로서, 우리는 다음과 같이 가정할 수 있다.int
는 대부분의 플랫폼에서 32비트 폭이기 때문에 다음과 같이 기술합니다.
unsigned int a = (...);
unsigned int b = a << 31;
이 코드가 모든 곳에서 작동하지는 않을 겁니다int
일부 시스템에서는 16비트, 다른 시스템에서는 64비트이며, 36비트일 수도 있습니다.단, 을 사용하여stdint.h
, 이 코드를 개선하려면 ,uint32_t
입력:
uint32_t a = (...);
uint32_t b = a << 31;
이제 다 끝난 거죠?그게 내가 몇 년 동안 생각한 거야.별로 그렇지 않아요.특정 플랫폼에서 다음이 있다고 가정합니다.
// stdint.h
typedef unsigned short uint32_t;
C/C++에서 산술 연산을 실행하는 규칙은 유형이 다음과 같은 경우(예:short
)는 보다 좁습니다.int
, 그리고 그것은 넓어집니다.int
모든 값이 적합할 경우 또는unsigned int
그렇지않으면.
컴파일러가 다음을 정의한다고 합시다.short
32비트(서명 첨부) 및int
48비트(서명)로 지정합니다.다음으로 다음 코드 행이 표시됩니다.
uint32_t a = (...);
uint32_t b = a << 31;
의 의미는 다음과 같습니다.
unsigned short a = (...);
unsigned short b = (unsigned short)((int)a << 31);
주의:a
로 승진하다int
모든 것이ushort
(즉,uint32
)는 에 적합합니다.int
(즉,int48
).
그러나 문제가 있습니다. 제로 이외의 비트를 부호화된 정수형 부호 비트로 전환하는 것은 정의되지 않은 동작입니다.이 문제가 발생한 이유는uint32
로 승진했다.int48
- 승진 대신uint48
(좌향좌향좌향좌향후 좌회전도 괜찮습니다.
다음은 질문입니다.
내 추론이 맞는가, 그리고 이것이 이론적으로 정당한 문제인가?
모든 플랫폼에서 다음 정수형은 너비의 2배가 되기 때문에 이 문제는 무시해도 안전한가요?
입력을 다음과 같이 미리 마스킹하여 이러한 병리적인 상황을 올바르게 방어하는 것이 좋은 방법입니까?
b = (a & 1) << 31;
(이것은 반드시 모든 플랫폼에서 올바른 것입니다.단, 이로 인해 속도 크리티컬 암호 알고리즘이 필요 이상으로 느려질 수 있습니다.)
설명/편집:
C, C++, 또는 둘 다에 대한 답변을 받아들이겠습니다.나는 적어도 한 가지 언어에 대한 답을 알고 싶다.
프리마스크 로직은 비트 회전에 악영향을 줄 수 있습니다.예를 들어, GCC가 컴파일 합니다.
b = (a << 31) | (a >> 1);
어셈블리 언어의 32비트 비트 변환 명령으로 변환합니다.그러나 왼쪽 시프트를 미리 마스크하면 새로운 로직이 비트 회전으로 변환되지 않을 수 있습니다. 즉, 이제 1이 아닌 4개의 연산이 수행됩니다.
이 질문에서 힌트를 얻어서 UB가 다음 중 하나일 수 있다는 것을 알 수 있습니다.uint32 * uint32
산술적으로 다음과 같은 간단한 접근법이 C 및 C++에서 작동해야 합니다.
uint32_t a = (...);
uint32_t b = (uint32_t)((a + 0u) << 31);
정수 상수0u
타입이 있다unsigned int
이 경우 추가가 촉진됩니다.a + 0u
로.uint32_t
또는unsigned int
어느 쪽이든 폭이 넓습니다.왜냐하면 그 유형은 계급이 있기 때문이다.int
이상, 승격이 더 이상 발생하지 않으며 왼쪽 피연산자를 사용하여 시프트를 적용할 수 있습니다.uint32_t
또는unsigned int
.
마지막 캐스팅은uint32_t
는, 변환이 좁혀지는 것에 관한 잠재적인 경고를 억제합니다(예를 들면,int
64비트).
적절한 C 컴파일러는 제로 추가를 no-op으로 인식할 수 있어야 합니다.이것은 프리마스크가 부호 없는 시프트 후에 아무런 영향을 주지 않는 것보다 부담이 적습니다.
Q1: 시프트 전에 마스킹하면 OP에 문제가 있는 정의되지 않은 동작을 방지할 수 있습니다.
Q2: "...모든 플랫폼에서 다음 정수형은 너비의 2배이기 때문?" --> 아니요."next" 정수 유형은 2x 미만일 수도 있고 같은 크기일 수도 있습니다.
다음 내용은 모든 준거 C 컴파일러에 대해 적절하게 정의되어 있습니다.uint32_t
.
uint32_t a;
uint32_t b = (a & 1) << 31;
질문 3:uint32_t a; uint32_t b = (a & 1) << 31;
에서는 소스에서만 마스크를 실행하는 코드가 발생할 것으로 예상되지 않습니다(실행 파일에는 필요하지 않습니다).마스크가 발생했을 경우는, 보다 좋은 컴파일러를 입수하는 것이 문제가 됩니다.
제안하신 바와 같이, 이러한 교대조에서는 부호 없는 것을 강조하는 것이 좋습니다.
uint32_t b = (a & 1U) << 31;
@John Bollinger good answer는 OP의 특정 문제에 어떻게 대처해야 하는지 잘 알고 있다.
일반적인 문제는 어떻게 하면 적어도 다음과 같은 숫자를 형성할 수 있는가 하는 것이다.n
OP 딜레마의 핵심인 비트(bits), 특정 부호(sign-ness), 놀라운 정수 승진의 대상이 아닙니다.아래는 이 기능을 수행합니다.unsigned
값을 변경하지 않는 작업 - 유형 우려 이외의 no-op이 유효합니다.제품의 폭은 적어도unsigned
또는uint32_t
. 일반적으로 주조하면 유형이 좁아질 수 있습니다.협착이 발생하지 않는 한 주조를 피해야 합니다.최적화 컴파일러는 불필요한 코드를 생성하지 않습니다.
uint32_t a;
uint32_t b = (a + 0u) << 31;
uint32_t b = (a*1u) << 31;
문제의 C측에서 말하면,
- 내 추론이 맞는가, 그리고 이것이 이론적으로 정당한 문제인가?
이전에는 생각하지 않았던 문제이지만, 당신의 분석에 동의합니다.C는 의 동작을 정의합니다.<<
승격한 좌파 피연산자의 유형에 관한 연산자, 그리고 정수 승진이 결과적으로 (서명된) 것으로 생각할 수 있다.int
해당 오퍼랜드의 원래 유형이uint32_t
현대 기계에서 실제로 그런 것을 보게 되리라고는 기대하지 않지만, 저는 제 개인적인 기대와는 달리 실제 표준에 맞게 프로그래밍하는 것에 찬성합니다.
- 모든 플랫폼에서 다음 정수형은 너비의 2배가 되기 때문에 이 문제는 무시해도 안전한가요?
C는 실제로는 어디에나 존재하지만 정수형 사이의 관계를 요구하지 않는다.그러나 표준에만 의존하기로 결심한 경우(즉, 엄격하게 일치하는 코드를 작성하기 위해 노력하고 있는 경우)는 이러한 관계에 의존할 수 없습니다.
- 입력을 이렇게 미리 마스킹함으로써 이러한 병리적인 상황을 올바르게 방어하는 것이 좋은 생각일까요?: b = (a & 1) < < 31 ; (이것은 반드시 모든 플랫폼에서 올바릅니다.단, 이로 인해 속도 크리티컬 암호 알고리즘이 필요 이상으로 느려질 수 있습니다.)
종류unsigned long
는 32비트 이상의 값 비트를 가질 것을 보증하며 정수 프로모션에 따라 다른 유형으로 승격되지 않습니다.많은 일반적인 플랫폼에서는 다음과 같은 표현을 사용합니다.uint32_t
같은 타입일 수도 있습니다.그래서 저는 이런 표현을 쓰고 싶습니다.
uint32_t a = (...);
uint32_t b = (unsigned long) a << 31;
필요한 경우a
계산의 중간값으로서만b
, 그리고 나서, 그것을 선언합니다.unsigned long
우선은요.
원치 않는 프로모션을 피하기 위해 다음과 같이 typedef와 함께 큰 유형을 사용할 수 있습니다.
using my_uint_at_least32 = std::conditional_t<(sizeof(std::uint32_t) < sizeof(unsigned)),
unsigned,
std::uint32_t>;
이 코드 세그먼트의 경우:
uint32_t a = (...);
uint32_t b = a << 31;
촉진하다a
서명된 유형 대신 서명되지 않은 유형으로 다음을 사용합니다.
uint32_t b = a << 31u;
의 양쪽이<<
연산자는 부호 없는 타입이며, 6.3.1.8(C 표준 드래프트 n1570)의 다음 행이 적용됩니다.
그 이외의 경우, 양쪽 오퍼랜드에 부호 있는 정수 타입이 있는 경우 또는 부호 없는 정수 타입이 작은 오퍼랜드가 더 큰 랭크인 오퍼랜드 타입으로 변환됩니다.
당신이 설명하고 있는 문제는 당신이 사용하는 원인입니다.31
어느 것이signed int type
즉, 6.3.1.8의 다른 행은
그 이외의 경우 부호 있는 정수형을 가진 오퍼랜드유형이 부호 없는 정수형을 가진 오퍼랜드유형의 모든 값을 나타낼 수 있는 경우 부호 없는 정수형을 가진 오퍼랜드유형은 부호 있는 정수형을 가진 오퍼랜드유형으로 변환됩니다.
폭력a
(서명이 있는 활자로)
업데이트:
이 답은 6.3.1(2)(강조) 때문에 올바르지 않습니다.
...
int가 원래 유형의 모든 값을 나타낼 수 있는 경우(비트 필드의 경우 너비에 의해 제한됨), 값은 int로 변환됩니다.그렇지 않은 경우 부호 없는 int로 변환됩니다.이를 정수 프로모션이라고 합니다.58) 기타 모든 유형은 정수 프로모션에 의해 변경되지 않습니다.
및 각주 58(내 것을 포함):
58) 정수 승진은 일반적인 산술 변환의 일부로서 특정 인수 표현식, 단항 +, - 및 ~ 연산자의 피연산자 및 각각의 하위 절에서 지정된 시프트 연산자의 양쪽 피연산자에만 적용된다.
일반적인 산술 변환이 아닌 정수 승격만 수행되므로31u
보증은 되지 않는다a
로 바뀌다unsigned int
상기와 같이
언급URL : https://stackoverflow.com/questions/39964651/is-masking-before-unsigned-left-shift-in-c-c-too-paranoid
'programing' 카테고리의 다른 글
Perceptron 학습 알고리즘이 0으로 수렴되지 않음 (0) | 2022.08.15 |
---|---|
Vuex - '변환 핸들러 외부의 vuex 저장소 상태를 변환하지 않음' (0) | 2022.08.15 |
프로세스 내부에서 CPU 및 메모리 소비량을 확인하는 방법 (0) | 2022.08.15 |
반영 일반 get 필드 값 (0) | 2022.08.15 |
Linux에서 시간 측정 - 시간 vs 클럭 vs getrusage vs clock_gettime vs gettime vs timespec_get? (0) | 2022.08.15 |