이 글은 AI(Gemini Pro)를 통한 학습 내용을 기반으로 작성하였습니다.
C언어는 철저하게 Unix 운영체제를 위해 태어난 언어이다. C언어 이전에는 어셈블리어로 Unix 운영체제가 구현되었다. 따라서, C언어 스펙(명세)의 기본 철학은 "프로그래머를 믿어라", 그리고 "기계어와 가장 가깝게"이다.(초보 코더가 어셈블리로 운영체제 코드를 짤 일은 없을 것이다. C언어는 프로그래머의 능력을 믿는다.) 이외에도 "모든 것은 파일이다"와 같은 철학들이 존재한다.
또한, 절차지향 프로그래밍(PP, Procedural Programming)과 객체지향 프로그래밍(OOP, Object Oriented Programming)을 말할 때 절차지향 프로그래밍 패러다임을 사용하는 대표적 언어가 C언어이다.
반환타입 함수이름(매개변수_타입 매개변수_이름) {
// 실행할 코드
return 반환값;
}
// 예시:
int add(int a, int b) {
return a + b;
}
입력값(매개변수)을 받아서, 코드를 실행하고, 결과값(return)을 반환한다.
C언어는 함수의 선언과 정의를 따로 분리해서 쓰는 경우가 많다. 주로 헤더 파일(.h)과 소스 파일(.c)로 나뉜다.
선언과 정의의 예시:
// 1. 선언 (이런 함수가 있다고 컴파일러에게 미리 귀띔)
int add(int a, int b);
int main() {
int result = add(3, 4); // 사용
return 0;
}
// 2. 정의 (실제 작동하는 코드 구현)
int add(int a, int b) {
return a + b;
}
함수를 main 함수 아래에 구현하려면, 반드시 main 함수 위에 '이런 함수가 있다'고 선언(프로토타입)을 해줘야 에러가 나지 않는다.
함수형 프로그래밍 언어(F#, OCaml, Scala, Haskell, Lisp, Erlang 등)를 접한 사람이라면 부수 효과(Side Effect)에 대해서 들어봤을 것이다. 함수형 프로그래밍 언어에서는 대체로 함수 스코프 안에서 함수 스코프 밖의 값을 변경하는 행위를 꺼린다. 즉, Side Effect를 만들고 싶지 않아 한다. 함수와 프로시저의 차이는 여기에서 기인한다.
파스칼(Pascal)이나 포트란(Fortran) 같은 옛날 언어들은 이 둘의 성격을 아주 엄격하게 구분했다.
- 계산해서 값을 돌려줄 거면 Function이라는 키워드로 선언해라.
- 반환값 없이 전역 변수를 바꾸거나 상태만 건드릴 거면 Procedure 키워드로 선언해라.
프로그래머가 코드를 읽을 때 "아, 이 함수는 상태를 바꾸는 위험한 동작을 하는구나"라고 바로 알 수 있도록 이런 식으로 언어 차원에서 제한을 두었다.
하지만 C언어의 창시자인 데니스 리치(Dennis Ritchie)는 철저한 '실용주의자'였다. C언어는 유닉스(Unix)라는 운영체제를 빠르고 간결하게 만들기 위해 태어났기에 문법이 복잡해지는 걸 극도로 꺼렸다.
그래서 프로시저와 함수를 나누지 않고 전부 다 함수라고 부르기로 결정을 내렸다.
그렇다면 프로시저는 어떻게 표현해야 할까? 여기서 유명한 키워드가 등장한다.
"반환 타입 자리에 '비어있음'이라는 뜻으로 void를 적어. 그럼 그게 프로시저지 뭐."
즉, C언어에서 void를 반환하는 함수는 수학적인 의미의 함수가 아니라 상태를 변화시키는 프로시저 역할을 하겠다는 선언이다.
따라서, C언어에서는 함수가 프로시저인지 함수인지에는 관심이 없다. "프로그래머를 믿는다". 프로시저인지 함수인지 판단하고 로직을 고민하는 것은 프로그래머의 몫이다.