C언어 - 변수의 자료형

1. C언어 자료형

signedunsignedshortlongchar
intfloatdoubleautoregister
staticexternconstvolatile

1.1. 정수형 부호 지정자

Integer Sign Modifiers: 정수형 크기 부호 지정자는 일반적으로 char 또는 int와 같은 정수형 타입과 결합하여 사용한다. 

  • 단독으로 사용될 수도 있으나, 그 경우 int가 생략된 형태로 해석된다.

signed
unsigned

signedunsigned는 정수형 타입이 음수를 표현할지 여부를 결정하는 키워드이다.

  • signed는 부호 비트를 사용하여 음수와 양수를 모두 표현한다.
  • unsigned는 음수를 표현하지 않고 0 이상의 값, 즉 양수만을 표현한다.

같은 1byte(8bit)라도 signed인지 unsigned인지에 따라 표현 가능한 값의 범위가 달라진다.

1.2. 정수형 크기 지정자

Integer Size Modifiers: 정수형 크기 지정자는 일반적으로 정수형 타입 int와 결합하여 사용한다. 

  • 단독으로 사용될 수도 있으나, 그 경우 int가 생략된 형태로 해석된다.

short
long

short

  • short는 정수형의 크기를 줄이기 위해 사용하는 지정자이다.

long

  • long은 정수형의 크기를 늘리기 위해 사용하는 지정자이다.
  • int보다 더 큰 범위의 정수를 표현할 수 있지만, 32비트 시스템에서는 intlong int의 크기가 같을 수 있다.

1.3. 기본 자료형

Fundamental Types: C 언어에서 정수, 실수 값을 표현하는 기본적인 내장 자료형이다.

1.3.1. 정수형

정수형은 소수점이 없는 정수 값을 표현한다. char는 문자 저장용으로 사용되지만, 정수형으로 분류되는 타입이다.

char
int
자료형 비트 수 16진수 범위 값의 범위 (10진수)
(signed) char 8bit 0x80 ~ 0x7f -128 ~ 127
unsigned char 8bit 0x00 ~ 0xff 0 ~ 255
short (int) 16bit 0x8000 ~ 0x7fff -32,768 ~ 32,767
unsigned short (int) 16bit 0x0000 ~ 0xffff 0 ~ 65,535
(signed) int 32bit 0x80000000 ~ 0x7fffffff -2,147,483,648 ~ 2,147,483,647
unsigned int 32bit 0x00000000 ~ 0xffffffff 0 ~ 4,294,967,295
long (int) 32bit* 0x80000000 ~ 0x7fffffff -2,147,483,648 ~ 2,147,483,647
unsigned long (int) 32bit* 0x00000000 ~ 0xffffffff 0 ~ 4,294,967,295

  • 일반적으로 최상위 비트(MSB, Most Significant Bit)를 부호 비트로 해석하며, 2의 보수 표현 방식을 사용해 하나의 덧셈기로 양수와 음수 연산을 처리함으로써 하드웨어를 단순화하고 연산을 일관되게 한다.

※ C90 기준에서 정수형 타입은 기본값이 signed이다. 따라서 signed를 명시하지 않아도 컴파일러는 이를 기본적으로 signed로 해석하며, 일반적으로 생략한다. shortlongint를 수식하는 지정자이므로, int는 기본값으로 간주되어 생략할 수 있다.

※ C99 이후에는 long long int가 도입되어 64비트 정수 표현이 가능하다.

자료형 비트 수 16진수 범위 값의 범위 (10진수)
(signed) long long int 64bit 0x8000000000000000
    ~ 0x7fffffffffffffff
-9,223,372,036,854,775,808
    ~ 9,223,372,036,854,775,807
unsigned long long int 64bit 0x0000000000000000
    ~ 0xffffffffffffffff
0 ~ 18,446,744,073,709,551,615

1.3.2. 실수형

실수형은 소수점을 포함한 실수 값을 표현하며, 부동소수점 방식으로 근사값을 저장한다.

float
double
자료형 저장 크기 값의 범위 (10진수) 정밀도 (10진수)
float 4 bytes 1.2E-38 ~ 3.4E+38 6 decimal places
double 8 bytes 2.2E-308 ~ 1.8E+308 15 decimal places
long double 10 bytes* 3.4E-4932 ~ 1.1E+4932 19 decimal places

  • floatdouble은 부호(Sign), 지수(Exponent), 가수(Mantissa)로 실수를 표현한다. 
  • 지수는 표현 가능한 범위를, 가수는 정밀도를 결정하며, float는 가볍고 빠른 대신 범위와 정밀도가 제한적이고 double은 더 넓은 범위와 높은 정밀도를 제공한다.

[출처] C Pocket Reference - 2003 O’Reilly

long double의 저장 크기와 범위는 플랫폼과 컴파일러에 따라 달라질 수 있다.

부동소수점 방식

부동소수점 방식은 실수를 저장할 때 소수점 위치를 자유롭게 옮기며, 숫자를 정확히 그대로 저장하지 않고 가장 가까운 이진수 값으로 근사 저장하는 방식이다.

#include <math.h>
double a = 0.1;
double b = 0.2;
if ((a + b) != 0.3) {
    /* condition is true due to floating-point precision error */
}
if (fabs((a + b) - 0.3) < 1e-9) {
    /* treated as equal */
}

  • 컴퓨터는 0.10.2정확한 값이 아니라 가장 가까운 이진수 값으로 저장한다.
    • 그래서 a + b의 실제 내부 값은 0.30000000000000004처럼 실제값에 약간 어긋난 값이 된다.
    • 그 결과, 사람이 보기에는 같아 보이는 0.3과 같다고 판단하지 못한다.

1.4. 저장 클래스 지정자

Storage Class Specifier: 저장 클래스 지정자는 변수의 수명과 접근 범위를 결정하는 키워드이다.

auto
register
static
extern

1.4.1. auto

  • 블록 내부에서 선언되는 지역 변수의 기본 저장 클래스이다.
  • 별도로 지정하지 않아도 모든 지역 변수는 기본적으로 auto이며, 일반적으로 명시하지 않는다.
    • C99 이후 auto는 사실상 의미 없는 키워드이다.

auto int x; /* same as int x; */

1.4.2. register

  • 변수를 CPU 레지스터에 저장하도록 요청하는 지정자이다.
    • 접근 속도를 빠르게 하기 위한 힌트이며, 실제로 레지스터에 저장될지는 컴파일러가 결정한다.
  • 주소 연산자(&)를 사용할 수 없다.

register int i;

※ C11 이후 대부분의 컴파일러에서는 최적화를 자동으로 수행하므로 register 키워드는 사실상 의미가 없다.

1.4.3. static

1.4.3.1. 지역 변수

  • 수명은 프로그램 전체, 스코프는 블록 내부이다.
  • 함수 호출이 끝나도 값이 유지된다.

void func() {
    static int count = 0;
    count++;
}

1.4.3.2. 전역 변수

  • 파일 내부에서만 접근 가능하게 만든다.
  • 다른 소스 파일에서 참조할 수 없다.

static int global;

1.4.4. extern

  • 다른 파일에 정의된 전역 변수를 참조할 때 사용한다.
    • 실제 저장 공간을 할당하지 않고 선언만 제공한다.

extern int shared;

1.5. 타입 한정자

Type Qualifier: 타입 한정자는 변수의 의미와 동작을 제한하는 키워드이며, const는 수정 불가, volatile은 외부 변경 가능성을 나타낸다.

const
volatile

1.5.1. const

  • const는 변수의 값을 변경할 수 없도록 제한하는 한정자이다.
    • 선언 이후에는 대입을 통해 값을 수정할 수 없다.

const int max = 10;
/* max = 20; // compilation error */

1.5.2. volatile

  • volatile은 변수의 값이 외부에 의해 변경될 있으므로, 항상 메모리에서 최신 값을 사용하게 하는 표시이다.
    • 컴파일러에게 최적화를 하지 말고, 항상 메모리에서 값을 다시 읽도록 지시한다.

volatile const int reg;

하드웨어 레지스터나 인터럽트에 의해 변경되는 변수에 사용된다.

volatile은 멀티스레드 동기화를 보장하지 않으며, atomic 연산이나 뮤텍스(mutex)를 대체하지 않는다.


2. C언어 자료형 출력

2.1. 예제

다음 예제는 여러 자료형의 크기를 출력한다.

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
 
#define SIZEOF_BITS(type) (sizeof(type) * 8)
 
int main(void)
{
    /* Character types */
    printf("%-19s : %zu byte (%zu bit)\n", "char", sizeof(char), SIZEOF_BITS(char));
    printf("%-19s : %zu byte (%zu bit)\n", "signed char", sizeof(signed char), SIZEOF_BITS(signed char));
    printf("%-19s : %zu byte (%zu bit)\n", "unsigned char", sizeof(unsigned char), SIZEOF_BITS(unsigned char));
    printf("\n");
 
    /* Boolean type */
    printf("%-19s : %zu byte (%zu bit)\n", "bool", sizeof(bool), SIZEOF_BITS(bool));
    printf("\n");
    /* Integer types */
    printf("%-19s : %zu byte (%zu bit)\n", "short", sizeof(short), SIZEOF_BITS(short));
    printf("%-19s : %zu byte (%zu bit)\n", "unsigned short", sizeof(unsigned short), SIZEOF_BITS(unsigned short));
    printf("%-19s : %zu byte (%zu bit)\n", "int", sizeof(int), SIZEOF_BITS(int));
    printf("%-19s : %zu byte (%zu bit)\n", "unsigned int", sizeof(unsigned int), SIZEOF_BITS(unsigned int));
    printf("%-19s : %zu byte (%zu bit)\n", "long", sizeof(long), SIZEOF_BITS(long));
    printf("%-19s : %zu byte (%zu bit)\n", "unsigned long", sizeof(unsigned long), SIZEOF_BITS(unsigned long));
    printf("%-19s : %zu byte (%zu bit)\n", "long long", sizeof(long long), SIZEOF_BITS(long long));
    printf("%-19s : %zu byte (%zu bit)\n", "unsigned long long", sizeof(unsigned long long), SIZEOF_BITS(unsigned long long));
    printf("\n");
 
    /* Floating-point types */
    printf("%-19s : %zu byte (%zu bit)\n", "float", sizeof(float), SIZEOF_BITS(float));
    printf("%-19s : %zu byte (%zu bit)\n", "double", sizeof(double), SIZEOF_BITS(double));
    printf("%-19s : %zu byte (%zu bit)\n", "long double", sizeof(long double), SIZEOF_BITS(long double));
    printf("\n");
 
    /* Fixed-width integer types from stdint.h */
    printf("%-19s : %zu byte (%zu bit)\n", "int8_t", sizeof(int8_t), SIZEOF_BITS(int8_t));
    printf("%-19s : %zu byte (%zu bit)\n", "uint8_t", sizeof(uint8_t), SIZEOF_BITS(uint8_t));
    printf("%-19s : %zu byte (%zu bit)\n", "int16_t", sizeof(int16_t), SIZEOF_BITS(int16_t));
    printf("%-19s : %zu byte (%zu bit)\n", "uint16_t", sizeof(uint16_t), SIZEOF_BITS(uint16_t));
    printf("%-19s : %zu byte (%zu bit)\n", "int32_t", sizeof(int32_t), SIZEOF_BITS(int32_t));
    printf("%-19s : %zu byte (%zu bit)\n", "uint32_t", sizeof(uint32_t), SIZEOF_BITS(uint32_t));
    printf("%-19s : %zu byte (%zu bit)\n", "int64_t", sizeof(int64_t), SIZEOF_BITS(int64_t));
    printf("%-19s : %zu byte (%zu bit)\n", "uint64_t", sizeof(uint64_t), SIZEOF_BITS(uint64_t));
    printf("\n");
 
    return 0;
}

Ubuntu 24.04(x86_64)

char                : 1 byte (8 bit)
signed char         : 1 byte (8 bit)
unsigned char       : 1 byte (8 bit)
bool                : 1 byte (8 bit)
short               : 2 byte (16 bit)
unsigned short      : 2 byte (16 bit)
int                 : 4 byte (32 bit)
unsigned int        : 4 byte (32 bit)
long                : 8 byte (64 bit)
unsigned long       : 8 byte (64 bit)
long long           : 8 byte (64 bit)
unsigned long long  : 8 byte (64 bit)
float               : 4 byte (32 bit)
double              : 8 byte (64 bit)
long double         : 16 byte (128 bit)
int8_t              : 1 byte (8 bit)
uint8_t             : 1 byte (8 bit)
int16_t             : 2 byte (16 bit)
uint16_t            : 2 byte (16 bit)
int32_t             : 4 byte (32 bit)
uint32_t            : 4 byte (32 bit)
int64_t             : 8 byte (64 bit)
uint64_t            : 8 byte (64 bit)

※ 자료형의 크기는 컴파일러 구현과 OS 및 아키텍처 환경에 따라 서로 다를 수 있다. → Fixed-width Integer Types 사용

2.2. Void Type

  • void는 "값이 없다"는 의미의 특수 타입이다.

함수 매개변수가 없을 때, 함수 반환 값이 없을 때 사용한다.

stdbool.h
void init(void); /* no parameters and no return value */

※ 포인터에서 void는 자료형이 정해지지 않은 범용 포인터(void *)를 의미한다.

2.3. Boolean type

C99 기준

  • <stdbool.h> 헤더에서 bool을 정의
  • true / false 매크로도 함께 제공
stdbool.h
#define bool _Bool
#define true 1
#define false 0
  • 크기는 1 byte, 내부적으로는 정수
    • _Bool는 실제로 unsigned char 정수 타입
  • 0이 아니면 참
    • 정수 값이 0이 아니면 true, 0이면 false로 해석된다.
    • bool에 0이 아닌 정수를 할당할 수 있지만, 자동으로 0 또는 1로 정규화(normalize)된다.

2.4. Fixed-width Types

플랫폼과 컴파일러에 관계없이 정수의 크기를 동일하게 보장하기 위한 용도로 사용된다.

stdint.h
typedef __int8_t int8_t;
typedef __int16_t int16_t;
typedef __int32_t int32_t;
typedef __int64_t int64_t;
typedef __uint8_t uint8_t;
typedef __uint16_t uint16_t;
typedef __uint32_t uint32_t;
typedef __uint64_t uint64_t;

Ubuntu 24.04(x86_64)

  
typedef signed char __int8_t;
typedef short __int16_t;
typedef int __int32_t;
typedef long long __int64_t;
typedef unsigned char __uint8_t;
typedef unsigned short __uint16_t;
typedef unsigned int __uint32_t;
typedef unsigned long long __uint64_t;

  • Fixed-width Integer Types는 정수형의 비트 수를 정확히 고정하기 위해 사용된다.
  • 플랫폼이나 컴파일러가 달라도 항상 동일한 크기와 범위를 보장한다.
  • 이로 인해 이식성과 바이너리 호환성이 중요한 코드에서 활용된다.