2010-05-14 4 views
9

다음과 같은 질문이 있습니다. 포함 된 어셈블리없이 C에서 회전 교대를 수행하는 방법. 더 구체적으로, 어떻게 시프트 회전 32 비트 int.C에서 회전 교대를 수행하는 방법

이제이 문제를 long long int 유형의 도움으로 해결할 예정입니다.하지만 좀 더보기 싫어서 더 우아한 방법이 있는지 알고 싶습니다.

친절하게 제공합니다.

답변

15

(향후 독자들에게 경고) : 위키 백과의 코드는 비정상 asm (gcc에 분기 또는 cmov 포함)을 생성합니다. 효율적인 UB 프리 회전을 위해서는 Best practices for circular shift (rotate) operations in C++을 참조하십시오. Wikipedia에서


:

unsigned int _rotl(unsigned int value, int shift) { 
    if ((shift &= 31) == 0) 
     return value; 
    return (value << shift) | (value >> (32 - shift)); 
} 

unsigned int _rotr(unsigned int value, int shift) { 
    if ((shift &= 31) == 0) 
     return value; 
    return (value >> shift) | (value << (32 - shift)); 
} 
+0

대상에'rotl' 명령이있는 경우 최종 어셈블리에서 사용하겠습니까? 그렇지 않다면 (나는 의심 스럽다), 나는 인라인 어셈블리보다 다른 방법이 없다고 생각한다. – Gauthier

+0

위키피디아가 맞는지는 모르겠지만 회전 연산을 수행 할 때 간단히 수행하면 몇 가지 결함이있는 것으로 보입니다 (값 >> 시프트). (값 << (32- 시프트)). 주의 사항 : C의 오른쪽 시프트 연산은 부호 확장됩니다. –

+2

오른쪽 시프트는 변수가 시프트 된 경우에만 부호 확장되어야합니다. 'value'변수가 서명 된 경우 올바른 전환을 위해 부호없는 변수로 변환해야합니다. –

3

이 대답은 내가 Best-practices for compiler-friendly rotates에 게시 무엇의 중복입니다.

자세한 내용은 my answer on another question을 참조하십시오. 당신이 할 수 있도록, 임의의 정수 유형에 대한

uint32_t rotl32 (uint32_t x, unsigned int n) 
{ 
    const unsigned int mask = (CHAR_BIT*sizeof(x)-1); 

    assert ((n<=mask) &&"rotate by type width or more"); 
    n &= mask; // avoid undef behaviour with NDEBUG. 0 overhead for most types/compilers 
    return (x<<n) | (x>>((-n)&mask)); 
} 

작품뿐만 아니라 uint32_t :

어떤 정의되지 않은 동작을 방지 C의 회전을 표현하는 가장 컴파일러 친화적 인 방법은 John Regehr의 구현 것 같다 여러 버전. 이 버전 inlines to a single rol %cl, reg (또는 rol $imm8, reg)은 컴파일러가 명령에 이미 마스크 작업이 내장되어 있음을 알고 있기 때문에 x86에 있습니다.

임시적으로 int에 저장된 16 비트 값을 가지고있을 때 실수로 잘못된 너비의 회전을하고 싶지 않기 때문에 피연산자 유형에 템플릿을 지정하지 않는 것이 좋습니다. 특히 정수 승격 규칙은 부호가없는 유형이 포함 된 표현식의 결과를 int으로 바꿀 수 있기 때문에 특히 그렇습니다.

x에 부호없는 유형을 사용하고 반환 값이 맞는지 확인하십시오. 그렇지 않으면 회전되지 않습니다. (gcc는 0보다 오히려 부호 비트의 복사본을 이동 시키므로, 두 개의 쉬프트 된 값이 합쳐져서 OR 일 때 문제가 발생합니다.)

+0

@chux : 회전 수의 유형을 완화 할 수 있습니다 ... 'gcc'는 'n'이 16이나 8이 아닌 32 또는 64b 유형이지만 'int'또는 'unsigned'를 사용하는 경우 x86에서 더 나은 코드를 생성합니다. 거기에서 ok 일 수 있었다. 그래도 내 어설트() 논리 오류 잡기. –

+1

@chux : 아, 어리석은 나를. 네가 'n'과 '마스크'가 아니라 'n'과 'x'라고 생각했다. 64 비트, 32 비트, 16 비트, 8 비트 타입을 실험 한 결과'uint32_t'를'n'으로 골랐고'uint32_t n '을 사용하여 gcc가 중복 AND 명령을 내놓은 것을 발견했습니다. –

+0

@chux : 동의 함. 'unsigned '은 이미 컴파일러/플랫폼에서 32 비트입니다. asm 출력을 보았습니다. 내 답변을 완성했습니다. 도움을 주셔서 감사합니다. –

1

쓰레드가 오래되었지만, 나는 두 센트를 토론하고 문제의 해결책을 제안하십시오. 그것이 가치가 있기를 바라지 만, 내가 틀렸다면 제발 저를 교정하십시오.

나는 효율적이고 안전한 회전 방법을 찾고있을 때 실제로 그 해결책이 없다는 것에 놀랐습니다. 여기 몇 가지 관련 스레드를 발견 https://blog.regehr.org/archives/1063 (안전, 효율적인, 그리고 C/C++에서 휴대용 회전) (분기하지만 안전 포함) Best practices for circular shift (rotate) operations in C++ 및 위키 피 디아 스타일 :

uint32_t wikipedia_rotl(uint32_t value, int shift) { 
    if ((shift &= 31) == 0) 
     return value; 
    return (value << shift) | (value >> (32 - shift)); 
} 

묵상 조금 후 결과적으로 알리미가 분기없이 항상 < 시프트 조건에 맞는 약수보다 항상 낮기 때문에 모듈로 나누기가 기준에 맞을 것으로 나타났습니다.도면의 수학적 관점에서 :

∀ x> = 0, Y (X 개조 Y) < Y 우리의 경우 매에서

정확히이다 (X의 32 %) < 32 우리 성취하고 싶다. (그리고 그래, 내가 경험적 것을 확인하고 항상 < 32)

#include <stdint.h> 
uint32_t rotl32b_i1m (uint32_t x, uint32_t shift) 
    { 
    shift %= 32; 
    return (x<<n) | (x>>(32-shift)); 
    } 

또한 모드()의 100 개 비트를 가정 해 봅시다의 실제 회전이 3 번 전체 32 개 비트를 회전하고 있기 때문에, 과정을하는 본질적으로 단순화 아무것도 변경하지 않고 4 비트를 변경합니다. 그래서 100 % 32 == 4를 계산하고 4 비트를 회전하는 것이 더 나은가요? 그것은 어쨌든 단일 프로세서 연산을 필요로하고 상수 값 + 하나의 명령을 더해서 인자를 스택에서 가져와야합니다. 그러나 위키피디아처럼 if()를 사용하여 분기하는 것보다 여전히 좋습니다.

그럼, 어떻게 생각하니?

+0

스택 오버플로에 오신 것을 환영합니다! 이것은 실제로 질문에 대답하지 않습니다. 피드백을 찾고 있다면,보다 완벽한 방식으로 샘플을 작성하고 [codereview.se]에서 비평을 요청할 수 있습니다. 다른 것들이 다르게 수행되기 때문에 [Stack Overflow 사용자를위한 코드 검토 가이드] (// codereview.meta.stackexchange.com/a/5778)를 먼저 읽어보십시오! –

+0

'shift % = 32'는 John Regehr의 'shift & = 31'와 정확히 같습니다. base-2 modulo는 상위 비트를 마스킹하여 처리 할 수 ​​있습니다. (컴파일러는 이것을 알고 있으므로 적어도 최적화가 가능할 때는 버전을 동일하게 컴파일 할 것입니다.)'(32-shift)'에 그것을 쓰는 것을 잊었을 때를 제외하고, 여러분의 버전은'shift = 0'에 대한 C의 정의되지 않은 동작을 갖습니다 (비록 대부분의 컴파일러에서 회전시키기만하면 컴파일됩니다.) –

+0

네, 맞습니다. shift = 32에서는 어떤 일이 일어날 지 생각했지만 shift = 0이면 분석하지 않았습니다.같은 시간에 C/C++에서 형식적으로 두 버전을 모두 사용할 수 없으며 강제로 "wikipedia"버전 (조건을 확인하고 분기를 생성 함)을 사용하는 'shift & = 31'과 동일한 정의되지 않은 동작이 발생합니다. 제가 사용 매우 안전한 코드를 생성 GCC 확인할 롤 : #의 GCC -g -O2 -S rot321m1.c \t _rotl32b_i1m : \t퍼센트 ESP \t pushl \t%의 EBP \t의 movl %의 EBP \t의 movl \t 8 (%의 EBP) %의 EAX \t \t \t의 movl 12 (%의 EBP), ECX % \t \t \t andl $ 31 % ECX \t \t 롤 \t퍼센트 카스티 % EAX \t \t leave \t \t ret // John Regehr의 shift & = 31 버전의 경우 동일합니다. – tansy

관련 문제