[만화가 있는 C] 2. 수학함수를 C 함수로

2. 수학 함수를 C 함수로

  C는 함수(function)들의 집합입니다. 비록 약간의 차이는 있지만, C의 함수는 수학(mathematics)에서 사용하는 함수의 개념과 유사합니다. 그러므로 C로 프로그램을 코딩(coding)한다는 것은, 프로그램에서 필요로 하는 함수를 만들어 주는 것을 의미합니다. 그러므로 독자들은 수학함수가 주어졌을 때 이를 C함수로 바꿀 수 있어야 합니다.

  다음과 같은 4개의 수학함수가 주어졌을 때 이를 C의 함수로 바꾸는 과정을 살펴봅시다.

(1) f(x)=x2+5

(2) g(x,y)=f(x)+y

(3) y=3

(4) l(x,y)={원점에서 (x,y)까지 선을 그린다}

  먼저, 필요한 용어들을 정의합시다. 함수 f(x)=x2+5 에서 f는 함수 이름(function name)입니다. 함수가 받는 파라미터(parameter)는 x인데, 함수 이름 다음에 괄호를 써서 나타냅니다.

파라미터의 개수가 2개 이상일 때는 파라미터 리스트(parameter list)라 하는데, 콤마(,)로 구분하여 열거합니다. 예를 들면 x,y,z 처럼 나타냅니다.

  함수가 하는 일은 등호(=) 다음에 정의하는데, 이를 함수 몸체(function body)라고 합니다. =의 의미는 =의 왼쪽에 있는 f(x)와 =의 오른쪽에 있는 x2+5가 같다는 의미입니다. 그러므로 f(x)라고 써야 할 자리에 x2+5를 적어도 됩니다. 예를 들면, f(2)는 22+5를 의미합니다.

  특별하게 함수를 정의(define)하는 쪽의 파라미터를 형식 파라미터(formal parameter)라고 합니다. 이것은 실제의 값(value)은 모르지만 그 값을 나타내는 형식적인 표기로 적어 놓은 변수임을 나타냅니다. 예를 들어 사용자가 f(2)처럼 함수 f를 호출(call)하면 형식 파라미터 x의 실제 값은 2가 되어 함수의 몸체가 평가(evaluate)됩니다.

정의된 함수를 사용use하는 것을 ‘함수를 호출call한다’라고 합니다.

x=2

y=3

i=f(y)

  위와 같은 일련의 문장이 실행되었을 때, i의 값은 얼마일까요?

‘f(x)=x2+5 이며, x의 값은 2이므로 i는 9입니다.’

  위의 문장처럼 계산해서는 안 됩니다. f를 호출할 때 형식 파라미터 x의 값은 y의 값인 3으로 치환(substitution)되어 평가되므로, 결과는 14가 되어 i의 값은 14가 됩니다. 이때 실제 x에 치환된 값을 실제 파라미터(actual parameter)라고 합니다.

  i가 14가 되는 것에 주의하세요. 함수 f가 값을 돌려주지(return) 않는다면 i는 값을 가질 수 없습니다. C에서는 문장(statement)이 값을 가질 수 도, 가지지 않을 수도 있는데, 값을 가지는 문장을 표현식(expression)이라고 합니다. 예를 들면 다음은 표현식입니다.

3+2

다음은 표현식이 아닙니다.

if( 3+2 )

  우리는 실제 파라미터와 형식 파라미터의 차이를 명확히 이해하고 있어야 합니다. 파라미터를 전달받는 쪽 – 정의하는 쪽 – 에서 선언한 변수를 형식 파라미터라고 합니다. 반면에 파라미터를 전달하는 쪽 – 호출하는 쪽 – 에서 사용한 변수를 실제 파라미터라 합니다.

  그렇다면 독자들은 첫 번째 규칙을 이해할 준비가 되었습니다.

“Formal parameter와 actual parameter는 변수의 이름이 달라도 됩니다.”

  그러면 차례대로 위의 함수들을 C의 함수로 바꾸는 과정을 살펴봅시다.

f(x)=x2+5

  1단계. 함수의 이름과 파라미터를 그대로 써 줍니다.

f(x)=x2+5 → f(    x)

  2단계. 함수의 몸체 부분을 여는 브레이스(open brace: {)와 닫는 브레이스(close brace: {)안에 써 줍니다.

여는 브레이스와 닫는 브레이스 안에 포함된 일련의 문장들을 – 브레이스를 포함하여 – 블록(block)이라고 합니다.

f(x)=x2+5 → f(    x) { x*x+5 }

  C에는 제곱(power of 2) 연산자(operator)가 없으므로 x*x로 표현합니다.

컴퓨터공학에서는 소문자 x와 곱하기 ×를 구분하기 위해 곱하기 대신에 *(asterisk)를 사용합니다.

  그리고 보통의 C프로그래머들은 보기 좋게 위의 C함수를 다음과 같이 나타냅니다. 즉 다른 줄에 나타내어 읽기 좋게 합니다.

의미 있는 문자 – 이를 토큰(token)이라 한다 – 사이에는 몇 개의 공백(space) 문자, 탭(tab) 문자, 줄 바꿈(return) 문자가 와도 상관이 없습니다. 이들 3개의 문자(실제는 몇 개의 문자가 더 있다)를 흰 공백(white space)이라 하는데, 이러한 규칙을 ‘토큰 사이의 흰 공백은 무시한다.’ 고 합니다.


    f(    x)
    {
        x*x+5
    }

  3단계. 함수가 리턴(return)하는 값이 무엇인지를 명시(specify)합니다.

  위와 같은 수학함수 f가 정의 되어있을 때 14라고 하는 것과 f(3)이라고 하는 것은 표현만 다를 뿐 전부 수(number) 14를 나타냅니다. 14 란 표현은 그렇다 치고 f(3)은 왜 14일까요?

  그것은 f(3)이 14란 값을 리턴(return)하기 때문에 그렇습니다. 일반적으로 수학 함수는 값을 리턴합니다. 하지만 C 함수 중에는 값을 리턴하지 않는 것도 있습니다. 그래서 함수가 값을 리턴한다면 ‘리턴한다’ 라는 특별한 표현을 해 주어야 합니다. 이것은 특별하게 예약된 단어(reserved word) return을 사용해 나타냅니다.


    f(    x)
    {
        t=x*x+5
        return t
    }

  새로운 t란 변수(variable)를 왜 도입했는지 알겠는가요? return 문장 다음엔 함수가 리턴해야 할 값이 와야 하는데, 계산 결과를 잠시 저장할 변수가 필요하므로 t를 도입했습니다. 보통 return 문장은 함수의 마지막에 위치하지만, 항상 그런 것은 아닙니다.

  여기서 C의 중요한 규칙 한 가지를 더 짚고 넘어갑시다. 우리는 국어의 한 문장(statement)이 끝났다는 것을 어떻게 아는가요? ‘마침표를 보고’란 대답은 틀린 대답입니다. 우리는 우리가 배운 많은 경험과 단어 등을 통해 마침표가 없어도 문장의 끝을 알 수 있습니다. 그렇다면 컴퓨터는 – 정확하게 말하면 컴파일러(compiler)는 – 문장의 끝을 우리처럼 알 수 있을까요? ‘모릅니다’. 그래서 우리는 “한국 언어(Korean language)”가 아닌 “C 언어”를 통해 표현할 때 컴퓨터가 문장의 끝을 알 수 있도록 특정한 표현을 해 주어야 합니다.

우리가 작성한 문장을 기계가 알 수 있는 형태로 번역하는 소프트웨어를 컴파일러라고 합니다. 그러므로 C 컴파일러는, C언어를 이용하여 작성한 문장을 기계어(machine language)로 바꾸는 작업을 합니다.

한국 언어 같은 언어를 자연어(natural language)라 하고, C언어 같은 언어를 형식 언어(formal language)라 합니다. 형식 언어는 정확한 문법에 의해 표현하여야 기계가 인식할 수 있는 언어입니다. 자연어를 이해하는 로봇이나 컴퓨터는 향후 개발될 것입니다. 하지만, 앞으로도 계속, 프로그래밍은 형식 언어를 사용하게 될 것입니다.

“C언어에서 문장의 끝은 세미콜론(semicolon: ;)으로 나타냅니다.”

  문장에는 어떤 것이 있을까요?. return 도 문장이고, 수식(numerical expression)도 문장입니다. 뒤에 배우게 될 변수의 형 선언(variable type declaration)도 문장입니다. 이 외에도 문장은 몇 가지가 더 있습니다. 문장의 종류에 너무 당황할 필요는 없습니다. 문장은 몇 개의 단어와 간단한 규칙을 통해 만들어집니다.


    f(    x)
    {
        t=x*x+5;
        return t;
    }


  4단계. 파라미터 변수와 함수 안에서 사용된 변수의 형을 선언합니다.

  독자들은 3장의 ‘이진수’를 통해서 같은 숫자가 여러 비트bit를 사용하여 다양하게 표현될 수 있다는 사실을 알게 될 것입니다. 함수 f가 2를 파라미터로 받았을 때 2를 메모리에 저장하기 위해 몇 비트를 사용할까요? 우리는 이 사실을 컴파일러에게 알려 주어야 합니다. 실제로 이러한 ‘알려줌’은 비트의 수(number)뿐만 아니라, ‘무슨 수를 어떻게 저장한다’라는 정보도 포함합니다. C에서 모든 변수는 사용하기 전에 알려주어야 하는데 이것을 변수의 형 선언(type declaration)이라고 합니다. 그러므로 형 선언은 반드시 변수가 사용되기 전에 해 주어야 합니다.

  그러므로 위의 경우 파라미터 x의, 그리고 임시 변수 t의 형 선언을 해야합니다.


    f(int x)
    {
        int t;
        t=x*x+5;
        return t;
    }

  int는 ‘변수가 메모리를 4바이트 차지하면서, 표현은 정수(integer)만이 허용된다’라는 의미입니다. 독자들이 int 형으로 선언된 변수에 4바이트 이상의 정수나, 정수가 아닌 다른 형태의 수를 대입하려고 하면 컴파일러는 에러를 발생시킵니다.

정수(integer)의 약자입니다. C의 초기 설계에서 int는 기계의 워드word크기에 대응하는 것이었습니다. 그러므로 16bit 운영체제인 DOS에서는 int는 2바이트를 의미했으며, 32bit 운영체제인 Windows95에서는 int는 4바이트를 의미했습니다. 그 이후 64bit 운영체제인 Windows7에서는 소스코드의 호환문제로 인해서 int는 4바이트로 고정되게 되었습니다.

  파라미터의 형 선언과 임시변수의 형 선언이 조금 다릅니다. 일반적으로 변수의 선언은 문장이기 때문에 ;을 포함합니다. 하지만 파라미터 리스트는 문장이 아니므로 ;을 적어서는 안 됩니다.

  우리는 C언어의 중요한 규칙 한 가지를 더 알게 되었습니다.

“변수는 사용하기 전에 반드시 선언해 주어야 합니다.”

  5단계. 함수의 리턴형을 선언합니다.

  위의 함수 f가 다음과 같은 수식의 중간에 사용되었다고 합시다.


    float■ i;
    i=f(1)+1.0;

float는 4바이트 실수(real)형을 의미합니다. 이것의 이름이 real이 아니고, float인 이유는 데이터형을 이야기할 때 자세히 다룹니다.

  위의 코드(Code)를 기계어로 바꾸기 위해 컴파일하는 과정을 살펴봅시다. 컴파일러는 함수가 리턴하는 값을 잠시 저장하기 위해 메모리를 사용할 것입니다. 그렇다면 f(1)이 리턴하는 값을 위해 어떤 형을 사용해야 할지 컴파일러가 어떻게 알 수 있을까요? 이것 역시 우리가 미리 컴파일러에게 알려 주어야 합니다. 이것은 변수 선언과 의미가 같습니다. 함수의 리턴 형은 함수 이름 앞에 명시합니다. 이것을 함수의 리턴형 선언(return type declaration)이라고 합니다.


    int f(int x)
    {
        int t;
        t=x*x+5;
        return t■;
    }

함수의 리턴 형이 int이므로 t가 int형 이라야 합니다.

  이제 모든 단계를 다 마쳤습니다. 이로써 수학 함수를 완전한 C함수로 바꿀 수 있게 되었습니다.


아규먼트(argument)와 파라미터(parameter)

  아규먼트와 파라미터는 엄격한 의미에서 다릅니다. 아규먼트는 함수를 호출하는 쪽에서의 실 인자(actual parameter)를 가리키는 말이며, 파라미터는 함수를 정의하는 쪽에서의 형식 인자(formal parameter)를 가리키는 말입니다. 하지만, 이 책에서는 아규먼트와 파라미터를 동일하게 취급할 것이며, 구분이 필요하다면 실 인자(actual parameter) 혹은 형식 인자(formal parameter)라는 용어를 사용할 것입니다.


...
void F(int k) {
    //어떤 일을 함
    //재미있는 일도 함.
    //k와 관계된 어떤 일도 함
}
...
void main() {
    int i=10;
    F(i);
}
..

  위의 예에서 main()의 F(i)에서 i의 값인 10을 – 의미가 명확하다면 i를 – 실 인자라고 합니다(이 값이 실제로 전달됩니다). 함수를 정의하는 쪽에서의 k를 형식 인자라고 합니다(실 인자를 받기 위해 형식적으로 적어둔 변수입니다).


관례(convention)

  규칙은 아니지만 ‘일반적으로 그렇게 하는 것’을 관례라고 합니다. 관례를 지키지 않는다고 해서, 컴파일 시간 에러가 발생하는 것은 아닙니다. 하지만, 코드는 사람이 읽으면서 작업하는 것이므로 관례를 지키는 것은 중요합니다.

  우리가 위에서 사용한 몇 개의 단어 중, int, return등은 언어에 의해서 미리 정의된 예약어(reserved word)입니다. 이러한 예약어를 키워드(keyword)라고도 합니다. 하지만, 변수 이름 x, t나, 함수 이름 f등은 우리가 – 규칙에 맞다면 – 마음대로 정할 수 있습니다. 이러한 사용자에 의해서 정의되는 단어를 명칭(identifier)이라고 합니다. 독자들은 몇 개의 예약어가 있는지, 명칭은 어떻게 정하는지 궁금할 것입니다. 하지만 걱정할 필요는 없습니다. 영어라는 언어는 수십만 개의 단어가 있지만, C라는 언어에는 수십 개의 단어밖에 존재하지 않습니다.

  명칭은 마음대로 정해도 되므로, 위의 함수 f는 아래와 같이 써도 같은 역할을 합니다.

뒤에 정규 표현(regular expression)을 이야기 할 때, 명칭을 정의하는 법을 다루겠습니다.


    int MyFirstFunction(int parameterX)
    {
        int temporary;
        temporary=parameterX*parameterX+5;
        return temporary;
    }

  함수 이름이나 변수 이름은 나중에 봐도 알기 쉽도록 하는 것이 좋습니다. 첫 번째 관례는 다음과 같습니다.

“함수의 이름과 변수의 이름을 정할 때 나중에 봐도 알기 쉽고 이해하기 쉽도록 이름을 정합니다.”

  또한 사용자 함수 이름은 대문자로 변수 이름은 소문자로 시작하도록 정하도록 합시다. 변수 이름을 정할 때, 변수의 역할을 이해하기 쉽도록 접두어(prefix)를 붙이는데, 예를 들면, 아래와 같습니다.

nFileOpened

xCurrent

  n은 수(number)를 의미하고, x는 x-좌표를 의미한다면, 사용자는 위의 문장을 보고, 각각이 파일의 열려진 개수와 현재의 x좌표를 나타내는 변수임을 쉽게 알 수 있습니다.

이러한 표기법을 처음 사용한 마이크로소프트의 한 프로그래머의 국적을 따서 헝가리식 표기법(hungarian notation)이라고 합니다. 최근의 객체 지향언어에서는 이러한 구분을 무시하는 경향도 있습니다. 왜냐하면, 객체를 다룰 때 이보다 하위 개념인 데이터타입이 노출된다면, 객체의 정보 은닉이 위반된다고 생각하기 때문입니다. 이것은 C#이나 Java같은 좀 더 시스템에 독립적인 언어에서는 타당한 주장이라고 생각합니다.

  읽기 쉽게(readability) 하기 위해

“변수의 선언 문장과 실행 문장 사이를 한 줄 띄웁니다”


    int f(int x)
    {
        int t;
        t=x*x+5;
        return t;
    }

  들여쓰기(indentation)를 하면 후에 설명할 제어문(control statement)등에서 문장의 포함관계를 쉽게 알 수 있습니다.

“들여쓰기를 합니다.”


    int f(int x)
    {
        int t;

        t=x*x+5;
        return t;
    }

“한 문장은 되도록이면 한 줄에 적습니다.”

int f(int x){int t;t=x*x+5;return t;}

  즉 위와 같이 적어도 무방합니다. 블록 구조를 나타내기 위해 쓰인 여는 브레이스 { 가 하는 일 없이 한 줄을 차지하고 있는 게 보기 싫다면 다음과 같이 바꾸는 것도 좋습니다. 하지만 닫는 브레이스는 블록의 끝을 나타내기 위한 시각적인 역할을 하므로 그대로 둡니다.


    int f(int x) {
        int t;

        t=x*x+5;
        return t;
    }


이젠 할 수 있다!

  이제 나머지 함수들을 C 함수로 바꿀 준비가 되었습니다. 두 번째 함수를 바꾸어 봅시다.

g(x,y)=f(x)+y

바꾼 결과는 다음과 같습니다.


    int g(int x, int y) {
        int t;

        t=f(x)+y;
        return t;
    }

이렇게 함수의 몸체를 정의(define)할 때는 이미 정의된 다른 함수를 사용 – 호출 – 할 수 있습니다. 위에서 f()가 정의되었으므로 위의 함수 g() 정의는 타당합니다. 하지만 아직 정의되지 않은 함수 h()는 호출할 수 없습니다.

g() 정의 후에 h()가 정의되었다고 해도, g() 입장에서는 h()가 정의된 것이 아닙니다. 물리적인 순서 – 줄 번호 – 는 ‘정의되었음’의 판단 기준입니다.


    int g(int x, int y) {
        int t;

        t=f(x)+y;
        return t;
    }

  파라미터가 2개 이상일 때는 콤마(comma: ,)로 구분을 합니다. 이것을 파라미터 리스트(parameter list)라고 합니다.

Q. 함수의 헤더(Header) 부분을 int g(int x,y)라고 하면 안될까요?

함수의 정의에서 몸체를 제외한 부분을 머리header 혹은 원형(prototype)이라 합니다. 머리는 선언 지정자(declare specifier)와 선언자(declarator)로 나뉩니다. 아래 그림을 참고하세요.


선언 지정자와 선언자로 구성되는 함수의 헤더

A. 안 됩니다. 이유는 안 되도록 정했기 때문입니다. 즉 C언어를 설계한 사람들이 정한 문법에 의해 그러한 정의는 허락되지 않습니다.

Q. 다음은 가능한가요?


    int x;
    x=3+5;
    int t;
    t=4*10;

A. 불가능합니다. 선언문장은 반드시 블록의 처음에 모여 있어야 합니다. 다음과 같이 고쳐야 합니다. 하지만 C++에서는 위의 문장이 가능합니다. 이러한 C++의 규칙은 다음과 같습니다.

“변수는 선언하고 싶을 때 선언합니다.”


    int x;
    int t;

    x=3+5;
    t=4*10;

  파라미터 선언과는 달리 임시 변수 선언은 형 이름이 한번만 와도 됩니다. 즉 위의 프로그램은 다음과 같이 표현해도 됩니다. 타입을 한번만 적고 여러개의 변수를 선언하는 아래와 같은 방법은 포인터 선언등에서 코드를 읽기 어렵게 만들므로 권장하지는 않습니다.


    int x,t;
    x=3+5;
    t=4*10;

  3번째 함수를 바꾸어 봅시다.

y=3

바꾼 결과는 아래와 같습니다.


    int y(void) {
        return 3;
    }

  이렇게 파라미터가 없다라는 표현을 void로 합니다. C++에서는 파라미터 리스트가 void인 경우는 생략해도 되지만, 엄격한 의미에서는 다릅니다. 이것은 다른 곳에서 살펴보겠습니다.

  4번을 바꾸어 봅시다. 4번 함수는 값을 리턴하지 않습니다.

l(x,y)={원점에서 (x,y)까지 선을 그린다}

  바꾼 결과는 다음과 같습니다.


    void l(int x,int y) {
        line(0,0,x,y)■;
    }

line()은 그래픽의 그리기 함수라고 가정합니다. 이렇게 미리 만들어진 함수(built in function)중, 모든 컴파일러에서 지원하도록 규정된 함수들의 모임을 표준 함수(standard function)라고 합니다. 초창기에 ANSI에서 정한 표준 C함수의 수는 100여 개 뿐이었습니다. C++에는 수많은 표준함수와 표준 클래스 및 표준 객체들이 존재합니다.

  이렇게 함수의 리턴값이 없다라는 표현도 void로 합니다. 주의 사항은 파라미터 리스트를 생략한 경우는 void이지만, 함수의 리턴 형을 생략한 경우는 int이므로 반드시 void를 명시해야 한다는 것입니다.

  자, 이제 우리는 어떤 수학 함수라도 C의 함수로 바꿀 수 있을 것 같습니다. 마지막으로 매우 중요한 한 가지 규칙을 더 언급하겠습니다. 위와 같이 4개의 함수가 있을 때 시작하는 함수는 어느 것으로 해야 할까요? 여기에 관한 규칙은 다음과 같습니다.

“시작하는 함수는 main()입니다. 반드시 그리고 유일하게 1개 있어야 합니다.”

  시작하는 함수는 소문자 main()입니다.

C언어 처럼 대소문자를 구분하는 언어를 대소문자에 민감한 언어(case sensitive language)라고 합니다.

  실행프로그램이 운영체제에 의해서 로드(load)된 후 운영체제는 제일 먼저 main()을 호출합니다(플랫폼platform이 Win32의 경우, 운영체제는 WinMain()을 호출합니다. 하지만, 대부분의 운영체제에서는 main()이 시작하는 함수입니다). 그러므로 실행 파일을 만드는 소스마다 반드시 1개만의 main() 함수를 가져야 합니다. main()의 원형은 여러 개가 존재합니다. 일반적으로는 다음과 같습니다.

void main(void)


함수도 선언해야 한다.

  아래의 프로그램은 무엇이 잘못인가요?


    void main() {
        int i;
        i=f(3);
    }

    int f(int x) {
        int t;
        t=x*x+5;
        return t;
    }

  함수 main()에서 f()를 호출하고 있습니다. 하지만 컴파일러가 i=f(3) 이란 문장을 기계어로 번역하기 위해서는 먼저 함수 f()가 제대로 사용되었는지에 대한 정보가 필요합니다. 함수 f()가 정수 2개를 파라미터로 받는다면 어떻게 에러를 발견할 수 있을까요? 만약, 아래와 같이 잘못 사용했다면, 컴파일러는 에러를 출력할 수 있어야 합니다.

i=f(3,4)

    컴파일러는 함수 호출이 타당한지 검사하기 위하여 함수의 머리 부분을 특정한 테이블 – 심벌 테이블(symbol table) – 에 저장하여 둡니다. 함수 호출 문장을 만나면 컴파일러는 자신이 현재 유지하고 있는 테이블에서 함수를 찾아서 다음과 같은 사항을 검사합니다.

  (1) 리턴 형이 맞는가

  (2) 파라미터의 개수는 몇 개이며, 형은 맞는가?

  이러한 검사가 실패하면 컴파일러는 에러 메시지를 출력합니다. 일반적으로 컴파일러는 코드를 생성하기 위해 소스를 두 번 스캔(scan)합니다.

이러한 컴파일러를 2번 검사 컴파일러(2 pass compiler)라고 합니다.

첫번째 스캔에서 함수와 변수에 관한 정보를 심벌테이블에 저장하며, 함수의 주소를 결정하기 위하여 함수의 사용이 맞게 되었는지 등도 검사합니다. 두 번째 스캔에서 실제로 코드를 생성합니다.

  하지만 main()에서 함수 호출 f()를 만난 시점에서 컴파일러는 f()에 관한 어떤 정보도 유지하고 있지 않습니다. 그러므로 함수의 사용이 타당한지를 검사할 수 없습니다. 그러므로 컴파일러는 에러 메시지를 출력합니다. 그러므로 변수를 쓰기 전에 선언해야 하듯이 함수도 쓰기 전에 선언해야 합니다. 규칙은 다음과 같습니다.

“함수는 호출 전에 반드시 선언해야 합니다.”

  그러므로, 소스는 다음과 같이 수정되어야 합니다.


    int f(int x);

    void main() {
        int i;
        i=f(3);
    }

    int f(int x) {
        int t;
        t=x*x+5;
        return t;
    }

  함수의 선언은 함수의 머리 부분만으로도 충분합니다. 그리고 문장이므로 끝에 ; 을 명기합니다. main() 밑은 실제의 함수 f()를 정의하고 있으므로, 함수 정의(definition)라고 합니다.

  우리는 위의 스타일이 보기 싫으면 다음과 같이 소스를 수정할 수 있습니다.


    int f(int x) {
        int t;
        t=x*x+5;
        return t;
    }



    void main() {
        int i;
        i=f(3);
    }

  위의 스타일에서는 함수 f()에 관한 선언(declaration)은 없어도 됩니다.

있어도 된다는 의미를 포함합니다.

  위와 같이 함수가 정의되었을 때, ‘함수의 선언과 정의를 동시에 했다.’고 합니다. 어떤 사람은 위와 같은 스타일(style)을 선호합니다. 하지만 위와 같은 스타일이 싫은 사람은 선언과 정의를 따로 하는 것을 즐겨하기도 합니다.

반드시 선언을 필요로 하는 함수가 있으므로, 선언의 역할을 명확히 알고 있어야 합니다.

  비록 위의 프로그램은 아무 것도 출력하지 않지만 코드를 입력해서 컴파일 하면, 훌륭히 컴파일 될 것입니다.

  컴파일러는 ANSI에서 정한 표준 함수(standard function)를 미리 만들어서 제공하는데, 이를 표준 함수라고 합니다.

성급하게 어떤 종류의 표준함수가 몇 개나 있는지 알려고 하지 마세요. C의 문법에 익숙해진 독자들은 그 어려운 작업(?)을 하여도 좋습니다. 이것은 후에 ‘표준함수‘에서 다루겠습니다.

  표준 함수는 컴파일러나 운영 체제에 상관없이 동일한 기능을 제공하는데, 운영 체제의 특성 때문에 오히려 어려운 상황이 발생하기도 합니다. 우리가 제일 먼저 사용하게 될 표준 함수는 다음과 같습니다.

printf()

  이 함수는 표준 출력(standard output)이라고 불리는 파일(file)에 문자열(character string)을 – 아직 표준 출력, 파일, 문자열을 정의하진 않았지만 – 출력합니다.

문자는 ASCII의 1문자를 의미하며, A인 경우 C언어에서 ‘A’처럼 표현한다. 문자열은 0개 이상의 문자의 연속한 순서를 의미합니다. ABC라는 문자열은 “ABC”로 표시한다. 특별히 길이가 0인 “”는 빈 문자열(empty string)이라고 합니다.

  예를 들면 아래의 문장은 화면(screen)의 현재 커서(cursor) 위치에 문자열 “I love the God”를 출력합니다.


printf("I love the God");

 

  이 프로그램을 완벽하게 C로 만들어 봅시다.


    void main() {
        printf("I love the God");
    }

  위의 프로그램은 아직 부족합니다. ‘함수는 쓰기 전에 선언해야 합니다.’라는 중요한 규칙을 만족하지 못하고 있기 때문입니다. printf()의 선언문이 필요한 것입니다.

  여기서 우리가 직면한 문제는 ‘수백 개나 되는 표준 함수의 선언을 어떻게 일일이 쓸 때마다 선언을 적어 줄 것인가?’ 입니다. 그래서 C언어는 이러한 문제를 해결하기 위한 훌륭한 해결책을 제시해 놓았습니다. 선언을 미리 해서 한 개의 파일에 모아 놓은 다음, 이 파일을 끼워 넣기(include) 하는 것입니다.

  먼저, 표준 함수를 종류와 기능 별로 구분한 다음, 구분된 각각의 부류의 함수들에 대해 함수 선언을 포함한 파일을 만듭니다. 이러한 파일은 대개 함수의 머리 부분(header)만을 포함하므로 헤더 파일(header file)이라고 하며, 파일의 확장자로 .H를 – *.h – 붙입니다. 아래는 그 중 몇 개의 표준 헤더 파일입니다.

  stdio.h : 표준 입출력에 관한 함수의 선언이 들어 있습니다.

  stdlib.h : 표준 함수 중 입출력 외의 함수들이 들어 있습니다.

  math.h : 표준 수학 함수의 선언이 들어 있습니다.

  디스크에 별도로 존재하는 이러한 외부 파일을 끼워 넣는 명령문은 다음과 같습니다.

#include

  예를 들면 printf()는 표준 출력에 사용되는 함수이므로, stdio.h에 선언이 들어 있습니다. 그러므로 완벽한 소스는 다음과 같습니다.

일반적으로 통합 개발환경IDE은 함수가 어느 헤더 파일에 포함되어 있는지에 대한 정보를 확인하는 방법을 제공합니다. 각자가 사용하는 통합 개발환경의 도움말을 참고하세요.


    #include <stdio.h>

    void main() {
        printf("I love the God");
    }

  즉 헤더 파일의 이름을 작다(less than: <)와 크다(greater than: >) 기호 사이에 적어줍니다. 어떤 독자들은 왜

#include “stdio.h”

라고 사용하지 않는지에 대해 의문이 생길지도 모릅니다. 후에 이러한 것의 차이점을 살펴보도록 하겠습니다.

  독자들이 다음으로 주목해야 하는 사실은 include 앞에 붙은 특수 심벌 #(sharp)입니다. 왜 그냥 아래처럼 사용하도록 허용하지 않았을까요?

include <stdio.h>

  왜 #을 붙여서 아래의 문장처럼 키워드를 사용해야 할까요?

#include <stdio.h>

  우리는 #include의 처리 과정에 주목해야 합니다. #include문은 컴파일러가 코드를 생성하기 전pre 단계에 처리process합니다. 즉 디스크에 존재하는 stdio.h 파일을 그 자리에 끼워 넣은 다음, 끼워 넣어져서 확장된 소스를 컴파일러가 컴파일하는 것입니다. 통합 개발환경의 메뉴에서 컴파일을 선택해서 컴파일 과정을 살펴보면, 우리가 작성한 소스에 비해 훨씬 긴 줄을 컴파일한 것을 살펴 볼 수 있습니다. #include 문에 의한 소스 확장(expansion)의 결과입니다.

  이렇게 컴파일 전에 처리되는 명령문을 전처리(preprocessing) 명령문, 혹은 컴파일러 지시자(compiler directive)라고 합니다.

컴파일 과정을 processing이라고 보고, processing 전에(pre) 처리된다는 의미입니다.


Borland C++에서 컴파일 화면: 원래의 소스는 4줄이지만, #include <stdio.h>에 의해 컴파일된 소스가 389줄인 것을 확인할 수 있습니다.

  C에서 전처리 명령문은 모두 특수 문자 # 으로 시작합니다. 많이 사용하는 전처리 명령문에는 #define, #ifdef, #endif 등이 있습니다.

  아래의 그림은 Visual Studio 2013에서 #include의 결과를 확인하는 방법을 보여줍니다. 프로젝트 속성 페이지에서 showInclude옵션을 활성화하면, 컴파일러가 빌드 과정에서 포함하는 파일들의 순서와 목록을 확인할 수 있습니다.


포함include되는 파일의 목록을 확인하기 위해서, Visual Studio 2013에서 프로젝트 속성의 [포함표시]를 [예]로 설정합니다.


프로젝트를 빌드하면, ConsoleApplication.cpp를 빌드할 때 포함된 파일들의 목록을 확인할 수 있습니다.


소스 문자 집합(source character set)

  C언어는 어떤 문자들로 구성할 수 있을까요? C/C++언어의 토큰을 구성하는 문자는 다음과 같습니다.


a b c d e f g h i j k l m n o p q r s t u v w x y z

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

0 1 2 3 4 5 6 7 8 9

_ { } [ ] # ( ) < > % : ; . ? * + – / ^ & | ~ ! = , \ ” ‘

  위의 문자들은 변수(variable), 명칭(identifier), 숫자(number), 기호(symbol)등을 형성합니다. 이러한 것들을 모두 토큰(token)이라고 합니다. 토큰은 컴파일러가 코드를 생성하는데 필요한 의미있는 단위를 말합니다.

  영문자와 숫자를 제외한 특수문자(구분자: delimiter)의 발음은 아래와 같습니다.


_       underscore(밑줄)

{       open brace or open curly bracket(여는 대괄호)

}       close brace(닫는 대괄호)

[       open bracket(여는 대대괄호)

]       close bracket(닫는 대대괄호)

#      pound(sharp or number)

(       open parenthesis(여는 괄호)

)       close parenthesis(닫는 괄호)

<      less than(보다 작다)

>      greater than(보다 크다)

%      percent

:       colon

;       semicolon

.       period(점)

?       question mark(물음표)

*       asterisk or star(별표)

+      plus

–       minus

/       divide

^      caret

&      ampersand

|       vertical bar(수직 바)

~      tilde(틸더, 물결표)

!       exclamation mark

        (느낌표)

=      equal

,       comma

\      back slash(역 스래쉬)

”       double quotation

        mark(이중 인용 부호)

‘       single quotation mark(인용 부호)

  아래표는 ASCII 문자 집합의 발음을 모두 표시한 것입니다.



확장 ASCII 문자 집합

@

2 thoughts on “[만화가 있는 C] 2. 수학함수를 C 함수로”

Leave a Reply