[C 02] 변수와 기본 자료형

프로그램은 결국 값을 다루는 일입니다. 사용자가 입력한 나이, 계산 도중의 합계, 화면에 찍을 점수 같은 값을 어딘가에 담아 두었다가 필요할 때 꺼내 써야 합니다. 그 "담아 두는 곳"이 바로 변수(variable)입니다. 변수를 상자에 비유하면, 상자마다 이름을 붙여 두고 그 안에 값을 넣었다 뺐다 하는 셈입니다. 이번 편에서는 이 상자를 어떻게 만들고, 어떤 종류의 값을 담을 수 있는지를 처음부터 하나씩 짚어 보겠습니다.

02.1 값을 담는 상자, 변수

변수를 하나 만드는 일을 선언(declaration)이라고 합니다. C에서는 상자를 만들 때 "어떤 종류의 값을 담을 상자인지"를 반드시 먼저 밝혀야 합니다. 이 종류를 자료형(data type)이라고 부릅니다. 가장 흔한 자료형은 정수를 담는 int입니다. 사과 개수를 담았다가 출력하는 짧은 프로그램을 봅시다.


#include <stdio.h>

int main(void) {
int apples = 5; /* int = 정수를 담는 상자 */
printf("사과 %d개\n", apples);
return 0;
}


실행하면 이렇게 나옵니다.


사과 5개


int apples = 5;를 한 조각씩 뜯어보겠습니다. int는 자료형, apples는 상자에 붙인 이름, = 5는 그 상자에 처음 넣을 값입니다. 이렇게 선언과 동시에 값을 넣는 것을 초기화(initialization)라고 합니다. 뒤에 붙는 세미콜론은 문장이 여기서 끝난다는 표시입니다. 그리고 printf 안의 %d는 "여기에 정수 하나를 끼워 넣어라"는 자리표시자(형식 지정자)로, 콤마 뒤의 apples 값이 그 자리에 들어가 화면에 찍힙니다.

02.2 자료형을 먼저 정해야 하는 이유

왜 굳이 상자의 종류를 미리 정해야 할까요. C는 값을 메모리(memory), 즉 컴퓨터 안의 저장 공간에 바이트(byte) 단위로 올려 둡니다. 정수와 소수와 문자는 저장되는 방식도, 차지하는 크기도 서로 다릅니다. 그래서 컴파일러는 상자의 자료형을 보고 "몇 바이트를 잡아 두고 그 바이트들을 어떻게 해석해 읽을지"를 결정합니다. 자료형은 사람 눈에 붙이는 꼬리표가 아니라, 값을 메모리에 올리고 다시 정확히 읽어 내기 위한 약속인 셈입니다.

02.3 소수를 담는 double

정수만으로는 키나 몸무게, 평균처럼 소수점이 붙는 값을 담을 수 없습니다. 소수를 담는 대표적인 자료형이 double입니다.


#include <stdio.h>

int main(void) {
double height = 175.5; /* double = 소수(실수) */
printf("키: %.1fcm\n", height);
return 0;
}


키: 175.5cm


소수 값은 %f로 출력합니다. 위처럼 %.1f라고 쓰면 소수점 아래 한 자리까지만 보여 줍니다. 보이는 자릿수를 바꾸고 싶으면 그 숫자만 고치면 됩니다. 주의. 정수 자리표시자 %d에 소수 값을 넘기거나, 반대로 %f에 정수를 넘기면 화면에 엉뚱한 값이 찍힙니다. 자리표시자와 넘기는 값의 자료형은 언제나 짝을 맞춰야 합니다.

02.4 문자 하나를 담는 char

글자 하나를 담는 상자는 char입니다. 문자를 코드에 쓸 때는 작은따옴표로 감쌉니다.


#include <stdio.h>

int main(void) {
char grade = 'A'; /* 작은따옴표: 문자 하나 */
printf("학점: %c\n", grade);
return 0;
}


학점: A


함정. 글자 하나는 작은따옴표 'A'로, 여러 글자가 이어진 문자열은 큰따옴표 "ABC"로 씁니다. 이 둘은 완전히 다른 것이라, char 상자에는 글자가 하나만 들어가므로 'A'는 되지만 "AB"는 담을 수 없습니다. 문자는 %c로 출력합니다. 덧붙이면, char에 실제로 담기는 것은 글자 모양이 아니라 그 글자에 매겨진 번호라, 'A'는 내부적으로 65라는 정수로 저장됩니다. 그래서 char는 아주 작은 정수를 담는 상자이기도 합니다.

02.5 여러 상자를 한꺼번에 쓰기

변수는 필요한 만큼 여러 개 만들 수 있습니다. 상품의 가격과 재고를 따로 담아 함께 출력해 보겠습니다.


#include <stdio.h>

int main(void) {
int price = 3000;
int stock = 12;
printf("가격 %d원, 재고 %d개\n", price, stock);
return 0;
}


가격 3000원, 재고 12개


printf에 자리표시자를 여러 개 두면, 콤마 뒤에 나열한 값들이 왼쪽부터 순서대로 끼워집니다. 첫 %d 자리에 price가, 둘째 %d 자리에 stock이 들어갑니다. 주의. 자리표시자 개수와 넘기는 값의 개수가 어긋나면, 컴파일이 되더라도 실행 중에 이상한 값이 찍히거나 정의되지 않은 동작(undefined behavior, 표준이 결과를 규정하지 않아 무슨 일이든 일어날 수 있는 상태)으로 이어집니다. 개수와 순서를 눈으로 맞춰 보십시오.

02.6 값은 언제든 바뀝니다

상자라는 이름처럼, 변수에 담긴 값은 프로그램이 도는 도중에 얼마든지 새 값으로 바꿀 수 있습니다. 게임 점수가 0에서 100으로 바뀌는 상황을 보겠습니다.


#include <stdio.h>

int main(void) {
int score = 0;
printf("시작 점수: %d\n", score);
score = 100; /* 값을 새로 덮어씀 */
printf("현재 점수: %d\n", score);
return 0;
}


시작 점수: 0
현재 점수: 100


여기서 =는 수학의 "양쪽이 같다"가 아니라 "오른쪽 값을 왼쪽 상자에 넣어라"라는 명령입니다. score = 100;score가 원래 무엇이었든 상관없이 그 자리를 100으로 덮어씁니다. 함정. 초기값을 넣지 않고 int score;라고만 선언한 뒤 곧바로 그 값을 읽으면, 상자 안에는 이전에 다른 용도로 쓰이다 남은 쓰레기 값(garbage value)이 들어 있습니다. 이렇게 초기화하지 않은 변수를 읽는 것 역시 정의되지 않은 동작이라, 실행할 때마다 다른 값이 나올 수 있습니다. 값을 읽기 전에 반드시 먼저 넣어 두십시오.

02.7 상자마다 다른 크기

자료형마다 메모리에서 차지하는 크기가 다릅니다. sizeof 연산자로 그 크기를 바이트 단위로 직접 확인할 수 있습니다.


#include <stdio.h>

int main(void) {
printf("int 크기: %zu 바이트\n", sizeof(int));
printf("double 크기: %zu 바이트\n", sizeof(double));
printf("char 크기: %zu 바이트\n", sizeof(char));
return 0;
}


일반적인 64비트 PC에서는 이렇게 나옵니다.


int 크기: 4 바이트
double 크기: 8 바이트
char 크기: 1 바이트


대체로 int는 4바이트, double은 8바이트, char는 1바이트입니다. 크기 값은 %zu라는 자리표시자로 출력하는데, sizeof가 돌려주는 값의 자료형에 맞춘 것이니 크기를 찍을 때는 %zu를 쓴다고 알아 두면 됩니다. 함정. 상자 크기는 곧 담을 수 있는 값의 한계이기도 합니다. int가 감당하는 정수 범위(대략 마이너스 21억에서 플러스 21억)를 넘어서면, 값이 넘쳐 엉뚱한 음수로 뒤집히는 오버플로(overflow)가 일어납니다. 아주 큰 정수를 다뤄야 할 때는 long long 같은 더 큰 상자를 골라 써야 합니다.

변수는 이름을 붙인 상자, 자료형은 그 상자가 담을 값의 종류이자 메모리에서의 크기입니다. 정수는 int, 소수는 double, 글자 하나는 char에 담고 각각 %d, %f, %c로 출력한다는 짝만 익혀도 값 다루기를 시작할 수 있습니다. 위 예제들을 직접 쳐 보고, 상자 이름과 담는 값을 바꿔 가며 출력이 어떻게 달라지는지 확인해 보시기 바랍니다.