Proven C BookEnglish GitHub

23 변수 선언

먼저 알아야 할 것

5장 워드와 주소 · 주소와 칸
19장 프로그램의 구조 · 선언이 놓이는 자리

돌아보기

5장에서 메모리는 번호(주소) 붙은 사물함들이라 했다. 그런데 지금까지 쓴 C 코드에는 주소가 한 번도 등장하지 않았다 — 그러면 프로그램은 사물함을 어떻게 쓰고 있었던 것인가?

답. 이름을 통해서다. C에서 변수를 선언한다는 것은 “사물함 몇 칸을 잡고, 거기에 사람이 읽을 이름을 붙이는” 일이다. 번호(주소)는 컴파일러가 관리하고, 우리는 이름으로 부른다 — 이름은 주소의 사람용 별명이다. 이 장에서 그 이름을 만드는 법을 배우고, 별명 뒤의 진짜 주소를 다시 만나는 것은 제7부(35장)다.

이 장의 필요성과 맥락

5부의 제목이 「이름을 만드는 법」인 이유가 여기서 드러난다. 4부까지는 남이 만든 이름만 썼다 — 이제 내가 만든다. 변수를 함수보다 먼저 두는 것은 함수의 매개변수와 반환값이 결국 변수이기 때문이고, 무엇보다 대입 = 를 여기서 정확히 못박아야 34장까지 이어지는 오해가 애초에 생기지 않는다.

이 장이 끝나면

제5부의 주제는 “이름을 만드는 법”이다. 그 첫걸음으로 변수를 선언한다 — 타입과 이름을 약속하고, 값을 담고, 바꾸는 것까지. 연산자 하나가 새로 합류한다: 대입(assignment) =. 그리고 이 =가 수학의 등호가 아니라는 것이 이 장의 가장 중요한 문장이다.

이 장에서 답할 질문

  1. 타입 이야기에서 int가 “약 ±21억”이라 했는데 — 정확한 크기가 기계마다 다르다는 뜻인가? 7장에서 배운 8비트, 16비트 이야기는 어디로 갔는가?

23.1 선언 — 타입, 이름, 그리고 첫 값

변수 선언의 문법은 이미 여러 번 스쳐 본 모양이다:

int apples = 12;

세 부분으로 읽는다 — 타입(int), 이름(apples), 그리고 초기화 (= 12). 뜻은: “정수를 담는 칸을 잡고, apples라 부르기로 하고, 첫 값으로 12를 담아라.”

타입은 두 가지를 약속한다. 첫째, 값의 집합int는 부호 있는 정수의 그릇으로, 흔한 기계에서 32비트, 즉 7장의 2의 보수 세계에서 약 ±21억 범위다. double이라 적으면 8장의 64비트 부동소수점 그릇이 된다. 둘째, 연산의 약속 — 그 이름에 무엇을 해도 되는지(정수 산술인지 근사 산술인지)가 타입에서 나온다. 5장에서 “덩어리를 아는 것은 읽는 쪽”이라 했는데, C에서는 타입이 바로 그 읽는 눈이다.

이름은 자유롭게 짓되 문법이 정한 틀 안에서다 — 글자·숫자·밑줄로 이루어지고 숫자로 시작할 수 없다. 좋은 이름은 문법이 아니라 사람을 위한 것이다: a보다 apples가 반년 뒤의 자신에게 친절하다.

초기화는 관행이 아니라 이 책의 규칙이다. 선언할 때 반드시 첫 값을 담아라. 초기화 없이 선언만 한 변수의 칸에는 그 자리에 우연히 남아 있던 쓰레기 비트가 들어 있고(5장 — 칸은 언제나 무언가로 차 있다), 그것을 읽는 것은 대표적인 사고 경로다. 정식 취급은 43장에서 하되, 습관은 지금부터 들인다.

23.2 대입 — 상태를 바꾸는 부수효과

선언된 변수의 값은 대입(=)으로 바꾼다. 20장의 배분표에 예고됐던 연산자가 드디어 합류하는 것이다. 시연부터:

examples/ch23/var.c

#include <stdio.h>

int main(void)
{
    int apples = 12;              /* 선언과 동시에 초기화한다 */

    printf("apples: %d\n", apples);
    apples = apples + 3;          /* 새 값 = 지금 값 + 3 */
    printf("apples: %d\n", apples);
    return 0;
}

실행 결과

apples: 12
apples: 15

셋째 문장 apples = apples + 3;을 정확히 읽는 것이 이 장의 핵심이다. 읽는 순서는 — ① 오른쪽 수식 apples + 3을 평가한다(지금 값 12를 읽어 15를 얻는다), ② 그 값을 왼쪽 이름의 칸에 담는다(원래 있던 12는 덮여 사라진다). 즉 =는 “같다”가 아니라 “오른쪽을 계산해 왼쪽에 담아라”라는 지시다. 값이 바뀌는 것 — 이것이 20장에서 예고한, 출력 말고 두 번째로 만나는 부수효과(side effect)다: 대입은 변수의 상태를 바꾼다.

흔한 오해. x = x + 1은 말이 안 되는 식이다 — 어떤 수도 자기 더하기 1과 같을 수 없으니까”

수학의 눈으로는 백번 옳다 — 등식으로 읽으면 해가 없는 식이다. 이 오개념의 뿌리는 = 기호의 재활용이다. C의 =는 등호가 아니라 담기 지시라서, x = x + 1은 “x의 지금 값에 1을 더해, 그 결과를 x에 도로 담아라” — 즉 x를 1 키우라는 멀쩡한 명령이다. 오른쪽의 x읽을 때의 값(옛 값)이고 왼쪽의 x담을 곳(칸)이라는 것 — 같은 이름이 자리에 따라 값과 장소로 다르게 읽히는 이 구별이 C 읽기의 기본기다. 참고로 “같은가?”를 묻는 진짜 비교는 다른 기호(==, 30장)가 맡는다 — 기호를 나눠 쓴 이유가 바로 이 혼동 때문이다.

23.3 const — 바꾸지 않겠다는 약속

바꿀 일이 없는 값에는 선언에 const를 붙인다:

const int max_floor = 63;    /* 이 이름의 값은 앞으로 바뀌지 않는다 */

const가 붙은 이름에 대입하려 하면 컴파일러가 오류로 막는다. 이것은 자물쇠라기보다 문서다 — “이 값은 변하지 않음”을 컴파일러와 다음 읽는 사람 모두에게 알리는 것이다. 바뀌지 않는 것이 많은 코드일수록 읽기 쉽고 사고가 적다 — 그래서 현대적 관행은 “일단 const로 선언하고, 바꿔야 할 것만 뺀다”에 가깝다. 이 책의 예제도 그 감각을 따른다.

문. 타입 이야기에서 int가 “약 ±21억”이라 했는데 — 정확한 크기가 기계마다 다르다는 뜻인가? 7장에서 배운 8비트, 16비트 이야기는 어디로 갔는가?

답. 정확한 관찰이다. int의 크기는 표준이 “최소한 이만큼”만 정하고 구체 크기는 플랫폼에 맡긴 타입이다 — 지금 주류 환경에서는 32비트가 사실상의 표준이다. 7장의 8비트·16비트 그릇들, 그리고 “크기를 정확히 못박고 싶을 때”의 타입들(int32_t 같은)은 27장에서 정수 타입 가족 전체를 소개하며 정리한다. 이 부에서는 int 하나로 충분하다 — 나선형 원칙대로, 가족 상봉은 필요해질 때 한다.

이름을 만들었고, 값을 담았고, 바꾸는 법도 배웠다. 그런데 15장부터 끌고 온 외상 장부에는 아직 두 줄이 남아 있다 — int main(void)return 0. 둘 다 함수의 문법이다. 변수에 이름을 붙였으니, 다음 장에서는 에 이름을 붙인다 — 함수를 스스로 만드는 법, 그리고 외상 장부의 완전한 청산이다.