C는 배울 문법이 아주 적습니다. 키워드 32개 남짓과 구두점 규칙 몇 가지가 전부죠. 앞으로 작성할 거의 모든 프로그램이 똑같은 바깥 골격을 가지므로, 그 골격을 한 번 꼼꼼히 뜯어볼 가치가 있습니다.
가장 작은 완전한 프로그램
이 프로그램은 컴파일되고, 실행되고, 아무 일도 하지 않습니다.
include도, 출력도, 형식적인 클래스나 모듈도 없습니다. main은 진입점입니다. 운영체제가 프로그램을 시작할 때 main을 호출합니다. main이 반환하면 프로그램이 끝나고, 반환한 값이 종료 상태가 됩니다.
출력을 추가하려면 include 한 줄이 필요합니다.
전처리기 지시문
#으로 시작하는 줄은 본격적인 컴파일 이전에 전처리기가 처리합니다. 이것들은 C 문장이 아니며 세미콜론을 붙이지 않습니다.
#include <stdio.h> /* 표준 라이브러리 헤더 - 꺾쇠괄호 */
#include "myutils.h" /* 직접 만든 헤더 - 따옴표, 현재 위치부터 탐색 */
#define MAX 100 /* 매크로 - 아래 모든 곳에서 MAX가 100으로 치환됨 */
#include는 말 그대로 텍스트 붙여넣기입니다. 지정한 파일의 내용 전체가 그 자리에 들어갑니다. stdio.h에는 printf의 선언이 들어 있고, 그래서 컴파일러가 printf가 어떤 인자를 받는지 알 수 있습니다. include를 빼면 implicit declaration of function 'printf' 오류를 보게 됩니다.
꺾쇠괄호와 따옴표의 차이는 컴파일러가 어디를 찾느냐의 문제입니다. <...>는 시스템 include 디렉터리를, "..."는 이 파일 옆을 먼저 보라는 뜻입니다.
main 함수
int main(void) {
return 0;
}
네 부분이 있고, 각각 의미가 있습니다.
int- main의 반환 타입입니다. 그 값은 종료 상태로 운영체제에 전달되며, 0은 성공을 뜻합니다.main- 런타임이 호출하는 예약된 이름입니다. 모든 호스티드 C 프로그램에는 정확히 하나가 있습니다.(void)- 매개변수 목록입니다. 여기서void는 "인자를 받지 않는다"는 뜻입니다. 대신main()이라고 써도 문법상 맞지만 더 약합니다. C에서 빈 괄호는 "매개변수가 지정되지 않음"을 의미해서 인자 검사가 꺼집니다.{ ... }- 함수 본문입니다.
또 다른 표준 형태는 명령줄 인자를 받습니다.
int main(int argc, char *argv[]) { ... }
필요해지기 전까지는 int main(void)를 쓰세요.
문장과 세미콜론
문장은 하나의 명령이며 세미콜론으로 끝납니다.
문장 네 개, 세미콜론 네 개입니다. 세미콜론은 구분자가 아니라 종결자입니다. 블록의 마지막 문장에도 붙여야 합니다.
세미콜론을 붙이지 않는 것들은 이렇습니다.
#include <stdio.h> /* 전처리기 줄 - 세미콜론 없음 */
int add(int a, int b) { /* 함수 정의 헤더 - 세미콜론 없음 */
return a + b;
} /* 함수의 닫는 중괄호 - 세미콜론 없음 */
if (x > 0) {
...
} /* 제어 블록의 닫는 중괄호 - 세미콜론 없음 */
누구나 한 번은 당하는 경우가 본문이 와야 할 자리에 세미콜론이 있는 것입니다.
if (score > 90); /* 버그: 이 ; 이 if의 본문 전체입니다 */
printf("Excellent!\n"); /* score가 무엇이든 항상 실행됨 */
이것은 완벽하게 합법적인 C입니다. if는 빈 문장을 제어하고, printf는 그저 순서상 다음 줄일 뿐입니다. -Wall은 이에 대해 경고하지만, 컴파일러가 막아 주지는 않습니다.
블록과 중괄호
중괄호는 문장들을 하나의 블록으로 묶으며, 문장이 와야 하는 자리 어디서든 하나의 문장으로 취급됩니다.
본문이 문장 하나뿐이면 중괄호는 선택 사항이지만, 생략하는 것이 그 유명한 버그들의 원인입니다. 나중에 한 줄을 더 넣으면 그 줄이 조용히 if 바깥으로 빠져나갑니다. 중괄호는 항상 쓰세요.
블록은 범위도 만듭니다. { ... } 안에서 선언된 변수는 닫는 중괄호에서 존재하기를 멈춥니다.
대소문자 구분과 공백
C는 어디서나 대소문자를 구분합니다. total, Total, TOTAL은 서로 다른 세 이름입니다. 모든 키워드는 소문자이므로 Int, Return, Printf는 전부 오류입니다. 그중 Printf의 오류 메시지는 특히 헷갈리는데, 컴파일러가 한 번도 본 적 없는 어떤 함수를 부르려 했다고 가정하기 때문입니다.
반면 공백은 거의 완전히 자유롭습니다. 컴파일러는 이어진 공백, 탭, 줄바꿈을 하나의 구분자로 취급합니다. 다음 코드는 유효합니다.
int main ( void )
{
printf
( "Hello\n" ) ;
return 0 ;
}
이것도 마찬가지입니다.
int main(void){printf("Hello\n");return 0;}
둘은 똑같이 컴파일됩니다. 컴파일러가 신경 쓰지 않으니 서식은 전적으로 사람을 위한 것이며, 관례적인 배치(한 줄에 한 문장, 중첩 단계마다 공백 네 칸이나 탭 하나, 여는 중괄호는 같은 줄에)를 따를 가치가 있습니다. 앞으로 읽게 될 모든 C 코드베이스가 그와 비슷한 방식을 쓸 테니까요.
공백이 정말로 중요한 곳은 두 군데입니다. 키워드나 식별자를 쪼갤 수 없고(in t는 int가 아닙니다), 일반 문자열 리터럴 안에 줄바꿈을 넣을 수 없습니다.
식별자: 이름 짓는 규칙
식별자는 여러분이 정하는 모든 이름, 즉 변수·함수·타입의 이름입니다. 규칙은 이렇습니다.
- 문자, 숫자, 밑줄로 이루어집니다.
- 숫자로 시작해서는 안 됩니다.
total2는 되지만2total은 안 됩니다. - 키워드일 수 없습니다.
int,for,return이라는 이름의 변수는 만들 수 없습니다. - 대소문자가 구분됩니다.
- 밑줄로 시작하는 이름과 밑줄이 두 개 연속으로 들어간 이름은 구현체를 위해 예약되어 있으니 직접 만들지 마세요.
int score; /* 좋음 */
int total_items; /* 좋음 - snake_case가 C의 관례 */
int _hidden; /* 합법이지만 예약됨 - 피할 것 */
int 2fast; /* 오류 */
int float; /* 오류 - 키워드 */
전체에 주석을 단 프로그램
위의 모든 내용을 한자리에 모았습니다.
여기서는 읽는 순서가 중요합니다. with_tax는 main이 사용하기 전에 정의되어 있습니다. C는 파일을 위에서 아래로 읽으므로 함수는 호출되기 전에 선언되어야 합니다. 앞쪽에 정의하거나, 파일 위쪽에 함수 프로토타입을 쓰고 정의는 뒤에 두세요.
자주 묻는 질문
C 프로그램의 기본 구조는 무엇인가요?
먼저 전처리기 지시문(#include <stdio.h>), 그다음 함수 정의가 오고, 그중 하나는 반드시 main이어야 합니다. 함수 안에서 문장은 세미콜론으로 끝나고 블록은 중괄호로 묶입니다. 가장 작은 프로그램은 #include <stdio.h>에 int main(void) { return 0; }을 더한 것입니다.
왜 그냥 main()이 아니라 int main(void)인가요?
int는 main의 반환 타입으로, 프로그램이 운영체제에 돌려주는 종료 상태입니다. (void)는 main이 인자를 받지 않는다고 명시합니다. 괄호를 비워 main()이라고 쓰는 것은 오래된 방식이며, 컴파일러에게 매개변수에 대해 아무것도 알려주지 않아 검사가 덜 이루어집니다.
C의 모든 문장에 세미콜론이 필요한가요?
모든 문장에는 필요하지만 모든 줄에 필요한 건 아닙니다. 전처리기 지시문(#include, #define), 함수 정의, 그리고 블록이나 if/for/while의 닫는 중괄호에는 붙이지 않습니다. if (x) 뒤에 실수로 붙은 세미콜론도 문법상 올바른 C이며, 본문이 조건과 무관하게 항상 실행되게 만듭니다.
C는 대소문자를 구분하나요?
네, 완전히 구분합니다. count, Count, COUNT는 서로 다른 세 개의 식별자이고, Int는 키워드 int가 아닙니다. C의 모든 키워드는 소문자이므로 Return이나 Printf처럼 대문자를 쓰면 오류가 납니다.