C 언어 - 정수와 부동소수점 수의 메모리 저장 방식 (도해 설명)

서론:

정수의 이진 표현 방식에는 세 가지가 있습니다: 원코드(원형), 역코드(반전), 보수코드(보수). 부호 있는 정수의 세 가지 표현 방식 모두 부호 비트와 값 비트 두 부분으로 구성됩니다. 2진수 시퀀스에서 최상위 1비트는 부호 비트로 사용되며, 나머지는 모두 값 비트입니다. 부호 비트는 양수를 "0"으로, 음수를 "1"로 표현합니다. 원코드: 수치를 양수/음수 형태로 직접 이진수로 변환한 것입니다. 역코드: 원코드의 부호 비트는 그대로 두고 다른 비트들을 모두 반전시켜 얻을 수 있습니다. 보수코드: 역코드에 1을 더하면 보수코드가 됩니다. 역코드에서 원코드로 돌아가는 것도 반전 후 1을 더하는 연산을 사용할 수 있습니다.

1. 정수의 메모리 저장 방식

1.1 부호 없는 정수 표현 방식

부호 없는 수의 원코드, 역코드, 보수코드는 모두 동일합니다. 아래 예제는 원코드, 역코드, 보수코드를 구체적으로 설명합니다:

#include <stdio.h>
int main()
{
    unsigned_data num = 10;
    //10
    //원코드: 이진수 시퀀스는 1010이며, 부호 없는 수이므로 부호 비트는 0입니다. 따라서 원코드 앞에는 0이 오고 나머지 자리는 0으로 채워집니다.
    // 00000000 00000000 00000000 00001010 -원코드
    // 00000000 00000000 00000000 00001010 -역코드
    // 00000000 00000000 00000000 00001010 -보수코드
    return 0;
}
//메모리에서는 16진수 방식으로 저장됩니다:
//num   2진수→16진수  —— 0x00 00 00 0A

1.2 부호 있는 정수 표현 방식

양수: 원코드, 역코드, 보수코드는 모두 동일합니다 - 원코드: 이진수 시퀀스에 따라 결정됩니다 음수: 원코드 - 이진수 시퀀스에 따라 결정됨, 역코드 - 원코드를 반전, 보수코드 - 역코드 + 1 예시:

#include <stdio.h>
int main()
{
    signed_value negative_num = -20;
    //-20
    // 원코드: 이진수 시퀀스는 10100이며, 음수이므로 부호 비트는 1입니다. 따라서 원코드 앞에는 1이 오고 나머지 자리는 0으로 채워집니다.
    //10000000 00000000 00000000 00010100 - 원코드
    //11111111 11111111 11111111 11101011 - 역코드
    //11111111 11111111 11111111 11101100 - 보수코드
    return 0;
}
//negative_num은 메모리에서 0xFF FF FF EC로 저장됩니다.

1.3 바이트 순서 (바이트 오더)

바이트 순서란: 바이트 하나보다 크기가 큰 데이터 타입이 메모리에 저장되는 순서입니다. 이는 크로스 플랫폼 및 네트워크 프로그래밍에서 고려해야 할 문제입니다. 바이트 순서는 일반적으로 두 가지로 분류됩니다:

  1. 빅 엔디안(Big-Endian): 상위 바이트가 메모리의 낮은 주소에 배치되고, 하위 바이트가 메모리의 높은 주소에 배치됩니다.
  2. 리틀 엔디안(Little-Endian): 하위 바이트가 메모리의 낮은 주소에 배치되고, 상위 바이트가 메모리의 높은 주소에 배치됩니다.

자신의 컴퓨터가 빅 엔디인지 리틀 엔디안인지 확인하는 방법은 다음과 같습니다:

#include <stdio.h>
int check_endian()
{
    union
    {
        int number;
        char byte;
    }data_union;
    data_union.number = 1;
    return data_union.byte;
}
int main()
{
    int result = check_endian();
    if (result == 1)
    {
        printf("리틀 엔디안\n");
    }
    else
    {
        printf("빅 엔디안\n");
    }
    return 0;
}

2. 정수 자릿수 축소, 정수 승격, 산술 변환

2.1 정수 자릿수 축소

정수 자릿수 축소란? 너무 길어서 저장할 수 없을 때 발생합니다:

바이트 수가 더 많은 변수를 바이트 수가 더 적은 변수에 할당할 때, 예를 들어 int(4바이트)를 char(1바이트)에 할당할 때, int 타입의 비트 중 가장 낮은 8비트만 char 타입 변수에 할당되고 상위 비트들은 모두 "축소"됩니다.

char small_value = -1;
//-1은 기본적으로 정수 타입입니다
//원코드——00000000 00000000 00000000 00000001
//역코드——11111111 11111111 11111111 11111110
//보수코드——11111111 11111111 11111111 11111111
//원래 데이터의 끝에서부터 필요한 데이터 크기만큼 직접 가져옵니다: 최종 결과——11111111

2.2 정수 승격

정수 승격이란 무엇인가요?

먼저, 다음 사실을 알아야 합니다: C 언어에서 정수 연산은 항상 기본 정수 타입 이상의 정밀도로 수행됩니다.

이 말의 의미는 무엇일까요? 쉽게 말해, C 언어에서 바이트 수가 정수 타입보다 작은 데이터 타입이 정수 연산에 참여할 때, 해당 타입의 데이터는 기본적으로 정수 데이터로 변환됩니다.

여기서 이 타입의 데이터가 정수 데이터로 변환되는 과정을 정수 승격이라고 합니다.

정수 승격이 발생하는 이유는 컴퓨터 아키텍처에 의해 결정됩니다. 우리 모두 컴퓨터의 계산은 CPU가 수행하며, 구체적으로는 CPU의 산술 논리 장치(ALU)가 수행한다는 것을 알고 있습니다. ALU는 일반적으로 설계될 때 작업 대상인 피연산자의 바이트 크기가 적어도 int의 바이트 크기여야 한다는 요구 사항을 가지고 있습니다. 이때 또 명확히 해야 할 사실은, 데이터가 연산될 때 데이터가 ALU에 직접 저장되는 것이 아니라 CPU의 레지스터에 저장된다는 것입니다. 그리고 일반 레지스터의 바이트 크기는 ALU 피연산자의 바이트 크기와 일치합니다.

사실 이 문제는 주소 문제와 유사합니다. 예를 들어, 저는 4개의 방이 있는 집을 가지고 있는데, 만약 당신이 4개의 방을 모두 채우지 못한다면, 당신을 어느 작은 방에 배치해야 할까요? 만약 제가 마음대로 작은 방에 당신을 배치한다면, 당신을 찾으려면 큰 방에서 4개의 작은 방을 모두 뒤져야 하므로 효율이 떨어집니다. 따라서 당신이 체크인할 때, 당신이 4개의 방을 모두 채울 수 있을 때까지 기다렸다가 당신을 넣어, 큰 방 번호로 쉽게 찾을 수 있도록 해드립니다.

사실 정수 승격이 필요한 데이터 타입은 char와 short 두 가지뿐입니다. 4바이트 이상의 데이터 타입이라면? 당신을 위해 두 개의 방을 배치해 드리겠습니다. C 언어의 비사용자 정의 데이터 타입의 바이트 크기는 4가지(1, 2, 4, 8)뿐입니다.

몇 가지 예를 들어 설명해 드리겠습니다:

char positive_char = 1;
//하나의 바이트만 있으므로 8비트입니다 (0000 0001)
//unsigned char로 정의되지 않았으므로 부호 있는 char입니다
//정수 승격 시 위에서 설명한 부분과 같이 부호 비트가 0이므로 0으로 채웁니다
//0000 0000 0000 0000 0000 0000 0000 0001
char negative_char = -1;
//char는 하나의 바이트만 있으므로 8비트입니다 (1111 1111)
//unsigned char로 정의되지 않았으므로 부호 있는 char입니다
//따라서 부호 비트는 1이며, 정수 승격 시 3바이트의 1로 채웁니다
//1111 1111 1111 1111 1111 1111 1111 1111
unsigned short unsigned_short_value = -1;
//short 타입이므로 2바이트, 16비트입니다 (1111 1111 1111 1111)
//부호 없는 short이므로 정수 승격 시 0으로만 채웁니다
//즉 —— 0000 0000 0000 0000 1111 1111 1111 1111

2.3 산술 변환

산술 변환이란 무엇인가요: 알려진 사실: char와 short는 바이트 길이가 int 타입보다 작을 때 산술 연산에 참여하면 정수 승격이 발생합니다.

바이트 길이가 int 타입보다 큰 데이터가 연산에 참여할 때, 어떤 연산자의 두 피연산자가 다른 타입이고 한 피연산자가 수준(낮은 수준의 데이터가 높은 수준의 데이터로 변환)에 따라 다른 피연산자의 타입으로 변환해야 할 때, 이러한 변환을 산술 변환이라고 합니다.

산술 변환의 데이터 수준(높은 순서부터):

long double

double

long float

float

unsigned long int

long int

unsigned int

int

3. 부동소수점 수의 메모리 저장 방식

3.1 부동소수점 수의 저장 규칙

국제 표준 IEEE(전기전자공학자협회) 754에 따르면, 임의의 이진 부동소수점 수 V는 다음과 같은 형태로 표현할 수 있습니다: V = (-1)^S × M × 2^E

  1. (-1)^S는 부호 비트를 나타내며, S=0일 때 V는 양수이고, S=1일 때 V는 음수입니다
  2. M은 유효 숫자를 나타내며, M은 1 이상, 2 미만의 값입니다
  3. E는 지수 비트를 나타냅니다

예를 들어, 십진수 5.0을 이진수로 쓰면 101.0이며, 이는 1.01×2^2와 같습니다. 위의 V 형식에 따라 S=0, M=1.01, E=2가 됩니다. 십진수 -5.0을 이진수로 쓰면 -101.0이며, 이는 -1.01×2^2와 같습니다. 따라서 S=1, M=1.01, E=2가 됩니다.

3.2 M의 정의

앞서 언급했듯이, 1≤M<2이므로, M은 1.xxxxxx 형태로 쓸 수 있으며, 여기서 xxxxxx는 소수 부분을 나타냅니다. IEEE 754는 컴퓨터 내부에서 M을 저장할 때 이 수의 첫 번째 비트는 항상 1이라고 가정하므로, 이를 생략하고 뒷부분의 xxxxxx만 저장하도록 규정합니다. 예를 들어 1.01을 저장할 때는 01만 저장하고, 읽을 때 첫 번째 1을 다시 더합니다. 이렇게 하는 목적은 1비트의 유효 숫자를 절약하기 위함입니다. 32비트 부동소수점 수의 경우 M에 할당되는 비트는 23비트뿐이지만, 첫 번째 비트 1을 생략하면 24비트의 유효 숫자를 저장할 수 있습니다.

3.3 E의 정의

먼저, E는 부호 없는 정수(unsigned int)입니다. 이는 E가 8비트일 경우 범위가 0255이고, 11비트일 경우 범위가 02047이라는 의미입니다. 그러나 과학적 표기법에서 E는 음수가 될 수 있다는 것을 알고 있습니다. 따라서 IEEE 754는 메모리에 저장할 때 E의 실제 값에 중간 수를 더하도록 규정합니다. 8비트 E의 경우 이 중간 수는 127이고, 11비트 E의 경우 이 중간 수는 1023입니다. 예를 들어, 2^10의 E는 10이므로 32비트 부동소수점 수로 저장할 때 10+127=137, 즉 10001001로 저장해야 합니다.

지수 E를 메모리에서 꺼내면 세 가지 경우로 나눌 수 있습니다: 1.E가 모두 0이 아니거나 모두 1이 아닌 경우

M: 앞에 1을 추가합니다. E: 중간 값을 뺍니다.

2.E가 모두 0인 경우

M: 앞에 0을 추가합니다. E: 1-127(또는 1-1023)과 같습니다 ±0 또는 0에 매우 가까은 작은 수를 표현하기 위함입니다.

3.E가 모두 1인 경우

M: 앞에 1을 추가합니다. E: 중간 값을 뺍니다 만약 M이 모두 0이면, ±무한대를 나타냅니다.

태그: C언어 메모리 저장 정수 부동소수점 IEEE 754

6월 24일 02:23에 게시됨