51 – 기억 클래스

C언어에서 변수와 함수는 데이터형과 기억 클래스라는 두 속성을 지니는데, 이 중 메모리의 어느 위치를 어떻게 확보하는지를 결정하는 것을 ‘기억 클래스’라고 한다. 변수와 함수가 참조될 수 있는 영역에 따라 다음의 4가지로 나뉘어 볼 수 있다.

  • auto
  • extern
  • register
  • static

그 종류에 따라 변수와 함수가 참조될 수 있는 영역이 결정되는 기억 클래스는 다음과 같은 형태로 사용한다.

[기억클래스] [데이터형] [변수(혹은 함수)이름]

예를 들어, static 클래스의 int 형 변수 i의 선언을 한다면 다음과 같이 한다.

static int i;

이제 변수와 함수의 기억 클래스에 대해 각각 확인해야한다. 내용이 많아서 글을 여럿 나눌 것이다.

50 – 함수간 데이터 전달 기법

함수가 제대로 동작하려면 한 함수에서 다른 함수를 호출할 때 필요한 값을 전달해주기도 하고 결과값을 전달받기도 해야 하는데, 함수들끼리 주고받는 데이터를 매개변수(parameter)라고 한다. 그리고 특별히 함수를 호출하는 쪽의 매개변수를 실매개변수(actual parameter)라고 하고, 함수를 정의한 쪽의 매개변수를 형식매개변수(formal parameter)라고 한다.

C언어에서 함수를 호출할 때 매개변수를 전달하는 방법에는 다음 두 가지가 있다. 하나하나 살펴본다.

우선 ‘값에 의한 전달(call by value)’는 함수 호출 시 값만 함수쪽으로 보내 해당 값을 매개변수에 저장해 동작하는 방법으로, 함수 실행중에 형식매개변수의 내용이 변해도 실매개변수의 니용은 전혀 변하지 않는다. 즉, 값이 그대로 함수쪽의 매개변수에 복사되어 별도의 변수처럼 취급되는 것이다.

그리고 ‘주소에 의한 전달(call by address)’ (call by reference라고도 합니다.)은 값을 전달하는 것이 아닌 전달하고자 하는 변수의 메모리 주소를 전달하는 방법으로, 함수 실행중에 형식 매개변수 값의 조정에 따라 실매개변수의 내용이 변경되는 방법이다. 당연히 포인터에 대한 개념이 제대로 있어야 쓸 수 있다.

이 두 가지를 직접 하나의 예제에 박아서 작성하였다. 함수명에도 적혀있지만 call by value, call by address(call by reference) 형식으로 전달하는 방법을 보여주는데, 결과값이 확실히 다른 것을 볼 수 있다.

스크린샷 2017-04-05 오전 1.35.17

call by value로 호출한 함수의 경우에는 두 변수의 값을 바꾸는 작업을 하였는데도 불구하고 함수에서 쓰인 변수와 main에 선언한 변수는 별개의 값으로 취급되기 때문에 둘의 값을 바꾼 건 함수 안에서만의 동작이고 실제로는 바뀌지 않았다. 그러나 call by address의 경우에는 main에서 선언한 변수를 포인터로 가져와서 치완하는 작업을 진행하였기 때문에 해당하는 값을 그대로 바꿨다. 그래서 출력 결과가 바뀌어 있는 것이다.

48 – 함수 정의

함수가 실행할 내용이 실제로 존재하는 부분이 바로 함수 정의며, 그 형식은 main 함수를 지겹게 써봐서 알겠지만 다음과 같이 좀 체계적으로 살펴보도록 하겠다. 일단 함수의 형식은 다음과 같다.

[데이터형] [함수이름]([매개변수들])
{
….
[문장들];
….
return [반환값];
}

함수 이름을 작성하는 방법은 변수 이름을 작성하는 방법과 같고, 함수의 영역은 중괄호( { } )로 표시한다. 그런데 이 때 중괄호 안에 다른 함수의 중괄호를 사용하면 안 되는데, 그 이유는 프로그램에서 정의되는 함수는 각각 독립적으로 존재하기 때문이다. 함수를 정의하는 데 꼭 필요한 것은 함수 이름뿐이고, 나머지는 생략이 가능하므로 가장 간단한 함수의 정의는 다음과 같은 구조이다.

[함수이름]()
{
}

함수 이름 앞의 데이터형은 그 함수가 반환하는 값의 데이터형을 의미한다. 그러므로 정수값을 반환하면 int를, 문자값을 반환하면 char를 적어준다. int형 값을 반환한다면 반환하는 함수의 예시는 다음과 같다.

int add_one(int number)
{
return number+1;
}

return은 값을 반환하는데, return에 반환값의 형은 함수에 반환하는 데이터형과 일치해야 한다. 반환하는 형식은 다음과 같다.

return;
return 수식;
return (수식);

수식이 없는 것은 반환값이 없는 함수고, 이때는 함수의 실행만 종료시킨다는 것을 의미하는데, 수식을 작성할 때는 수식을 괄호로 둘러싸도 되고 그러지 않아도 상관없다. 그리고 반환값이 없을 경우에는 데이터형이 들어갈 자리에 void를 명시하는데, void를 생략할 수도 있지만 명시하면 값을 반환하지 않는 함수라는 것을 명확히 밝히는 것이므로 void를 작성하는 것이 더 바람직하다.

void no_return(int num)
{
printf(“num: %d”,num);
}

그리고 매개변수를 통해 함수끼리 데이터를 주고받을 수 있는데, 매개변수는 데이터형과 변수 이름으로 구성된다. 아래에서는 number가 매개변수가 된다.

int function(int number)
{

}

매개변수는 콤마로 분리해 다음과 같이 여러 개를 지정할 수도 있다.

int subtract(int num1, int num2)
{
return num1-num2;
}

없을 경우에는 생략해도 된다. 그런데 이때도 void를 써주면 매개변수가 없다는 것을 명확히 해주므로 명시하는 것이 좋다.

void function(void)
{
printf(“function”);
}

물론 함수의 정의가 함수 호출 앞에 나오는 경우에는 함수 선언을 생략해도 되지만 한 프로그램에서 이용하는 함수가 많거나, 함수들간에 서로 호출할 때에는 함수 호출보다 먼저 정의하는 것이 쉽지 않다. 그러므로 대부분의 프로그램에서는 함수 선언을 이용한다.

47 – 함수 선언

변수를 사용하기 전에 변수를 선언하는 것과 같이 함수를 사용하기 위해서는 프로그램에서 사용할 함수를 선언해야 한다. 그렇지 않고 함수를 호출하면 함수의 존재 여부를 모르기 때문에 오류를 발생시킨다.

함수의 선언은 다음과 같은 형식으로 함수를 먼저 선언하면, 이것은 컴파일러에서 함수의 결과값 데이터형, 함수 이름, 매개 변수에 대해 알려줌으로써, 그 함수를 호출할 때 올바른 호풀을 하였는지 검사할 수 있게 된다. 이런 형태의 선언을 함수의 원형을 선언한다고 한다.

[데이터형] [함수이름]([매개변수들]);

이때, 매개변수는 데이터형과 변수 이름으로 구성되는데, 이 중 변수 이름을 생략할 수는 있지만 써주는 것이 어떤 값을 전달받는 함수인지 쉽게 이해할 수 있다. 그래서 될 수 있으면 생략하지 않는 것이 좋다. 즉,

int func(char *, int);

와 같이 선언할 수 있지만

int func(char *name, int age);

와 같이 선언하면 어떤 값을 전달받는지를 쉽게 알 수 있게 된다.

이러한 함수 선언은 main 함수 전이나 함수가 호출되기 전에 이루어져야 하며, 가능한 한 함수 선언을 main 함수 앞에서 모아서 하면 프로그램의 구조를 이해하기가 더 쉽다.

46 – 함수

함수는 특정한 작업을 수행핟도록 설계된 독립적인 프로그램의 조각이라고 보면 좀 더 쉽게 이해할 수 있을 거 같다. 이런 함수들이 모여서 하나의 프로그램이 완성되는 거다. 특정 작업을 여러 번 해야 할 경우에는 이 작업을 하나의 함수로 만들면 필요할 때마다 그 함수를 호출해서 사용할 수 있다. C 프로그램은 함수로 이루어져 있는데, 대표적인 함수는 우리가 매번 쓰던 main 함수이다. main 함수는 C 프로그램에서 반드시 필요로 하는 함수이므로, 프로그램의 시작점이다. 그래서 C 프로그램을 실행하면 main 함수 내에 있는 문장들을 실행하게 된다. 만약 main 함수가 없으면 오류를 발생하게 된다.

사용하는 함수에 따라서 라이브러리 함수와 사용자 정의 함수로 나뉜다. 라이브러리 함수는 사용자들이 프로그램을 작성하는 데 도움을 주기 위해 자주 사용될 함수들을 시스템에서 미리 작성해 놓은 것들이다. 어떤 라이브러리 함수들을 제공하는지는 쓰면서 차차 익혀가면 된다. 우리가 자주 쓰던 printf 함수 또한 시스템 라이브러리에 있는 함수이다. stdio 라는 라이브러리에서 불러올 수 있으며, 실제로 사용할 때마다 해당되는 함수를 불러오기 위해 맨 위 문장에 #include를 통해서 직접 불러온 것이다. (#include에 대해서는 좀 더 자세히 다룰 기회가 있다.)

이 외에 사용자들이 직접 작성한 함수를 사용자 정의 함수라고 한다. 프로그램 안에서 필요한 기능들을 사용자가 직접 만들어서 사용하는 함수들로써, 이런 함수들은 마음대로 만들고 없앨 수 있다.

큰 프로그램을 main 함수 하나로만 작성하면 작성하기도 어려울 뿐만 아니라 이해하기도 힘들기 때문에 여러 함수로 분할 작성하는 것이 좋다. 함수를 사용하기 위해서는 앞에 장에서도 잠깐 설명하였듯, 함수 선언, 함수 호출, 함수 정의 작업이 필요하다. 이제 좀 하나하나 자세히 설명하기로 하자.

45 – 포인터와 문자열 다루기

이전 글에서 작성한 듯, 문자열을 문자의 비열로 처리된다. 즉, char형 포인터를 이용하여 문자열을 처리할 수 있다는 것이다.

char *str;

str = “hello”;

이런 식으로 하게 되면 아래의 그림처럼 된다.


바로 앞에서 배열과 포인터 부분에서 본 것과 똑같은 상황인 것이다. 문자 처리에 차이가 있을 뿐이므로 예시도 바로 보여주도록 하겠다. 


44 – 포인터와 배열

포인터와 배열은 진짜로 밀접한 관계를 가지고 있다. 다른 것보다 예시를 직접 보고 시작하자.



배열 이름인 arr은 &arr[0], 즉, 배열의 첫 번째 요소를 주소로 가지고 있으며, 상수로 취급하지 않기 때문에 배열 이름인 arr값을 변경할 수 없다. for문의 초기식쪽을 주목해야 하는데, ptr = arr은 ptr = &arr[0]을 의미하는 것이다. 그래서 처음에 출력되는 것이 첫 번째 값인 10이다. 

그 다음에 ptr+1을 하게 되면, 배열 요소의 다음 요소인 arr[1]을 카리킨다. ptr++을 하면 할수록 다음 칸을 실행하게 되는 것이다. 그것이 바로 아래 그림의 설명이다.


결국 포인터 하나로 배열의 모든 값을 처리할 수 있는 것이다.