문제

C에서는 시프트 연산자(<<, >>) 산술 또는 논리?

도움이 되었습니까?

해결책

에 따르면 K&R 2판 결과는 부호 있는 값의 오른쪽 이동에 따라 구현에 따라 달라집니다.

위키피디아 C/C++는 '보통' 부호 있는 값에 산술 시프트를 구현한다고 말합니다.

기본적으로 컴파일러를 테스트하거나 의존하지 않아야 합니다.현재 MS C++ 컴파일러에 대한 내 VS2008 도움말에는 해당 컴파일러가 산술 시프트를 수행한다고 나와 있습니다.

다른 팁

왼쪽으로 이동하는 경우 산술 이동과 논리 이동 사이에는 차이가 없습니다.오른쪽으로 이동하는 경우 이동 유형은 이동되는 값의 유형에 따라 달라집니다.

(차이에 익숙하지 않은 독자를 위한 배경 지식으로, 1비트만큼 "논리적" 오른쪽 이동은 모든 비트를 오른쪽으로 이동하고 가장 왼쪽 비트를 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);

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를 오버플로하는 경우 정의되지 않습니다.나는 승진형이다


이동

첫 번째는 데이터 유형 크기에 대한 걱정 없이 수학적 관점에서 논리적 시프트와 산술 시프트의 차이입니다.논리 시프트는 버려진 비트를 항상 0으로 채우는 반면 산술 시프트는 왼쪽 시프트에 대해서만 0으로 채우지만 오른쪽 시프트의 경우 MSB를 복사하여 피연산자의 부호를 유지합니다( 2의 보수 음수 값에 대한 인코딩).

즉, 논리 시프트는 시프트된 피연산자를 비트 스트림으로 보고 결과 값의 부호에 대해 신경 쓰지 않고 이동합니다.산술 시프트는 이를 (부호 있는) 숫자로 보고 시프트가 수행될 때 부호를 유지합니다.

숫자 X를 n으로 왼쪽 산술 시프트하는 것은 X에 2를 곱하는 것과 같습니다.N 따라서 논리적 왼쪽 시프트와 동일합니다.MSB가 어쨌든 끝에서 떨어지고 보존할 것이 없기 때문에 논리적 이동도 동일한 결과를 제공합니다.

숫자 X를 n으로 오른쪽 산술 시프트하는 것은 X를 2로 정수로 나누는 것과 같습니다.N X가 음수가 아닌 경우에만!정수 나눗셈은 수학적 나눗셈일 뿐이며 둥근 0 (잘라내다).

2의 보수 인코딩으로 표현되는 음수의 경우 n 비트만큼 오른쪽으로 이동하면 수학적으로 이를 2로 나누는 효과가 있습니다.N −무한대 방향으로 반올림(바닥);따라서 오른쪽 이동은 음수가 아닌 값과 음수 값에 따라 다릅니다.

X ≥ 0인 경우, X >> n = X / 2N = 트렁크(X ¼ 2N)

X < 0, X >> n = 바닥(X ¼ 2)N)

어디 ÷ 수학적 나눗셈이고, / 정수 나누기입니다.예를 살펴보겠습니다:

37)10 = 100101)2

37 ÷ 2 = 18.5

37 / 2 = 18(18.5를 0으로 반올림) = 10010)2 [산술적 오른쪽 시프트의 결과]

-37)10 = 11011011)2 (2의 보수, 8비트 표현을 고려)

-37 ÷ 2 = -18.5

-37 / 2 = -18(18.5를 0으로 반올림) = 11101110)2 [산술적 오른쪽 시프트의 결과가 아님]

-37 >> 1 = -19(-무한대 방향으로 18.5 반올림) = 11101101)2 [산술적 오른쪽 시프트의 결과]

처럼 Guy Steele이 지적했습니다., 이러한 불일치로 인해 둘 이상의 컴파일러에 있는 버그.여기서 음수가 아닌 값(수학)은 부호 없는 값과 음수가 아닌 값(C)에 매핑될 수 있습니다.둘 다 동일하게 취급되며 오른쪽 이동은 정수 나누기에 의해 수행됩니다.

따라서 논리와 산술은 왼쪽 이동에서 동일하며 오른쪽 이동에서는 음수가 아닌 값에 대해 동일합니다.서로 다른 것은 음수 값을 오른쪽으로 이동하는 것입니다.

피연산자 및 결과 유형

표준 C99 §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이 부호 있는 유형이고 음수가 아닌 값을 갖고 E1×2인 경우E2 결과 유형으로 표현할 수 있으면 이것이 결과 값입니다.그렇지 않으면 동작이 정의되지 않습니다.

왼쪽 시프트는 둘 다 동일하므로 비워진 비트는 단순히 0으로 채워집니다.그런 다음 부호 없는 유형과 부호 있는 유형 모두에 대해 산술적 변화가 발생한다고 명시합니다.논리적 시프트는 비트로 표시되는 값에 대해 신경 쓰지 않고 단지 비트 스트림으로 간주하기 때문에 이를 산술 시프트로 해석하고 있습니다.그러나 표준은 비트 단위로 이야기하지 않고 E1과 2의 곱으로 얻은 값으로 정의합니다.E2.

여기서 주의할 점은 부호 있는 유형의 경우 값이 음수가 아니어야 하고 결과 값이 결과 유형에서 표현 가능해야 한다는 것입니다.그렇지 않으면 작업이 정의되지 않습니다. 결과 유형은 통합 승격을 적용한 후 E1 유형이 되며 대상(결과를 보유할 변수) 유형이 아닙니다.결과 값은 암시적으로 대상 유형으로 변환됩니다.해당 유형으로 표현할 수 없는 경우 변환은 구현에 따라 정의됩니다(C99 §6.3.1.3/3).

E1이 음수 값을 갖는 부호 있는 유형인 경우 왼쪽 이동 동작은 정의되지 않습니다. 이는 쉽게 간과될 수 있는 정의되지 않은 동작에 대한 쉬운 경로입니다.

오른쪽 시프트

E1 >> E2의 결과는 E1이 오른쪽으로 이동한 E2 비트 위치입니다.E1에 부호 없는 유형이 있거나 E1에 부호 있는 유형과 음수가 아닌 값이 있는 경우 결과 값은 E1/2 몫의 정수 부분입니다.E2.E1에 부호 있는 유형과 음수 값이 있는 경우 결과 값은 구현에 따라 정의됩니다.

부호 없는 값과 음수가 아닌 부호 있는 값에 대한 오른쪽 시프트는 매우 간단합니다.빈 비트는 0으로 채워집니다. 부호 있는 음수 값의 경우 오른쪽 이동 결과는 구현에 따라 정의됩니다. 즉, GCC와 같은 대부분의 구현은 비주얼 C++ 부호 비트를 보존하여 산술 이동으로 오른쪽 이동을 구현합니다.

결론

특수 연산자가 있는 Java와는 달리 >>> 평소와는 다른 논리적 이동을 위해 >> 그리고 <<, C 및 C++에는 일부 영역이 정의되지 않고 구현이 정의되어 있는 산술 이동만 있습니다.내가 이를 산술이라고 생각하는 이유는 이동된 피연산자를 비트 스트림으로 처리하는 것이 아니라 수학적으로 연산을 표현하는 표준 표현 때문입니다.이것이 아마도 모든 사례를 논리적 변화로 정의하는 대신 해당 영역을 정의되지 않은/구현으로 남겨두는 이유일 것입니다.

얻는 변화 유형과 관련하여 중요한 것은 변화하는 가치의 유형입니다.버그의 전형적인 원인은 리터럴을 비트 마스크로 전환하는 경우입니다.예를 들어, 부호 없는 정수의 가장 왼쪽 비트를 삭제하려면 다음을 마스크로 사용해 볼 수 있습니다.

~0 >> 1

불행하게도, 이동되는 값(~0)에 부호가 지정되어 산술 이동이 수행되기 때문에 마스크에 모든 비트가 설정되므로 문제가 발생할 수 있습니다.대신 값을 부호 없는 값으로 명시적으로 선언하여 논리적 이동을 강제로 수행할 수 있습니다.다음과 같이 함으로써:

~0U >> 1;

다음은 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;
}

당신이 할 때 - 왼쪽 시프트 1으로 당신은 2를 곱합니다 - 오른쪽 전환 1으로 당신은 2로 나눕니다.

 x = 5
 x >> 1
 x = 2 ( x=5/2)

 x = 5
 x << 1
 x = 10 (x=5*2)

글쎄, 나는 보았다 위키피디아에 나와 있어, 그들은 이렇게 말합니다.

그러나 C에는 오른쪽 시프트 연산자가 하나뿐입니다. >>.많은 C 컴파일러는 어떤 유형의 정수가 바뀌는 지에 따라 수행 할 올바른 전환을 선택합니다.종종 서명 된 정수는 산술 이동을 사용하여 이동하고 서명되지 않은 정수는 논리적 시프트를 사용하여 이동합니다.

따라서 컴파일러에 따라 달라지는 것 같습니다.또한 해당 기사에서 왼쪽 시프트는 산술 및 논리에서 동일하다는 점에 유의하십시오.테두리 케이스(물론 높은 비트 세트)에 부호 있는 숫자와 부호 없는 숫자를 사용하여 간단한 테스트를 수행하고 컴파일러에서 결과가 무엇인지 확인하는 것이 좋습니다.또한 적어도 그러한 의존성을 피하는 것이 합리적이고 가능하다면 C에는 표준이 없는 것처럼 보이기 때문에 둘 중 하나에 의존하는 것을 피하는 것이 좋습니다.

왼쪽 시프트 <<

이것은 다소 쉽고 시프트 연산자를 사용할 때마다 항상 비트 단위 연산이므로 이중 및 부동 연산과 함께 사용할 수 없습니다.시프트 1을 0으로 둘 때마다 항상 최하위 비트에 추가됩니다(LSB).

하지만 오른쪽 쉬프트에서는 >> 우리는 하나의 추가 규칙을 따라야 하며 해당 규칙을 "부호 비트 복사"라고 합니다."부호 비트 복사"의 의미는 최상위 비트(MSB)가 설정되고 다시 오른쪽으로 시프트한 후 MSB 재설정되면 설정되고 다시 재설정됩니다. 즉, 이전 값이 0이었다가 다시 이동한 후 비트가 0이 되고 이전 비트가 1이었다면 이동 후 다시 1이 됩니다.이 규칙은 왼쪽 교대에는 적용되지 않습니다.

오른쪽 시프트에 대한 가장 중요한 예는 음수를 오른쪽 시프트로 이동하면 약간의 시프트 후에 값이 마침내 0에 도달한 다음 이 후 -1로 시프트하면 값이 동일하게 유지됩니다.확인해주십시오.

일반적으로 부호 없는 변수에는 논리 시프트를 사용하고 부호 있는 변수에는 왼쪽 시프트를 사용합니다.산술 오른쪽 시프트는 변수를 부호 확장하기 때문에 정말 중요한 것입니다.

다른 컴파일러와 마찬가지로 적용 가능한 경우 이를 사용할 것입니다.

GCC는

  1. for -ve - > 산술 시프트

  2. +ve -> 논리적 시프트의 경우

많은 사람들에 따르면 컴파일러:

  1. << 산술 왼쪽 시프트 또는 비트 단위 왼쪽 시프트입니다.
  2. >> 산술 오른쪽 시프트 또는 비트 단위 오른쪽 시프트입니다.
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top