C에서는 시프트 연산자(<, >)가 산술입니까 논리입니까?
C에서 시프트 연산자(<<
,>>
) 산술적 또는 논리적입니까?
왼쪽으로 이동하면 산술적 이동과 논리적 이동 사이에 차이가 없습니다.오른쪽으로 이동할 때 이동 유형은 이동되는 값의 유형에 따라 달라집니다.
(차이에 익숙하지 않은 독자의 배경으로서 "논리적인" 오른쪽 시프트는 모든 비트를 오른쪽으로 이동하고 맨 왼쪽 비트를 0으로 채웁니다. "산술적인" 시프트는 원래 값을 맨 왼쪽 비트에 남깁니다.음수를 취급할 때는 그 차이가 중요해집니다.)
부호 없는 값을 이동할 때 C의 >> 연산자는 논리적인 시프트가 됩니다.부호 있는 값을 이동할 때 >> 연산자는 산술적인 시프트입니다.
예를 들어, 32비트 머신을 상정해 보겠습니다.
signed int x1 = 5;
assert((x1 >> 1) == 2);
signed int x2 = -5;
assert((x2 >> 1) == -3);
unsigned int x3 = (unsigned int)-5;
assert((x3 >> 1) == 0x7FFFFFFD);
K&R 제2판에 따르면 서명된 값의 올바른 이동에 대한 결과는 구현에 따라 달라집니다.
위키피디아에 따르면 C/C++는 보통 서명된 값에 산술적 변화를 시행한다.
기본적으로 컴파일러를 테스트하거나 컴파일러에 의존하지 않는 것이 좋습니다.현재의 MS C++ 컴파일러의 VS2008 도움말에는 컴파일러가 산술적인 시프트를 실행한다고 나와 있습니다.
TL;DR
고려하다i
그리고.n
각각 시프트 연산자의 왼쪽과 오른쪽 피연산자가 된다.i
정수 승격 후,T
. 가정하다n
들어가다[0, sizeof(i) * CHAR_BIT)
- 정의되지 않은 경우 - 다음과 같은 경우가 있습니다.
| Direction | Type | Value (i) | Result |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | < 0 | Implementation-defined† |
| Left (<<) | unsigned | ≥ 0 | (i * 2ⁿ) % (T_MAX + 1) |
| Left | signed | ≥ 0 | (i * 2ⁿ) ‡ |
| Left | signed | < 0 | Undefined |
§ 대부분의 컴파일러는 이것을 산술 시프트로서 실장합니다.
값이 결과 유형 T를 오버플로하는 경우 정의되지 않음, 승격된 유형 i
시프트
첫째, 데이터 유형 크기를 걱정하지 않고 수학적 관점에서 논리적 및 산술적 전환의 차이입니다.논리적 이동은 항상 폐기된 비트를 0으로 채우고 산술 이동은 왼쪽 이동의 경우에만 0으로 채웁니다. 그러나 오른쪽 이동의 경우 MSB를 복사하여 피연산자의 기호를 보존합니다(음수 값에 대한 2의 보완 부호화 가정).
즉, 논리 시프트는 시프트된 피연산자를 단순한 비트 스트림으로 간주하여 결과 값의 부호에 신경 쓰지 않고 이동합니다.산술적 교대조에서는 이 숫자를 (서명된) 숫자로 보고 교대조 수행 시 부호를 보존합니다.
숫자 X에 n을 곱하는 것은 X에n 2를 곱하는 것과 같기 때문에 논리적인 왼쪽 시프트도 마찬가지입니다.논리적인 시프트도 MSB가 끝부분에서 떨어져 보존할 것이 없기 때문입니다.
X가 음이 아닌 경우에만 숫자 X를 2로n 나눈 것과 같은 오른쪽 산술 이동입니다!정수 나눗셈은 수학 나눗셈에 불과하며 0(트렁크)을 향해 반올림합니다.
2의 보완 부호화로 나타나는 음수의 경우, 오른쪽으로 n비트로 이동하면 수학적으로 2로 나누고n -θ(바닥)로 반올림하는 효과가 있습니다. 따라서 오른쪽 이동은 음수가 아닌 값과 음수 값이 다릅니다.
X 0 0, X >> n = X / 2n = trunk(X 2n 2)의 경우
X < 0, X >> n = 플로어(X 2n 2)의 경우
어디에÷
수학적 나눗셈입니다./
정수 나눗셈입니다.예를 들어 보겠습니다.
37)10 = 100101)2
37 ÷ 2 = 18.5
37 / 2 = 18 (0 방향으로 18.5 증가) = 10010)2 [산술 우측 이동 결과]
-37)10 = 11011011)2 (2의 보완, 8비트 표현을 고려)
-37 ÷ 2 = -18.5
-37 / 2 = -18 (0 방향으로 18.5 이상) = 11101110)2 [산술적 우측 이동의 결과가 아님]
-37 >> 1 = -19 ( -18.5 ) -19 ) = 11101101)2 [산술 우측 이동 결과]
Guy Steel이 지적한 바와 같이 이 불일치로 인해 여러 컴파일러에 오류가 발생하고 있습니다.여기서 비음수(수학)는 부호 없는 비음수 값(C)에 매핑할 수 있습니다.둘 다 동일하게 취급되며, 오른쪽 시프트는 정수 나눗셈에 의해 이루어집니다.
따라서 논리적인 것과 산술적인 것은 왼쪽 시프트와 오른쪽 시프트에서 음이 아닌 값의 경우 등가입니다.그것들이 다른 것은 음이 아닌 값의 오른쪽 시프트입니다.
오퍼랜드 및 결과 유형
표준 C99 6 6.5.7:
각 피연산자는 정수형을 가져야 한다.
정수 프로모션은 각 오퍼랜드에서 실행됩니다.결과의 유형은 승격된 왼쪽 피연산자의 유형입니다.오른쪽 피연산자의 값이 음수이거나 승격된 왼쪽 피연산자의 폭 이상일 경우 동작은 정의되지 않습니다.
short E1 = 1, E2 = 3;
int R = E1 << E2;
위의 스니펫에서는 양쪽 오퍼랜드가int
(정수 승격을 위해)E2
음성이었거나E2 ≥ sizeof(int) * CHAR_BIT
조작은 정의되지 않습니다.이는 사용 가능한 비트보다 더 많이 이동하면 반드시 오버플로가 발생하기 때문입니다.가졌다R
로서 선언되었다short
,그int
시프트 연산의 결과는 암묵적으로 로 변환됩니다.short
; 변환 범위를 좁혀, 값이 행선지 타입으로 나타낼 수 없는 경우, 실장 정의의 동작이 발생할 가능성이 있습니다.
좌측 시프트
E1 < E2의 결과는 E1의 좌측 시프트 E2 비트 위치입니다.빈 비트는 0으로 채워집니다.E1이 부호 없는 타입일 경우 결과값은 E1×2로E2 결과 타입에서 나타낼 수 있는 최대값보다 1개 더 감소한다.E1이 부호 있는 타입과 음이 아닌 값을 가지며, E1E2×2가 결과 타입으로 표현 가능한 경우, 그것이 결과 값입니다.그렇지 않으면 동작은 정의되지 않습니다.
왼쪽 시프트는 양쪽에서 동일하기 때문에 빈 비트는 0으로 채워집니다.그런 다음 부호 없는 유형과 부호 있는 유형 모두에 대해 산술적 교대라고 명시합니다.논리 시프트는 비트로 표현되는 값을 신경 쓰지 않고 비트의 스트림으로 간주되지만 표준에서는 비트로 표현되는 것이 아니라 E1과E2 2의 곱으로 얻을 수 있는 값으로 정의하여 해석합니다.
여기서 주의할 점은 부호 있는 유형의 경우 값은 음이 아니어야 하며 결과 값은 결과 유형으로 나타낼 수 있어야 한다는 것입니다. 그렇지 않으면 작업이 정의되지 않습니다.결과 유형은 대상(결과를 보유하는 변수) 유형이 아니라 통합 프로모션을 적용한 후의 E1 유형입니다.결과값은 암묵적으로 행선지 타입으로 변환됩니다.그 타입으로 나타낼 수 없는 경우는, 변환이 실장 정의됩니다(C99 「6.3.1.3/3」).
E1이 음의 값을 갖는 부호 있는 유형인 경우, 좌측 시프트의 동작은 정의되지 않습니다.이는 쉽게 간과될 수 있는 정의되지 않은 행동을 위한 쉬운 경로입니다.
오른쪽 시프트
E1 > > E2의 결과는 E1의 오른쪽 시프트E2 비트 위치입니다E1이 부호 없는 타입을 가지거나 E1이 부호 있는 타입과 음이 아닌 값을 가지면 결과의 값은 E1/2의E2 몫의 적분 부분이 된다.E1에 부호 있는 타입과 음의 값이 있는 경우, 결과치는 실장 정의됩니다.
부호 없는 값과 부호 없는 값의 오른쪽 시프트는 매우 직선적입니다.빈 비트는 0으로 채워집니다.서명된 음수 값의 경우 우측 이동의 결과는 구현 정의됩니다.즉, GCC 및 Visual C++와 같은 대부분의 구현은 부호 비트를 보존함으로써 산술적 시프트로서 오른쪽 시프트를 구현합니다.
결론
특별한 연산자를 가진 자바와 달리>>>
평소와 다른 논리적 전환을 위해>>
그리고.<<
, C 및 C++ 에서는, 산술적인 시프트만을 실시합니다.일부 영역은 미정의 및 실장 정의되어 있습니다.연산이라고 하는 것은, 시프트 피연산자를 비트의 스트림으로서 취급하는 것이 아니라, 수학적으로 연산을 표현하는 표준적인 표현에 의한 것입니다.이것은 아마도, 이러한 영역을 논리적인 시프트로서 정의하는 것이 아니라, 정의되지 않은 채로 두는 이유일 것입니다.
다음은 C에서 int의 논리적인 오른쪽 이동과 산술적인 오른쪽 이동을 보장하는 함수입니다.
int logicalRightShift(int x, int n) {
return (unsigned)x >> n;
}
int arithmeticRightShift(int x, int n) {
if (x < 0 && n > 0)
return x >> n | ~(~0U >> n);
else
return x >> n;
}
당신이 얻는 변화의 종류와 관련하여 중요한 것은 당신이 바꾸고 있는 가치의 종류입니다.전형적인 버그의 근원은 문자 그대로를 예를 들어 비트로 전환하는 것입니다.예를 들어 부호 없는 정수의 맨 왼쪽 비트를 드롭하는 경우 마스크로 다음을 시도할 수 있습니다.
~0 >> 1
안타깝게도, 시프트되는 값(~0)이 서명되어 산술 시프트가 수행되므로 마스크에 모든 비트가 설정되므로 문제가 발생합니다.대신 다음과 같은 방법으로 값을 부호 없이 명시적으로 선언함으로써 논리적 전환을 강제할 수 있습니다.
~0U >> 1;
왼쪽 시프트에 1을 곱하면 2를 곱하고 오른쪽 시프트에 1을 곱하면 2를 나눕니다.
x = 5
x >> 1
x = 2 ( x=5/2)
x = 5
x << 1
x = 10 (x=5*2)
gcc는 일반적으로 부호 없는 변수와 부호 있는 변수의 왼쪽 방향 전환에 논리적인 이동을 사용합니다.산술적 오른쪽 시프트는 변수 확장에 서명하기 때문에 정말 중요한 것입니다.
gcc는 다른 컴파일러와 마찬가지로 필요에 따라 이를 사용합니다.
GCC에서는
-ve -> 산술 시프트의 경우
+ve -> Logical Shift의 경우
많은 c 컴파일러에 따르면:
<<
는 산술적 좌측 시프트 또는 비트 단위 좌측 시프트입니다.>>
는 산술적 우측 시프트 또는 비트별 우측 시프트입니다.
위키피디아에서 찾아봤는데 이렇게 써 있더군요
단, C에는 오른쪽 시프트 연산자 >>가1개밖에 없습니다.대부분의 C 컴파일러는 시프트되는 정수의 유형에 따라 실행할 오른쪽 시프트를 선택합니다.많은 경우 부호 있는 정수는 산술 시프트를 사용하여 시프트되고 부호 없는 정수는 논리 시프트를 사용하여 시프트됩니다.
컴파일러에 따라 달라지는 것 같네요.또한 이 기사에서는 왼쪽 시프트가 산술 및 논리적으로 동일하다는 점에 유의하십시오.보더 케이스(물론 높은 비트 세트)에 서명된 숫자와 서명되지 않은 숫자를 사용하여 간단한 테스트를 수행하여 컴파일러에서 결과를 확인할 것을 권장합니다.C는 기준이 없는 것 같기 때문에 적어도 그러한 의존을 피할 수 있다면 어느 쪽인가에 의존하는 것을 피하는 것이 좋습니다.
좌측 시프트<<
이것은 어느 정도 간단하며, 시프트 연산자를 사용할 때는 항상 비트 조작이기 때문에 더블 플로트 조작으로는 사용할 수 없습니다.시프트 1 0을 남길 때마다 항상 최하위 비트에 추가됩니다(LSB
).
하지만 오른쪽 교대에서는>>
하나 더 지켜야 하는 규칙이 있는데 그 규칙을 '사인 비트 복사'라고 합니다."sign bit copy"의 의미는 가장 중요한 비트가MSB
)은 다시 오른쪽 시프트 후에 설정됩니다.MSB
재설정되면 다시 재설정됩니다. 즉, 이전 값이 0이었다가 다시 전환한 후 이전 비트가 1이면 비트가 0이 되고 전환한 후 다시 1이 됩니다.이 규칙은 좌회전에는 적용되지 않습니다.
오른쪽 시프트에서 가장 중요한 예시는 음수를 오른쪽 시프트로 이동한 후 값이 0에 도달한 후 이 값을 -1로 이동한 경우 값이 몇 번이라도 동일하게 유지됩니다.확인해주세요.
언급URL : https://stackoverflow.com/questions/7622/are-the-shift-operators-arithmetic-or-logical-in-c
'programing' 카테고리의 다른 글
larabel 및 vue js를 사용하여 Axios 오류를 처리하는 더 나은 방법이 있습니까? (0) | 2022.08.09 |
---|---|
이미지가 Vue.js 2에 로드되지 않는 이유는 무엇입니까? (0) | 2022.08.09 |
정수의 제곱근이 정수인지 확인하는 가장 빠른 방 (0) | 2022.07.17 |
Java에서 XML을 JSON으로 변환하는 가장 빠른 방법 (0) | 2022.07.17 |
Vue2 + Vuex - 저장소에서 성공적으로 설정된 어레이를 렌더링하지 않음 (0) | 2022.07.17 |