[c] C에서 시프트 연산자 (<<, >>)가 산술적이거나 논리적입니까?

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



답변

에 따르면 K & R 2 판 결과는 구현에 의존 서명 값 오른쪽 변화에 대한 있습니다.

Wikipedia에 따르면 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에 오버플로 인 경우 정의되지 않습니다. i의 승격 된 유형


이동

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

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

N에 의해 번호 X의 좌측 시프트 연산 (2)에 의해 X를 승산에 상당 N 논리 왼쪽 시프트에 따라서 동일하다; 어쨌든 MSB가 끝에서 떨어지고 보존 할 것이 없기 때문에 논리적 이동도 동일한 결과를 제공합니다.

N에 의해 번호 X의 우측 시프트 연산에 의해 X 2의 정수 나눗셈 동등 N ONLY X는 음이 아닌 경우! 정수 나누기는 수학 나누기 일 뿐이며 0 ( trunc )을 향해 반올림 됩니다 .

2의 보수 인코딩으로 표현 된 음수의 경우, n 비트만큼 오른쪽으로 이동하면 수학적으로 2 n으로 나누고 -∞ ( floor ) 쪽으로 반올림 하는 효과가 있습니다 . 따라서 오른쪽 음의 이동은 음이 아닌 값과 음의 값이 다릅니다.

X ≥ 0 인 경우 X >> n = X / 2 n = trunc (X ÷ 2 n )

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 (-1∞를 -∞쪽으로 반올림) = 11101101) 2 [산술 오른쪽 이동 결과]

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

따라서 논리 및 산술은 왼쪽 이동과 오른쪽 이동의 음이 아닌 값에 해당합니다. 음수 값이 다르면 올바르게 이동합니다.

피연산자와 결과 유형

표준 C99 §6.5.7 :

각 피연산자는 정수 유형을 가져야합니다.

정수 승격은 각 피연산자에서 수행됩니다. 결과의 유형은 승격 된 왼쪽 피연산자의 유형입니다. 오른쪽 피연산자의 값이 음수이거나 승격 된 왼쪽 피연산자의 너비보다 크거나 같으면 동작이 정의되지 않습니다.

short E1 = 1, E2 = 3;
int R = E1 << E2;

위의 스 니펫에서 두 피연산자는 int(정수 승격으로 인해)됩니다. 경우는 E2제외 하였다 또는 E2 ≥ sizeof(int) * CHAR_BIT다음 동작이 정의되지 않는다. 사용 가능한 비트 수보다 많이 시프트하면 오버플로가 발생하기 때문입니다. 했다 R선언 된 short1, int시프트 연산의 결과가 암시 적으로 변환 될 것입니다 short; 대상 유형에서 값을 표현할 수없는 경우 구현이 정의 된 동작으로 이어질 수있는 축소 변환

왼쪽 시프트

E1 << E2의 결과는 E1 좌측-시프트 된 E2 비트 위치이고; 비워진 비트는 0으로 채워집니다. E1에 부호없는 유형이있는 경우 결과 값은 E1 × 2 E2 이며 결과 유형에 표시 할 수있는 최대 값보다 모듈로가 하나 더 줄어 듭니다. 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 및 Visual 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)


답변

글쎄, 나는 Wikipedia 에서 그것을 찾았다 .

그러나 C에는 오른쪽 시프트 연산자가 하나만 있습니다 >>. 많은 C 컴파일러는 어떤 유형의 정수가 시프트되는지에 따라 수행 할 오른쪽 시프트를 선택합니다. 종종 부호있는 정수는 산술 시프트를 사용하여 시프트되고 부호없는 정수는 논리 시프트를 사용하여 시프트됩니다.

따라서 컴파일러에 따라 달라집니다. 이 기사에서도 왼쪽 시프트는 산술 및 논리에 동일합니다. 테두리 케이스 (높은 비트 세트)에서 부호있는 숫자와 부호없는 숫자로 간단한 테스트를 수행하고 컴파일러에서 결과가 무엇인지 확인하는 것이 좋습니다. 적어도 C가 표준을 가지고 있지 않은 것처럼 보이기 때문에 적어도 의존성을 피하는 것이 합리적이고 가능하다면 피할 것을 권장합니다.