본문 바로가기
프로그래밍 언어/C, C++

C언어에서의 16진수, signed int와 unsigned int

by 니키티스 2021. 5. 3.

문제 발생

만약 int형 변수의 32비트에서 상위 4비트만 떼어내려고 하면 어떻게 해야 할까?

 

이건 오른쪽으로 28비트만큼 쉬프트 하면 얻을 수 있다.

int bit;
result = bit >> 28;

그러나 여기서, 만약 bit가 음수라면 어떨까?

C언어는 부호가 있는 정수형 연산에서, 왼쪽 쉬프트(<<)는 논리 쉬프트, 오른쪽 쉬프트(>>)는 산술 쉬프트로 계산한다.

 

여기서 논리 쉬프트는 무조건 빈자리를 0으로 채운다는 뜻이고,

1101 << 2 = 0100

산술 쉬프트는 빈자리를 부호 값과 똑같이 채운다는 뜻이다.

0101 >> 1 = 0010
1101 >> 1 = 1110

 

 

그렇다면 음수를 오른쪽으로 28비트 쉬프트하게 되면(bit >> 28), 상위 비트는 모두 1로 채워지게 된다고 예상할 수 있다. 이를 해결하려면 쉬프트와 동시에 *마스크를 씌워줘서 상위 비트를 지울 수 있다.

 

(* 마스킹(Masking): 숫자에 비트곱 연산(&)을 통해 필요한 비트만 남기는 기법. 예를 들어 오른쪽 4비트만 남기고 싶다면, (원하는 숫자) & 0x0F로 얻을 수 있다. 이때 0x0F는 마스크(Mask)가 된다.)

 

그렇다면 궁금한 게 생기는데, (1) 쉬프트를 먼저 하고 마스크를 씌우는 것과 (2) 마스크를 먼저 씌운 다음 쉬프트 한 결과는 같을까?

 

1
2
(bit >> 28& 0x0F        // (1)
(bit & 0xF0000000>> 28 // (2)
cs

 

정답은 '똑같다'이다.

 

 

결과

음수를 넣든 양수를 넣든 결과는 똑같이 나타난다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main()
{
    int bit;
    while (1)
    {
        printf("bit = ");
        scanf("%d"&bit);
        printf("(%d(%08x) >> 28) & 0x0F = %d(%08x)\n", bit, bit, 
            (bit >> 28& 0x0F, (bit >> 28& 0x0F); // (1) 쉬프트하고 마스크를 씌웠을 때
        printf("(%d(%08x) & (unsigned)0xF0000000) >> 28 = %d(%08x)\n", bit, bit, 
            (bit & 0xF0000000>> 28, (bit & 0xF0000000>> 28); // (2) 마스크를 씌우고 쉬프트했을 때
    }
    return 0;
}
cs

 

위 코드를 직접 실행해본 결과

 

위 코드는 (1), (2)를 실제로 코드로 작성한 건데 실행 안 해본 사람도 이해할 수 있게 결과를 삽입해놨다.

 

나는 마스크를 먼저 씌우고 쉬프트를 하게 되면 결과가 다르게 나타날 것 같았다.

(bit & 0xF0000000) >> 28

음수에 0xF0000000이라는 마스크를 씌우면 MSB가 1이므로 그대로 음수로 나타나고,

음수를 오른쪽으로 쉬프트하면 산술 쉬프트에 의해 상위 비트가 모두 1로 채워질 것이라 생각했다.

 

그러나 실제로는 그렇지 않았다.

 

왜 이런 결과가 나올까? 

 

그건 바로

1. 0xF0000000이 int가 아니라 unsigned int로 계산되면서

2. int와 unsigned int 간에 연산이 일어나면 unsigned로 계산되기 때문이다.

 

C언어에서 리터럴 키워드(i, L, LL) 없이 16진수를 적으면 무슨 형일까?

int형은 32비트이고 16진수로 8개의 숫자로 표현할 수 있다.

그러면 0xF0000000은 최상위 비트가 1이므로 -268,435,456과 같이 음수로 저장될 것이라고 생각할 수 있다.

int(부호가 있는 정수)로는 0xF000 0000 = -268,435,456

 

그러나 실제로는 16진수로 수를 표현하면 무조건 양의 정수로 계산된다.

 

그래서 0xF0000000은 int형 변수에서는 음수가 되므로 unsigned int로 형이 결정된다.

예상과 달리 결과가 4,026,531,840으로 계산된다.

 

unsigned int(부호가 없는 정수)로는 0xF000 0000 = 4,026,531,840

 

리터럴 키워드를 붙이지 않으면 16진수 표현법은 무조건 양의 정수가 되도록 계산된다.

비슷한 예시로 0xF000 0000 0000을 적으면 long long으로, 64비트를 모두 채워 0xF000 0000 0000 0000을 적으면 unsigned long long이 된다.

0xf000 0000 0000 -> long long
0xf000 0000 0000 0000 -> unsigned long long. 이처럼 무조건 양수가 되도록 형이 정해진다.

 

signed와 unsigned의 연산 = unsigned

다시 코드로 돌아오자.

1
2
3
4
5
// 상위 4비트를 계산하기
// (1) 쉬프트를 먼저 하고, 마스크를 씌운다.
(bit >> 28& 0x0F
// (2) 마스크를 먼저 씌우고, 쉬프트를 한다.
(bit & 0xF0000000>> 28
cs

 

이 코드에서 0xF0000000이 unsigned인 것은 알겠다.

 

그런데 왜 (bit & 0xF0000000) >> 28의 결과가 양수로 나타나는 건가?

 

그것은 부호가 있는 정수(signed)와 부호가 없는 정수(unsigned)를 연산하면 부호가 없는 정수(unsigned)로 계산되기 때문이다.

 

CS:APP - 정보, 정수 형변환 - KOYO KR

 

CS:APP - 정보, 정수 형변환

중요한 것 중에서도 글로 적기 쉬운 것만 적음 1. signed와 unsigned 간의 변환 C에서 signed와 unsigned 간의 캐스팅은 어떻게 될까? 한가지만 명심하자. 비트상의 데이터

koyo.kr

 

위 링크는 CS:APP에 있는 내용을 정리한 글로, 참고하기 좋을 거 같아서 올려보았다.

 

다시 말하면, (int) & (unsigned int)를 하게 되면 결과가 unsigned int로 나타나게 된다.

즉, unsigned를 오른쪽 쉬프트 해봐야 양수이므로 상위 비트가 0으로 채워지게 된다!!

bit        // int
0xF0000000    // unsigned int
(bit & 0xF0000000)    // unsigned int
(bit & 0xF0000000) >> 28    // unsigned int

 

 

 

그렇다면 0xF0000000을 int로 형 변환하면 결과가 달라질까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
int main()
{
    int bit;
    while (1)
    {
        printf("bit = ");
        scanf("%d"&bit);
        // unsigned 그대로 계산할 때
        printf("(%d(%08x) & (unsigned)0xF0000000) >> 28 = %d(%08x)\n"
            bit, bit, (bit & 0xF0000000>> 28, (bit & 0xF0000000>> 28);
        // int로 형변환했을 때
        printf("(%d(%08x) & (signed)0xF0000000) >> 28 = %d(%08x)\n"
            bit, bit, (bit & (int)0xF0000000>> 28, (bit & (int)0xF0000000>> 28);
    }
    return 0;
}
cs

 

 

 

실제로 int로 변환하니 다른 결과가 나타나는 걸 볼 수 있다.

 

 

 

3줄 요약

1. C언어에서 16진수로 표기하면, 변수 형이나 리터럴 키워드(i, U, L)를 붙이지 않는 한 양수가 취급하는 것으로 보인다.

2. int와 unsigned int를 계산하면 unsigned int가 되니 주의하자.

3. 그래서 프로그래밍할 땐 int나 unsigned int 중 하나만 사용하는 것이 좋다.

댓글