[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 코드가 안전해진다.
연습문제
int,double,char변수를 각각 선언하고, 선언과 동시에 초기화한 뒤 출력해보세요. 형식 지정자가 타입과 정확히 일치하는지 확인하세요.- 재시도 횟수, 최대 길이 같은 정책 값을
const로 선언해 작은 반복 프로그램을 작성하세요. - 같은 기능을 (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./lesson04echo $?로 종료 코드 확인const수정 라인을 주석 해제해 컴파일 에러 확인