[C언어 50강] 04강. 변수와 상수 선언 초기화, 네이밍 규칙, 스코프 맛보기

[C언어 50강] 04강. 변수와 상수 선언 초기화, 네이밍 규칙, 스코프 맛보기

변수와 상수를 배우는 시점에서 가장 중요한 건 문법 자체보다 “값이 어디에 저장되고, 언제까지 유효하며, 누가 바꿀 수 있는가”를 구분하는 사고방식입니다. C언어는 개발자가 메모리와 수명을 직접 다루는 언어이기 때문에, 여기서 기초를 단단히 잡아두면 이후 포인터·배열·구조체·동적 메모리까지 훨씬 안정적으로 이해할 수 있습니다. 이번 강의에서는 선언과 초기화, 상수화 전략, 네이밍 규칙, 스코프(유효 범위) 감각을 개념 중심으로 정리합니다.


핵심 개념

  • 변수 선언은 “이름 붙은 저장 공간을 타입과 함께 계약하는 행위”이고, 초기화는 “그 공간의 첫 값을 명시하는 행위”다.
  • 상수(const)는 단순 스타일이 아니라, 변경 가능성을 줄여 버그 표면적을 줄이는 안정성 도구다.
  • 스코프를 제대로 이해하면 이름 충돌·의도치 않은 값 변경·디버깅 난이도를 크게 줄일 수 있다.

개념 먼저 이해하기

처음 C언어를 배우면 변수 선언을 int a;처럼 문장 하나로 받아들이기 쉽습니다. 하지만 실제로는 이 한 줄이 꽤 많은 정보를 담고 있습니다. 첫째, int는 저장할 값의 해석 방식(정수), 대략적인 범위, 연산 규칙을 결정합니다. 둘째, a라는 식별자는 이 메모리 위치를 사람이 접근할 수 있게 해주는 이름입니다. 즉 선언은 “이 메모리를 어떤 규칙으로 다룰지”를 컴파일러와 약속하는 단계입니다. 이 약속이 정확해야 경고가 줄고, 타입 불일치에서 오는 오류를 조기에 막을 수 있습니다.

초기화는 선언과 자주 함께 등장하지만, 의미는 다릅니다. 선언은 공간과 타입을 확정하는 행위이고, 초기화는 그 공간에 시작값을 넣는 행위입니다. C에서 초기화를 소홀히 하면 미정의 값(쓰레기 값)을 읽게 되어 버그가 발생할 수 있습니다. “어차피 곧 값을 넣을 건데?”라는 생각으로 초기화를 미루면, 분기 로직이 복잡해질수록 초기화 누락이 숨어들기 쉽습니다. 실무에서는 가능하면 선언과 동시에 초기화하는 습관을 권장합니다. 이 습관 하나만으로도 디버깅 시간이 크게 줄어듭니다.

상수는 더 중요합니다. 많은 입문자가 상수를 “값을 못 바꾸게 하는 귀찮은 문법”쯤으로 생각하지만, 실제로는 의도 표현 도구입니다. 예를 들어 세율, 최대 버퍼 길이, 재시도 횟수처럼 변경되면 안 되는 값을 const로 선언하면, 컴파일러가 실수로 값을 바꾸는 코드를 막아줍니다. 이것은 단순한 스타일 차이가 아니라, 결함 방지 장치입니다. 즉 const는 “이 값은 정책상 불변이다”라는 팀의 약속을 코드에 박아 넣는 방법입니다.

네이밍도 가볍게 보면 안 됩니다. C는 동적 언어보다 타입 정보가 제한적이고, 도구가 의도를 완전히 추론해주지 못하는 경우가 많습니다. 그래서 이름이 곧 문서입니다. x, tmp, data2 같은 이름은 지금은 빨라도 한 달 뒤엔 비용이 됩니다. 반대로 retry_limit, total_count, is_valid처럼 역할 중심 이름을 쓰면 코드 자체가 설명서 역할을 합니다. 특히 불리언은 is_, has_, can_ 접두를 쓰면 조건문의 가독성이 크게 올라갑니다.

마지막으로 스코프는 “변수가 보이는 범위”를 넘어서 “변수가 살아있는 설계 단위”라는 관점으로 이해해야 합니다. 블록 내부에서만 쓰는 변수는 블록 안에 두고, 함수 밖 전역 변수는 정말 필요할 때만 사용해야 합니다. 스코프를 좁히면 사이드 이펙트가 줄고, 수정 영향 범위를 예측하기 쉬워집니다. 이는 C에서 특히 중요합니다. 포인터나 참조 전달이 본격화되기 전 단계라도, 스코프 설계가 약하면 프로그램이 커질수록 오류가 누적됩니다. 좋은 C 코드는 복잡한 문법보다도 “값의 소유권과 생명주기”를 명확히 표현하는 코드입니다.

요약하면, 변수와 상수는 단순 저장소가 아니라 프로그램의 상태를 구조화하는 언어적 장치입니다. 선언/초기화/상수화/네이밍/스코프는 따로 떨어진 주제가 아니라 하나의 안전망입니다. 이 다섯 가지를 함께 설계해야 유지보수 가능한 C 코드를 만들 수 있습니다.

기본 사용

예제 1) 선언과 초기화를 함께 하는 기본 패턴

#include <stdio.h>

int main(void) {
    int total_count = 0;        // 즉시 초기화
    double average_score = 0.0; // 타입에 맞는 초기값
    char grade = 'F';           // 문자 리터럴

    printf("count=%d, avg=%.1f, grade=%c\n", total_count, average_score, grade);
    return 0;
}

설명:

  • 선언과 동시에 초기화하면 “초기화 누락” 가능성을 크게 줄일 수 있습니다.
  • 타입별 초기값을 명시하면 디버깅 시 상태 추적이 쉬워집니다.
  • 출력 형식 지정자를 타입과 맞추는 습관을 초반부터 고정해야 합니다.

예제 2) 상수(const)로 정책 값 고정하기

#include <stdio.h>

int main(void) {
    const int retry_limit = 3;
    int attempt = 0;

    while (attempt < retry_limit) {
        attempt++;
        printf("attempt %d/%d\n", attempt, retry_limit);
    }

    // retry_limit = 5; // 컴파일 에러: const 객체 수정 시도
    return 0;
}

설명:

  • const는 “실수로 바뀌면 안 되는 값”을 코드 차원에서 보호합니다.
  • 정책 값과 실행 중 변하는 상태 값(attempt)을 구분하면 설계가 선명해집니다.
  • 컴파일 타임에 실수를 잡아내므로 런타임 버그를 줄일 수 있습니다.

예제 3) 스코프를 좁혀 의도를 명확히 하기

#include <stdio.h>

int main(void) {
    int total = 0;

    for (int i = 1; i <= 5; i++) {
        int current = i * 10; // current는 반복문 블록에서만 유효
        total += current;
        printf("i=%d, current=%d, total=%d\n", i, current, total);
    }

    // printf("%d\n", current); // 컴파일 에러: 스코프 밖 식별자 접근
    printf("final total=%d\n", total);
    return 0;
}

설명:

  • current를 필요한 범위에만 선언해 오염 범위를 줄였습니다.
  • 스코프가 좁을수록 이름 충돌과 의도치 않은 재사용 가능성이 낮아집니다.
  • “변수는 가능한 가장 작은 범위에서 선언”이 실무에서 매우 강력한 규칙입니다.

자주 하는 실수

실수 1) 선언만 하고 초기화하지 않은 변수 사용

  • 원인: “곧 값 넣을 예정”이라고 생각하고 지나감.
  • 해결: 선언 시 즉시 초기화 원칙을 적용하고, 컴파일 경고를 에러처럼 처리한다.

실수 2) const가 필요한 값을 일반 변수로 둠

  • 원인: 상수화의 효용을 체감하지 못함.
  • 해결: 정책 값, 제한 값, 변경되면 위험한 값은 기본적으로 const부터 검토한다.

실수 3) 의미 없는 변수명 사용

  • 원인: 빠르게 작성하려다 의도 표현을 생략함.
  • 해결: 역할 중심 이름 사용(retry_limit, user_count, is_active)을 팀 규칙으로 고정한다.

실수 4) 스코프를 과도하게 넓게 잡음

  • 원인: 함수 시작 부분에 모든 변수를 몰아서 선언하는 습관.
  • 해결: “첫 사용 지점 근처에서 선언”하고, 블록 스코프를 적극 활용한다.

실무 패턴

  • 선언 즉시 초기화: 특히 숫자/포인터/플래그 변수는 기본값을 명시한다.
  • 불변성 우선: 값이 바뀔 필요가 없다면 const를 기본 선택으로 둔다.
  • 네이밍 컨벤션 통일: snake_case 또는 팀 규칙을 일관되게 유지한다.
  • 스코프 최소화: 전역 변수는 최소화하고, 지역 변수는 필요한 범위에서만 사용한다.
  • 경고 제로 정책: -Wall -Wextra -Wpedantic 기준으로 경고를 방치하지 않는다.

오늘의 결론

한 줄 요약: 변수는 저장공간이 아니라 상태 설계의 단위이고, 초기화·상수화·이름·스코프를 함께 설계해야 C 코드가 안전해진다.

연습문제

  1. int, double, char 변수를 각각 선언하고, 선언과 동시에 초기화한 뒤 출력해보세요. 형식 지정자가 타입과 정확히 일치하는지 확인하세요.
  2. 재시도 횟수, 최대 길이 같은 정책 값을 const로 선언해 작은 반복 프로그램을 작성하세요.
  3. 같은 기능을 (a) 넓은 스코프 방식, (b) 좁은 스코프 방식으로 각각 구현한 후, 어떤 코드가 더 읽기 쉬운지 비교 설명해보세요.

이전 강의 정답

  • 3강 연습문제 1) 종료 코드 비교
    • return 0;은 성공, return 1;은 실패 의미로 관례적으로 사용합니다.
    • macOS/Linux 셸에서 실행 직후 echo $?로 종료 코드를 확인할 수 있습니다.
  • 3강 연습문제 2) 형식 지정자 경고 관찰
    • 예: double 값을 %d로 출력하면 타입 불일치 경고가 발생합니다.
    • 핵심은 “출력 결과가 우연히 맞아 보여도 코드가 안전하다는 뜻은 아니다”입니다.
  • 3강 연습문제 3) 좋은 주석 vs 나쁜 주석
    • 나쁜 주석: 코드 문장을 그대로 번역한 주석
    • 좋은 주석: 설계 의도, 제약, 경계조건, 선택 이유를 설명한 주석

실습 환경/재현 정보

  • 컴파일러: clang 17+ 또는 gcc 13+
  • 컴파일 옵션: -std=c11 -Wall -Wextra -Wpedantic -O0 -g
  • 실행 환경: macOS/Linux 터미널
  • 재현 체크:
    • clang lesson04.c -o lesson04 -std=c11 -Wall -Wextra -Wpedantic -O0 -g
    • ./lesson04
    • echo $?로 종료 코드 확인
    • const 수정 라인을 주석 해제해 컴파일 에러 확인