Chapter 9. Functions

지환·2021년 11월 20일
0
post-custom-banner

function이란? 간단하게 말하자면, statements를 모아 이름을 붙인 것이다.
한 program을 여러 조각으로 나눠서, 이해하고 수정하기 쉽게 해주고,
같은 code를 여러번 작성할 필요를 줄여준다.


9.1 Defining and Calling Functions

argument가 없더라도 함수 호출시 parentheses를 꼭 적어야 한다.
같은 이름의 함수는 같은 파일에서 동시에 define 될 수 없다.(p.325)

Function Definitions

return-type function-name ( parameters )
{
    declarations
    statements
}

Return-type

  1. array는 return할 수 없다. 이외 제한은 없다.
  2. void라고 작성하면 return value가 없는 것
  3. C89에선 return type을 빼먹으면 int로 가정했지만, C99에서는 return type 안 적는건 illegal

Parameters

  1. 각각의 parameter 앞에 type이 명시돼야 한다.
  2. parameter가 없다면, void라고 작성해야 한다.

함수 내에서 선언된 변수는 그 안에서만 쓰임.(block scope)

Function Calls

function call을 하려면 parentheses가 같이 있어야 한다.(argument가 있든 없든)
없어도 illegal은 아닌데, pointer to function으로 인식되기 때문이다.(Chpater 17)

function call에서 return value를 의도적으로 버리려면, 앞에 (void)를 붙일 수 있다.

(void) printf("Hi \n");

한 함수에 한개 이상의 return statements가 올 수 있다.


9.2 Function Declarations

함수를 호출하려면, 호출 이전에 해당 함수의 return type, parameter 개수, parameter type을 알아야 한다. 따라서 function call 이전에 function definition이 와야 한다.(함수 호출 이전에 definition이나 prototype이 와야한다.(C99))
하지만 함수 여러개가 꼬이면 함수 호출 이전에 definition을 놓는다는 것만으로는 문제가 해결되지 않는다.
예를들어 a함수와 b함수가 서로를 호출하면 어떤 함수의 definition이 먼저 오더라도 문제가 된다. 또한 파일이 꽉차서 definition을 다른 파일로 옮겨야 하는 경우에도 문제가 된다.

그러므로 해당 함수의 return type과 parameter 정보(type과 개수)를 미리 제공해주는
function declaration(function prototype)이 필요하다.

return-type function-name ( parameters ) ;

여기서 parameter names는 명시할 필요가 없다.(명시하더라도, definition과 같은 이름을 사용할 필요는 없다) 왜냐하면 애초에 function prototype이 제공하는 정보는 (1)return type, (2)type of parameters, (3)number of parameters 이 세가지이기 때문이다. 하지만 잘 알아볼 수 있게 parameters name은 그냥 적어주는게 좋다.

만약 function call 이전에 function definition(or prototype)을 보지 못하면 implicit declaration을 만드는데,
이와 실제 function definition이 일치하지 않을 경우 오류가 일어난다(자세한건 p.191~192).
하지만 C99에서 function call 이전에 무조건 "function definition"이나
"function prototype"이 와야 한다고 했으므로, 신경 쓸 필요는 없다.
그냥 함수 호출 이전에 prototype 잘 적어주면 된다.
옛날에는 선행하는 definition이나 prototype 없이도 작동은 했다 정도로만 알고 있자.

9.3 Arguments

passed by value
: C에서 argument는 무조건 passed by value이다. 즉, argument를 넘기면, 그 값은 복사돼서 함수에서 사용된다.

Argument Conversions (7.4 Type conversions과 연결됨)

  1. The compiler has encountered a prototype prior to the call
    : assignment에서처럼 convert된다.
  2. The compiler has not encountered a prototype prior to the call
    : "default argument promotion"을 실행한다.
    (float은 double로 변환, integer promotion 실행)

2번 경우는 C99면 생길 일 없음, 애초에 함수 호출 이전에 prototype을 못볼수가 없으므로..
근데 또 variable arguments 같은 경우가 있으므로 2번 경우도 생각해야한다.(ex. printf, scanf)

argument와 return 값에 type csating을 해줘서 implicit declaration
(implicit declaration 만들 때 default argument promotion을 함)으로 인한 오류를 잡을 수는 있다.
자세한건 p.195

Array Arguments

int f(int a[])
{
    ...
}

일차원 배열이 argument인 경우, 주로 길이를 빼고 적는다.(포인터를 넘겨받는 것)
배열 길이를 알고 싶다면, 다른 argument를 하나 더 추가해서 입력 받아야 한다.(배열이름이 포인터로 넘어오기 때문에 길이는 모름)

여기서 배열 길이를 명시하더라도, 컴파일러는 무시한다.
어차피 포인터를 넘겨받는거라 적나마나 의미는 없다.
그냥 프로그램 작성자가 알아보기 쉽게 매모하는 정도의 역할만 할 뿐이다.

다차원 배열이 argument인 경우, 첫번째 dimension을 제외하고는 모두 작성해야한다.

왜 다차원 배열은 첫번째 dimension 제외하고 적어야하지?
subscripting operator가 어떻게 작동하는지 봐야한다.
subscript를 하려면 "단위" 크기를 알아야 한다.
일차원 배열은 type을 알면 그 단위 크기를 알 수 있다.
(ex. int a[]에서 a[3]을 subscript하려면, int형 크기에 3을 곱한 주소로 가면 됨.)
근데 multidimensional array는 element의 type만 알아서는 그게 불가능하다.
(어떻게 보면 그냥 전체적인 type을 다 작성해준다는 것에서 같다고 볼 수 있는 것 같다.
int a[5][7]의 type은 a를 기준으로 보면 int [7] 이니까..)
a[2][3]의 위치를 알려면 a의 column이 몇개 있는지 알아야 알 수 있다.
column이 5까지 있으면 a의 type의 크기에 5를 곱한뒤에 2를 곱해서 3을 더해야하고,
column이 3까지 있으면 a의 type의 크기에 3을 곱한뒤에 2를 곱해서 3을 더해야하기 때문이다.
따라서 첫번째 dimension을 제외하고 모두 작성해야 한다.

(이차원 배열을 넘겨받는 함수에서 그걸(이차원배열을) 처리할 정보가 필요하다.)

Variable-Length Array Parameters

1. 
int Afunc(int n, int a[n])
{
    ...
}

2. 
int Afunc(int n, int a[*])
{
    ...
}

3. 
int Afunc(int, int a[*])
{
    ...
}

parameter가 VLA라는 것, argument가 VLA일 필요는 없다.
즉, 위 형태의 함수는 어떤 형태의 일차원 배열도 입력 받을 수 있다.
하지만 기본 형태도 아무 형태의 일차원 배열이나 입력받을 수 있으므로 크게 의미가 없고(배열 길이를 정확히 명시해주긴 함),
2차원배열을 입력받을 때 의미가 있다.

1.
int Bfunc(int n, int m, int a[n][m]);

2.
int Bfunc(int n, int m, int a[*][*]);

3. 
int Bfunc(int n, int m, int a[][m]);

4.
int Bfunc(int n, int m, int a[][*]);

Using static in Array Paramenter Declarations

int Afunc(int a[static 3], int n){ ... }

배열의 최솟값을 명시해준다.
컴파일러가 array에 접근하는 instruction을 좀 더 빨리 만드는 것 외엔 딱히 기능이 없다.
첫번째 dimension에만 쓰일 수 있다.

Compound Literals

(int [3]){1, 2, 3};
(const int [3]){1, 2, 3};

배열 initialization과 다른 점은, arbitrary expression이 올 수 있다는 것이다.
lvalue라서 element의 값이 바뀔 수 있다.(아래처럼 const 붙이면 read only로 바뀜)

사용하는 주 목적은 함수에 array를 넘겨주기 위함.
넘겨줄때 얘의 storage duration 주의해야됨. 주로 calling function(호출한 함수)이 종료되기전까진 duration 유지되니까 그 안에서 호출된 함수한테 넘겨준거면 상관없겠지만, return값으로 주소를 넘겨주거나 할 경우 주의해야한다.(array는 값 전체가 copy되는게 아니기때문에)
knk.chapter10에서도 말했지만(원래는 chapter18 q&a내용), compound literal은 block 밖에서 쓰여야 static, 아니면 automatic이다.
반례가 있다면 링크 참고. UB일 확률이 높음.


9.4 The return Statement

return expression ;

return type 일치하지 않는 경우 그냥 implicitly convert됨 (Section 7.4와 연결)

return type이 void인 경우
1. return ;
2. 굳이 return 문을 작성하지 않아도, 알아서 마지막 statement 실행되고 종료된다.

return type이 non-void인 경우
1. return statement 작성 안할 시 undefined behavior이다.
2. return statement를 작성 하더라도 expression이 없으면 undefined behavior이다.(C99)


9.5 Program Termination

main이 종료하고 return하는 값은 status code로 프로그램 종료 후 사용할 수 있다.
program이 terminate될 때의 상태를 반환. 성공시 보통 0을 return.

The exit Function

exit(0);
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);

<stdlib.h>에 있다.
EXIT_SUCCESS==0, EXIT_FAILURE==1 얘도 저 헤더파일에 있음.

return 문과 딱히 다른 점이 없는데,
한가지 차이는 exit function은 main이 아닌 어떤 함수에서도 프로그램을 종료할 수 있다는 것이다.


9.6 Recursion

A function is recursion if it calls itself.

The Quicksort Algorithm

큰 프로그램이 같은 알고리즘이 적용되는 작은 조각들로 나누어 진다면(divide-and-conquer) recursion을 사용하기 좋다.
자세한 quicksort관련 내용은 p.205부터... 여기 나오는게 best는 아니라고 했음.


Q&A

함수 안에 다른 함수의 definition이 오는 것은 허용하지 않는다.(C++도 마찬가지)
하지만 함수 안에 다른 함수의 declaration이 오는 것은 허용한다.(그럼 그 함수에서만 선언한 해당 함수를 사용할 수 있다. 다른 곳에서도 쓰려면 거기서 declare해야한다.)

f(a,b) 형태로 호출했을 때, 컴파일러가 어떻게 저게 comma operator가 아닌지 알아보나?
: 함수 argument에 오는건 아무 expression이 아니라 assignment expression이어야 한다. assignment expression에선 comma operator가 오지 못하므로 컴파일러가 알아볼 수 있다. f((a,b))면 comma operator로 인식한다. 문맥에 따라 의미가 달라지는게 이상해 보일 수 있지만, compiler가 그렇게 인식한다. * <- 얘도 보면 indirection operator인지 multiplication operator인지 문맥에 따라 다르게 해석된다.
(참고 : https://stackoverflow.com/questions/17383492/how-does-the-compiler-know-that-the-comma-in-a-function-call-is-not-a-comma-oper?r=SearchResults&s=4|99.6896)

double average(); 이런 prototype은 parameter가 없다는 뜻인가?
위 형태로 prototype을 적으면, parameter가 없다는 뜻이 아니라 그저 parameter 정보를 제공 안하는 것 뿐이다.
(parameter가 없다는 뜻으로 쓰려면 void라고 써야한다.(p.468))
(K&R에서는 오직 이런 방식의 declaration만 허용했었다. 지금도 허용하고는 있지만, parameter 정보 모두 제공하는게 일반적. 저거는 구식 표현 방법이다.)
(declare할 때만 해당되는 얘기, definition에 없으면 그냥 없는거지.)
C++에서 이런 형태로 적으면, C와 달리, parameter가 없다는 뜻이다.

main 함수의 경우 return문이 없어도 자동으로 0을 return하고 종료된다.(C99)
그럴거면 그냥 main return type을 void로 하면 안되나?

: C99에선 허용하긴하지만, portability를 위해서 되도록 자제하는게 best다.

post-custom-banner

0개의 댓글