변수는 프로그램이 읽고 바꿀 수 있는 값을 담는, 이름 붙은 메모리 조각입니다. C에서는 어떤 종류의 값을 담을지 항상 미리 밝혀야 하고, 그 선택은 변수가 살아 있는 동안 바뀌지 않습니다.
변수 선언하기
형태는 타입, 이름, 세미콜론입니다.
int age;
double price;
char grade;
각 줄은 해당 자료형에 맞는 메모리를 잡고 거기에 이름을 붙입니다. 아직 아무것도 저장되지 않았습니다. 공간은 존재하지만, 그 안의 내용은 앞서 실행된 무언가가 남겨 둔 값입니다.
같은 순간에 값을 줄 수도 있고, 이를 초기화라고 합니다.
형식 지정자를 눈여겨보세요. int에는 %d, double에는 %f, char에는 %c입니다. printf는 받은 값을 들여다보지 않습니다. 형식 문자열을 그대로 믿기 때문에, 짝이 맞지 않으면 오류가 아니라 엉뚱한 값을 출력합니다.
선언 후 대입
변수가 존재하고 나면 =로 바꿉니다.
C에서 =는 같다는 뜻이 아니라 대입입니다. score = score + 10은 "score에 있는 값을 가져와 10을 더하고, 그 결과를 다시 score에 넣어라"로 읽힙니다. 비교 연산자는 ==이고, 이 둘을 헷갈리는 것은 불리언 페이지에 따로 절이 있을 만큼 흔한 버그입니다.
score += 10이라는 축약형은 더 적은 글자로 같은 일을 합니다. 복합 대입 연산자는 모든 산술 연산에 대해 제공됩니다.
초기화하지 않은 변수의 함정
이것은 C에서 초보자가 가장 많이 만드는 단 하나의 버그이고, 오류를 내지도 않습니다.
실행해 보세요. 정답인 15가 나올 수도 있습니다. 거대한 임의의 숫자가 나올 수도 있습니다. 두 번째 실행이나 다른 컴퓨터에서는 또 다른 값이 나올 수도 있습니다. 이 모두가 "올바른" 동작입니다. 초기화하지 않은 지역 변수를 읽는 것은 미정의 동작이고, C 표준은 무슨 일이 일어날지에 대해 아무 요구도 하지 않기 때문입니다.
많은 언어와 달리 C는 지역 변수를 0으로 채워 주지 않습니다. 지역 변수는 스택의 한 조각이고, 스택은 앞선 함수 호출들이 남긴 찌꺼기로 가득합니다. 그 찌꺼기가 우연히 0일 때도 있어서 이 버그는 더욱 위험합니다. 테스트에서는 잘 되다가 운영에서 터지는 일이 잦습니다.
고치는 데는 한 글자면 됩니다.
습관으로 만드세요. 모든 변수는 선언하는 자리에서 초기화합니다. 정말로 아직 마땅한 값이 없다면 0이나 NULL이 쓰레기 값보다 낫습니다. 그리고 -Wall로 컴파일하세요. 대부분의 경우를 잡아 줍니다.
warning: 'total' is used uninitialized in this function
전역 변수와 static 변수는 예외로, 자동으로 0으로 초기화됩니다. 거기에 기대도 괜찮지만, 그래도 = 0을 적어 두면 의도가 드러납니다.
한 번에 여러 개 선언하기
같은 타입의 변수들은 초기화자가 있든 없든 한 선언을 공유할 수 있습니다.
이것은 합법이고 x, y, z처럼 긴밀히 연관된 값에는 흔합니다. 동시에 고전적인 오독이 사는 자리이기도 합니다.
int a = 0, b = 0, c = 0; /* 셋 다 0 */
int d, e, f = 0; /* f만 0 - d와 e는 쓰레기 값 */
초기화자는 줄이 아니라 이름 하나에 붙습니다. 값들의 의미가 서로 다르다면 각각 자기 줄을 주세요. 읽기도 좋고 diff도 깔끔합니다.
포인터를 한 줄에 선언하면 같은 함정이 더 날카롭게 나타납니다.
int *p, q; /* p는 int 포인터, q는 평범한 int */
*는 타입이 아니라 이름에 붙습니다. 포인터는 한 줄에 하나씩 선언하세요.
이름 규칙과 관례
컴파일러의 규칙은 이렇습니다.
- 영문자, 숫자, 밑줄만 가능합니다.
- 숫자로 시작할 수 없습니다.
total2는 되지만2total은 안 됩니다. - C 키워드일 수 없습니다.
int,return,for,double이라는 이름의 변수는 없습니다. - 대소문자를 구분합니다.
count,Count,COUNT는 서로 다른 세 변수입니다. - 밑줄로 시작하거나 밑줄 두 개를 포함하는 이름은 구현을 위해 예약되어 있습니다. 만들지 마세요.
int itemCount; /* 합법 */
int item_count; /* 합법 - C에서 흔한 스타일 */
int _count; /* 합법이지만 예약됨 - 피할 것 */
int item-count; /* 오류: '-'는 빼기 연산자다 */
int 3rd_place; /* 오류: 숫자로 시작한다 */
int float; /* 오류: 키워드 */
대부분의 C 코드가 따르는 관례는 이렇습니다. 변수와 함수에는 snake_case, 매크로 상수에는 UPPER_CASE, 그리고 타입이 무엇인지가 아니라 값이 무엇을 뜻하는지 말해 주는 이름입니다. t보다 elapsed_ms가, n2보다 customer_count가 낫습니다.
스코프가 아주 좁을 때는 짧은 이름도 괜찮습니다. 반복 카운터 i는 누구나 이해하고, for (int loop_iteration_index = 0; ...)을 원하는 사람은 없습니다.
변수가 사는 곳
변수를 어디에 선언하느냐가 누가 볼 수 있고 얼마나 오래 사는지를 결정합니다. 이것이 스코프이고, 짧게 요약하면 이렇습니다.
변수는 선언된 지점부터 그것을 담은 블록의 닫는 중괄호까지 보입니다. 전역 변수는 프로그램 전체 동안 살아 있고 어디서나 보입니다. 편리하게 들리지만 그래서 문제를 일으킵니다. 어떤 함수든 바꿀 수 있으므로, 누가 바꿨는지 추적하려면 파일 전체를 뒤져야 합니다. 지역 변수를 선호하고, 값은 매개변수로 함수에 넘기세요.
어디서나 선언하기 (C99 이후)
원래의 C89에서는 블록 안의 모든 선언이 어떤 문장보다도 앞에 와야 했습니다.
int main(void) {
int i, sum = 0; /* 선언이 먼저 */
printf("Starting\n");
for (i = 0; i < 10; i++) { sum += i; }
return 0;
}
C99부터는 값이 처음 필요한 자리에서 선언할 수 있고, for 헤더 안도 포함됩니다.
이 쪽이 더 좋은 스타일입니다. 선언이 첫 사용 바로 옆에 놓이고, i가 함수의 나머지로 새어 나갈 수 없습니다. -std=c17(또는 C99 이상 설정)로 컴파일하면 어디서든 동작합니다.
자주 묻는 질문
C에서 변수는 어떻게 선언하나요?
타입, 이름, 세미콜론 순서로 씁니다. int age;처럼요. 같은 자리에서 값을 줄 수도 있는데(int age = 30;) 이를 초기화라고 하며, 거의 항상 이 쪽이 바람직합니다.
변수 선언과 초기화의 차이는 무엇인가요?
선언은 공간을 잡고 이름을 붙이는 것입니다(int count;). 초기화는 그 변수가 만들어지는 순간 값을 넣는 것입니다(int count = 0;). 선언만 하고 초기화하지 않은 지역 변수에는 그 주소에 이미 있던 바이트가 그대로 들어 있고, 그것을 읽는 것은 미정의 동작입니다.
C의 변수 이름 규칙은 무엇인가요?
영문자, 숫자, 밑줄만 쓸 수 있고, 숫자로 시작할 수 없으며, int나 for 같은 키워드일 수 없습니다. 대소문자를 구분하므로 total과 Total은 다른 변수입니다. 밑줄로 시작하는 이름은 예약되어 있으니 피하세요.
C 함수 안 어디서든 변수를 선언할 수 있나요?
C99부터는 가능합니다. 처음 필요한 바로 그 자리에서 선언하세요. 원래의 C89 표준에서는 모든 선언이 블록 맨 위에 와야 했고, 그래서 오래된 코드는 실제 작업 전에 선언이 벽처럼 쌓여 있습니다.