음수는 메모리에 어떻게 들어가 있나
Esc

    음수는 메모리에 어떻게 들어가 있나

    2026년 10월 26일 수정–C#기초

    메모리에는 0과 1만 들어갑니다. 그 한 자리가 비트이고, 비트 8개(1바이트)로 만들 수 있는 조합은 28 = 256가지라서 byte 는 0~255 를 담습니다. 그런데 int 는 -21억부터 시작합니다. 마이너스 기호를 적을 칸은 따로 없습니다. 음수는 어디에 들어가 있는 걸까요.

    이걸 알아 두면 int.MaxValue 에 1을 더했더니 갑자기 -21억이 되는 버그나, uint 에서 빼기 한 번에 42억이 나오는 버그를 바로 설명할 수 있습니다.

    결론부터 쓰면 이렇습니다.

    음수라고 다른 곳에 저장되지 않습니다. 같은 자리에 비트만 다르게 채워질 뿐이고, 맨 앞 비트 하나가 부호를 맡습니다.

    맨 앞 1비트를 부호에 쓴다

    byte 는 8비트를 전부 값에 씁니다. 그래서 0~255 이고 음수가 없습니다.

    sbyte · short · int · long 은 다릅니다. 비트 하나를 부호에 떼어 줍니다.

    int 의 32비트 쓰는 곳
    맨 앞 1비트 부호. 0이면 양수, 1이면 음수
    나머지 31비트 값

    그래서 int 가 담는 값은 한쪽으로 42억 가지가 아니라 231 = 21억 가지씩 두 쪽입니다.

    아래에서 “읽는 방식” 을 바꿔 보세요. 비트는 하나도 안 바뀌는데 값만 바뀝니다. 그리고 부호 있게 읽는 순간, 맨 앞 칸의 자릿값이 128 에서 부호 로 바뀝니다.

    11111011 은 부호 없이 읽으면 251, 부호 있게 읽으면 -5 입니다. 어느 쪽이 맞느냐는 질문은 성립하지 않습니다. 타입이 정합니다.

    0이 양수 쪽에 들어간다

    여기서 한 가지가 어긋납니다. 0은 부호가 없는데, 어딘가에는 넣어야 합니다. 0은 양수 쪽에 들어갑니다.

    범위 개수
    양수 0 ~ 2,147,483,647 0이 한 자리를 먹어서 하나 모자람
    음수 -1 ~ -2,147,483,648 0을 안 쓰니 그대로 21억

    음수가 하나 더 많습니다.

    Console.WriteLine(int.MaxValue);   //  2147483647
    Console.WriteLine(int.MinValue);   // -2147483648  ← 절댓값이 1 더 크다

    int.MinValue 의 절댓값이 int.MaxValue 보다 1 큰 이유가 이겁니다. 헷갈리면 위의 8비트 그림에서 확인하면 됩니다. 부호 있게 읽으면 -128 ~ 127 입니다.

    -5 는 메모리에 어떻게 놓이나

    말로만 하면 안 남으니 직접 넣어 봅니다.

    FB FF FF FF 입니다. “음수 표시” 같은 건 없습니다. 값의 비트를 보면 맨 앞이 1 이고, 그게 신호의 전부입니다.

    음수는 뒤집고 1을 더해 만든다

    -5 를 만드는 방법은 이렇습니다. 8비트로 보면 세 단계입니다.

    1단계   00000101    5
    2단계   11111010    모든 비트를 뒤집는다
    3단계   11111011    1을 더한다  →  -5

    이걸 2의 보수(two’s complement) 라고 합니다. 위 <x-bits> 에서 11111011 을 만들어 두고 부호 있게 읽으면 정말 -5 가 나옵니다.

    왜 뒤집고 1을 더하는지는 숫자로 보면 보입니다. 8비트를 전부 1로 채우면 255 입니다. 5를 뒤집는 것은 255 에서 5를 빼는 것과 같아서 250 이 되고, 1을 더하면 256 - 5 = 251 입니다. 처음에 부호 없이 읽었을 때 251 이던 그 비트가 바로 -5 였던 이유가 이것입니다. 8비트에서 -5 는 “256 에서 5 모자란 수” 로 적힙니다.

    왜 하필 이렇게 만드나

    “맨 앞 비트만 1로 바꾸면 되지 않나?” 가 자연스러운 질문입니다. 그렇게 안 하는 이유는 덧셈 회로 하나로 뺄셈까지 하려고입니다.

    2의 보수로 만들어 두면 뺄셈이 그냥 덧셈이 됩니다. a - b 는 a + (-b) 로 계산하면 되기 때문입니다. 5 + (-5) 를 8비트로 직접 더해봅니다.

      00000101   (5)
    + 11111011   (-5)
    = 100000000

    9번째 비트는 8비트 밖으로 밀려나 버려지고, 남는 건 00000000 = 0 입니다. 덧셈 회로 하나로 뺄셈까지 처리됩니다.

    시계를 3시간 뒤로 돌리는 것과 앞으로 9시간 돌리는 게 같은 것과 같은 원리입니다. 자릿수가 정해져 있으면 뒤로 가는 것과 앞으로 도는 것이 같아집니다.

    맨 앞 비트만 뒤집는 방식(부호-크기 표현)을 쓰면 +0 과 -0 이 따로 생기고, 덧셈 회로가 부호를 따로 봐야 합니다. 2의 보수는 그 둘을 다 해결합니다.

    int.MaxValue 에 1을 더하면 -21억이 된다

    같은 원리로 넘침(오버플로)도 설명됩니다. 8비트로 보면 부호 있는 최댓값 127 은 01111111 입니다. 여기에 1을 더합니다.

      01111111   (127)
    + 00000001   (1)
    = 10000000   (-128)

    자리올림이 맨 앞 칸, 곧 부호 비트까지 올라갑니다. 맨 앞이 1이 됐으니 음수로 읽히고, 값은 가장 작은 -128 입니다. int 도 똑같아서 int.MaxValue 에 1을 더하면 int.MinValue 가 됩니다. 예외도 경고도 나지 않습니다.

    int hp = int.MaxValue;
    hp = hp + 1;
    Console.WriteLine(hp);   // -2147483648

    점수나 돈처럼 커질 수 있는 값은 long 을 쓰거나, checked 로 감싸서 넘치는 순간 예외(프로그램이 그 줄에서 멈추고 오류를 알리는 것)가 나게 합니다.

    u 가 붙은 타입은 부호 비트를 안 쓴다

    byte · ushort · uint · ulong 은 부호 비트가 없습니다. 전부를 값에 씁니다.

    타입 비트 범위
    sbyte 8 -128 ~ 127
    byte 8 0 ~ 255
    short 16 -32,768 ~ 32,767
    ushort 16 0 ~ 65,535
    int 32 약 -21억 ~ 21억
    uint 32 0 ~ 약 42억

    가짓수는 같습니다. 8비트면 어느 쪽이든 256가지고, 그 256개를 0부터 세느냐 가운데를 기준으로 나누느냐만 다릅니다.

    기본은 int 입니다. 음수가 안 나온다고 uint 를 쓰기 시작하면 빼기 한 번에 값이 42억으로 튀는 자리가 생깁니다. uint 0 에서 1을 빼면 비트가 전부 1이 되고, 부호 없이 읽으니 가장 큰 값인 4,294,967,295 가 됩니다. 정말 필요할 때만 씁니다.

    정리

    • 음수도 그냥 비트다. 다른 곳에 저장되지 않는다.
    • 부호 있는 타입은 맨 앞 1비트를 부호에 쓴다. 0이면 양수, 1이면 음수.
    • 같은 비트를 부호 없이 읽으면 251, 부호 있게 읽으면 -5. 무엇으로 읽을지는 타입이 정한다.
    • 0이 양수 쪽에 들어가므로 음수가 하나 더 많다.
    • 음수는 뒤집고 1을 더해 만든다(2의 보수). 그래야 뺄셈이 덧셈이 된다.
    • 최댓값에 1을 더하면 자리올림이 부호 비트까지 올라가 가장 작은 음수가 된다. 예외도 경고도 없다.
    • u 가 붙은 타입은 부호 비트를 안 쓴다. 가짓수는 같고 배치만 다르다.