Notes for C

정수 자료형의 저장 방식과 scanf함수 사용시 주의할 점

Sloth Coder 2022. 8. 28. 22:03

오늘 백준 문제를 풀고 코드를 이리저리 만져보다 발견한 주의점이다.

해당 문제: https://csloth.tistory.com/24

 

백준 2420번 사파리 월드 [C언어]

문제 출처: https://www.acmicpc.net/problem/2420 2420번: 사파리월드 첫째 줄에 두 도메인의 유명도 N과 M이 주어진다. (-2,000,000,000 ≤ N, M ≤ 2,000,000,000) www.acmicpc.net Sol) #include int main()..

csloth.tistory.com

 

 

C언어에서 정수 자료형의 저장 방식은 다음과 같다.

1. 2진수로 저장한다.

2. 어떤 한 자료형에서, 가장 왼쪽 비트는 부호 비트로 사용한다(0이면 양수, 1이면 음수로 판단).

3. 뺄셈, 혹은 음수 저장에는 2's complement(2의 보수) 방식을 사용한다.

 

위 문제에서 입력값의 제한 범위는 -2,000,000,000 ~ 2,000,000,000 이었다. int 자료형의 정수 저장 범위는 -2,147,483,648 ~ 2,147,483,647 이므로, 위 문제의 입력값들은 int 자료형으로 문제 없이 저장할 수 있다. 그렇다면, 위 문제를 다음과 같은 코드로 풀어도 될까?

#include <stdio.h>

int main(){
    long long s1, s2;
    long long diff;

    scanf("%d %d", &s1, &s2); //s1 과 s2는 모두 int범위 안에 들어오므로 %d로 받아도 될까?

    diff = s1 >= s2 ? s1 - s2 : s2 - s1;

    printf("%lld\n", diff);

    return 0;
}

1. s1과 s2모두 입력 과정에서는 int 범위 안에 들어온다.

2. 우리는 s1과 s2를 long long으로 선언 해 줬다.

3. 따라서, 입력만 int형태로 받을 뿐이지, 계산 결과는 int가 아닌 long long형이 되므로 계산 결과에도 문제가 없을 것이다.

 

그렇다면 위와 같은 코드에 s1 에는 2,000,000,000 을, s2 에는 -2,000,000,000 을 저장해주면 diff는 어떤 값으로 출력될까?

우리의 예상대로라면, 위 코드는 아무 문제가 없으므로 4,000,000,000 이 출력돼야 한다.

하지만 놀랍게도, 위 코드는 4589934592 라는 이상한 값을 출력하게 된다. 

 

위와 같은 문제는 C언어에서 정수를 저장하는 방식, 그 중에서도 특히 '음수인 정수'를 저장하는 방식때문에 발생하게 된다.

 

위 코드에서 s1과 s2는 모두 long long 형이다. 즉, 8바이트(64비트) 만큼의 저장공간을 보유하고 있을 것이다. 그런데 우리가 %d, 즉 4바이트(32비트)만큼 버퍼에서 정수를 읽어와 8바이트인 저장공간에 저장하게 되면 다음과 같이 저장된다.

 

<2,000,000,000을 %d로 버퍼에서 읽어 올 당시>

2^31 2^30 2^29 2^28 2^27 2^26 2^25 2^24
0(부호비트) 1 1 1 0 1 1 1
2^23 2^22 2^21 2^20 2^19 2^18 2^17 2^16
0 0 1 1 0 1 0 1
2^15 2^14 2^13 2^12 2^11 2^10 2^9 2^8
1 0 0 1 0 1 0 0
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
0 0 0 0 0 0 0 0

 

<2,000,000,000이 s1에 저장된 형태>

2^63 2^62 2^61 2^60 2^59 2^58 2^57 2^56
0(부호비트) 0 0 0 0 0 0 0
2^55 2^54 2^53 2^52 2^51 2^50 2^49 2^48
0 0 0 0 0 0 0 0
2^47 2^46 2^45 2^44 2^43 2^42 2^41 2^40
0 0 0 0 0 0 0 0
2^39 2^38 2^37 2^36 2^35 2^34 2^33 2^32
0 0 0 0 0 0 0 0
2^31 2^30 2^29 2^28 2^27 2^26 2^25 2^24
0 1 1 1 0 1 1 1
2^23 2^22 2^21 2^20 2^19 2^18 2^17 2^16
0 0 1 1 0 1 0 1
2^15 2^14 2^13 2^12 2^11 2^10 2^9 2^8
1 0 0 1 0 1 0 0
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
0 0 0 0 0 0 0 0

 

< -2,000,000,000을 %d로 버퍼에서 읽어 올 당시>

2^31 2^30 2^29 2^28 2^27 2^26 2^25 2^24
1(부호비트) 0 0 0 1 0 0 0
2^23 2^22 2^21 2^20 2^19 2^18 2^17 2^16
1 1 0 0 1 0 1 0
2^15 2^14 2^13 2^12 2^11 2^10 2^9 2^8
0 1 1 0 1 1 0 0
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
0 0 0 0 0 0 0 0

 

< -2,000,000,000이 s2에 잘못 저장된 형태>

2^63 2^62 2^61 2^60 2^59 2^58 2^57 2^56
0(long long에서의 부호비트) 0 0 0 0 0 0 0
2^55 2^54 2^53 2^52 2^51 2^50 2^49 2^48
0 0 0 0 0 0 0 0
2^47 2^46 2^45 2^44 2^43 2^42 2^41 2^40
0 0 0 0 0 0 0 0
2^39 2^38 2^37 2^36 2^35 2^34 2^33 2^32
0 0 0 0 0 0 0 0
2^31 2^30 2^29 2^28 2^27 2^26 2^25 2^24
1(int에서의 부호 비트) 0 0 0 1 0 0 0
2^23 2^22 2^21 2^20 2^19 2^18 2^17 2^16
1 1 0 0 1 0 1 0
2^15 2^14 2^13 2^12 2^11 2^10 2^9 2^8
0 1 1 0 1 1 0 0
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
0 0 0 0 0 0 0 0

 

각 숫자들이 저장된 형태를 보면, 양수일 경우 %d로 읽어와도 부호비트가 0이기때문에 아무런 문제도 발생하지 않는다.

하지만 음수일 경우 %d로 읽어오면 64비트 저장공간 중 2^0부터 시작해 처음 32비트만큼만 업데이트되게 된다.

 

그런데, 32비트에서의 부호비트의 위치와 64비트에서의 부호비트의 위치가 다르기때문에 형식지정자보다 더 큰 자료형에 음수인 정수를 저장하게 되면 원래 값과는 전혀 다른 어떤 한 양수로 취급돼 버리게 된다.

 

이때문에 위 코드에서는 -2,000,000,000 이 양수인 6589934592 이 되어 4589934592 와 같은 값이 나오게 되는 것이다.

 

-2,000,000,000을 long long 자료형에 올바르게 저장하기 위해서는 이를 %lld로 64비트만큼 읽어와 위에서는 업데이트 되지 못했던 나머지 32개의 비트들도 아래와 같이 모두 1로 반전될 수 있게 해 주어야 한다.

 

< -2,000,000,000이 s2에 올바르게 저장된 형태>

2^63 2^62 2^61 2^60 2^59 2^58 2^57 2^56
1(long long에서의 부호비트) 1 1 1 1 1 1 1
2^55 2^54 2^53 2^52 2^51 2^50 2^49 2^48
1 1 1 1 1 1 1 1
2^47 2^46 2^45 2^44 2^43 2^42 2^41 2^40
1 1 1 1 1 1 1 1
2^39 2^38 2^37 2^36 2^35 2^34 2^33 2^32
1 1 1 1 1 1 1 1
2^31 2^30 2^29 2^28 2^27 2^26 2^25 2^24
1(int에서의 부호 비트) 0 0 0 1 0 0 0
2^23 2^22 2^21 2^20 2^19 2^18 2^17 2^16
1 1 0 0 1 0 1 0
2^15 2^14 2^13 2^12 2^11 2^10 2^9 2^8
0 1 1 0 1 1 0 0
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
0 0 0 0 0 0 0 0

 

 

<결론>

음수인 정수를 다룰 때에는 항상 변수의 자료형의 크기와 형식 지정자가 읽어올 자료형의 크기를 맞춰 주자.

'Notes for C' 카테고리의 다른 글

후위 증감연산자와 전위 증감연산자의 비밀  (0) 2022.08.17
scanf 함수  (0) 2022.08.11
메모리 관련 함수  (0) 2022.08.07