네트워크 프로그래밍을 공부하다가 패킷을 분석하는 중에 이해가 안되는 것을 발견했다.
대충 다음 코드와 같았다.

1
2
char* p = header; //헤더의 시작을 가리키게 만듦
int data = p[0]<<8 + p[1]; //???

p는 8비트의 크기를 가지는 char형 포인터인데, p[0]을 8번 왼쪽으로 쉬프트 한것과 p[1]을 더해서 data 변수에 저장했다. 처음에 봤을 때는 당연히 오류인 줄 알았다. 8비트 짜리를 왼쪽으로 8번 밀면 오버플로우가 나서 0이 될 것 같았다. 그런데 코드를 실행해 보니까 제대로 작동했다. 이해가 안돼서 하룻동안 고민하다가 도달하게 된 결론을 적어 보겠다. 뇌피셜이라 틀린것일 수도 있는데 꽤나 타당하다.

가설 1 : 포인터라 그런가?

처음 든 생각은 ‘포인터라 약간 다르게 동작하나’ 였다. 이 생각은 빠르게 접었다. char 포인터면 8비트 영역을 가리키기 때문에 결국은 char형 변수와 다를게 없기 때문이다. 결정적으로 char형 변수를 만들어서 쉬프트 연산을 해보니까 똑같은 결과가 나왔다.

가설 2 : 컴퓨터 구조 때문이다.

첫번째 가설을 폐기한 후 일단 덮어 두었다. 시간이 조금 지나서 다시 들여다 보았는데 갑자기 번뜩이는 생각이 들었다. 컴퓨터 구조 때문이 아닐까.

컴퓨터 구조 강의에서 배운대로 우리 컴퓨터는 폰 노이만 구조를 따른다. Memory에 명령어와 변수가 저장되고 실행 될 때는 CPU에 fetch해와서 연산하는 방식이다. 변수를 CPU에 가져올 때 레지스터에 저장하는데 이 레지스터의 크기가 요즘은 32비트 또는 64비트이다. 8비트 보다는 크기 때문에 CPU에 올라갔을 때는 int형 변수처럼 취급이 되기 때문에 char를 쉬프트해도 앞에있는 애들이 살아 있는 것이라고 추측한다.

문제

내 노트북 OS 64bit 윈도우즈이기 때문에 레지스터의 크기도 64비트 일 것이고, 만약 32비트 짜리 int형 변수를 쉬프트하고 바로 long long형에 저장하면 char형을 쉬프트 했을 때 처럼 앞에 있는 애들 이 살아있을 줄 알았다. 근데 직접 코드를 돌려보니 그냥 오버플로우가 났다. 이건 왜 그런지 모르겠는데 사실 int형을 쉬프트 하면 오버플로우가 나기 때문에 당연한 결과였다.

1
2
3
4
5
6
7
#include<stdio.h>
int main(){
    __UINT32_TYPE__ var32 = 0xFFFFFFFF;
    printf("%llx\n", var32<<1);
    
    return 0;
}
1
fffffffe

결론

레지스터의 크기가 32bit 또는 64bit 라는 점 때문에 char를 쉬프트 해도 오버플로우가 나지 않는 것 같은데 64비트 레지스터인 경우 int를 쉬프트 했을 때 오버플로우가 나는 것은 좀 이해가 안된다. 누가 알려주면 참 좋겠다.

댓글남기기