가변 인자 함수, 올바른 오류 처리 방법
가변 인자 함수
<반환형> <함수명> (<자료형이 정해진 매개변수 목록>, ...);
- 정해지지 않은 수의 매개변수(가변 인자)를 허용하는 함수
- 반드시 최소 한 개의 정해진 자료형의 매개변수가 필요
- 가변인자는 '...'로 표현
가변 인자 함수의 예
#include <stdarg.h>
int add_ints(const size_t count, ...)
{
va_list ap;
int sum;
sisze_t i;
sum = 0;
va_start(ap, count);
{
for(i = 0; i < count; i++){
sum += va_arg(ap, int);
}
}
va_end(ap);
return sum;
}
int main(void)
{
int result;
result = add_ints(1, 16);
printf("result: %d\n", result);
result = add_ints(4,1,2,3,4);
printf("result: %d\n", result);
return 0;
}
va_list
- 가변 인자 목록
- va_start(), va_arg(), va_end() 매크로 함수를 사용할 때 필요한 정보가 포함
- 명시되지 않은 자료형(구현마다 다름)
va_start()
va_start(<가변 인자 목록>, <가변 인자 시작하기 직전 매개변수>);
- 매크로 변수
- 함수 매개변수로 들어온 가변 인자들에 접근하지 전에 반드시 호출해야 함
- va_list에 필요한 초기화를 수행
- 특히 가변 인자가 스택 메모리의 어디서부터 시작하는지를 찾아냄
- 그래서 두 번째 매개변수가 필요
va_end()
va_end(<가변 인자 목록>)
- 매크로 함수
- 함수 매개변수로 들어온 가변 인자들에 접근이 끝난 뒤에 반드시 호출해야 함
- 사용했던 가변 인자 목록을 정리함
- 더 이상 가변 인자 목록을 사용할 수 없더록 가변 인자 목록의 값을
va_arg()
va_arg(<가변 인자 목록>, <얻어올 가변 인자의 자료형>);
-
매크로 함수
-
가변 인자 목록으로부터 다음 가변 인자를 가져옴
-
가져올 가변 인자의 자료형은 두 번째 매개변수로 알려줌
-
예전 표준상의 문제로 가변 인자 목록의 기본 자료형 인자들은 다음과 같이 승격됨
- 모든 정수형은 int로
- 모든 부동소수점은 double로
-
따라서, 두 번째 매개변수에는 int나 double을 쓸 것
-
va_arg()는 매크로 함수
- 함수처럼 보이지만 엄밀한 의미의 함수는 아님
- 그 대신 전처리기가 매크로 함수의 구현 코드로 대체시켜 줌
가변 인자 함수가 인자를 읽어오는 방법
-
va_start(ap, count)에서 가변 인자 시작 직전 매개변수(int 형)에 기초해서 가변 인자 목록의 시작 메모리 주소를 계산
va_start(ap, count); => ap.data = (char*)&count + sizseof(count)
-
va_arg(ap, int); 가 호출될 때마다 int 크기만큼 더해가며 읽을 위치를 변경하면 됨
va_arg(ap, int); => val = *(int*)ap.data; ((int*)ap.data)++;
따라서 va_list는 수택 메모리에서 위치를 가리키는 포인터 같은 것을 가지고 있을 수 밖에
함수에서 매개변수로 가변 인자만을 받을 수 있을까?
- 가변 인자(...) 앞에 자료형이 특정된 매개변수가 반드시 있어야 함
- 가변 인자 뒤에 자료형이 정해진 매개변수가 있으면 안 됨
- 함수가 정확히 어느 오프셋에서 읽어와야 하는지 컴팡리 중에 특정 불가
- 즉, 가변 인자 아닌 것을 우선 차례대로 읽음
- 그 뒤, 가변 인자는 va_arg()가 시키는 대로 하나씩 주소를 늘려가며 읽는 것
void do_something(..., int); // err
void do_something(int, ..., int); // err
void do_somethign(int, int, ...); // ok
- 가변 인자가 몇 갠지 가변 인자 함수는 모름
- 가변 인자의 자료형을 가변 인자는 모름
오류 처리
- C 언어는 예외를 지원하지 않음
안 좋은 오류 처리의 예
- 문제는 한 군데서만 찾는 게 더 효율적
- 따라서 오류 처리를 할 때에도 원칙이 있어야 함
- 생각 없이 무조건 작동한다고 코드 짜는 건 일단 OK
- 그러나 그 문제를 찾는 곳은 최소한인 게 좋음
버그와 오류의 차이, 올바른 오류 처리 전략
- assert의 문제는 실행해야만 보인다는 것
- C89에서는 컴파일 중에 판단 가능한 것도 모두 실행해야만 보임
- C11은 정적 어서트(static assert)로 이러한 한계를 극복
널 포인터를 허용한다면 함수나 변수에 명시
- 함수의 매개변수가 널 포인터를 허용한다면, 매개변수 이름 끝에 'or_null'을 붙인다
- 함수도 마찬가지
monster_t* spawn_monster_or_null(const monster_t* special_monter_or_null) { # 코드 생략 }
오류 코드를 반환하자!
- 오류를 처리해주는 함수/코드에서 오류가 있음을 알려줘야 함
- 가장 좋은 방법은 함수에서 곧바로 오류 코드 반환한느 것
libabc_error_t try_get_student(int id, student_info_t* out_student) { size_t idx; // 생략 if(idx == -1){ return ERROR_STUDENT_NOTFOUND; } // 생략 return ERROR_NONE; }
모든 오류 코드를 하나의 enum으로 만들자
- 구조체로 반환도 가능하나 C에서 많이 쓰는 방법은 아님
- 오류 코드 만들 때는 해당 라이브러리에서 제공할 수 있는 모든 오류코드를 enum으로 정의하는 게 좋다
typedef enum{ ERROR_NONE, ERROR_BAD_REQUEST, ERROR_UNAUTHORIZED, ERROR_FORBIDDEN, ..., } libabc_error_t;
함수마다 오류 enum을 만드는 것은 좋지 않음
- C#의 enum과 다르게 C의 enum은 서로 비교 및 대입이 가능
전에 본 errno도 좀 별로...
올바른 오류 처리 전략 정리
- 기본적으로 내가 작성하는 모든 함수에 들어오는 데이터는 유효하다 가정하고 어서트를 많이 쓸 것
- 그렇지 않은 함수는 매개변수나 함수 이름에서 그렇지 않다는 사실을 명백히 표시할 것
- 오류 상황을 처리하는 장소는 최소한으로 할 것
- 어떤 함수가 오류 처리를 한다는 사실을 반환형 등을 통해 확실히 보여줄 것
오류 처리 후에도 발생하는 예외 상황
운영체제의 예외 처리
- 함수 포인터를 등록하고 OS가 보내는 예외 처리를 받아올 수는 잇음
- 근데 받아와도 어떻게 대처해야 할지 애매한 겨웅가 있음
'프로그래머 > C, C++' 카테고리의 다른 글
[면접 대비] C를 사용한 해시 맵 구현 (0) | 2020.11.29 |
---|---|
[면접 대비] C를 사용한 linked list 구현 (0) | 2020.11.29 |
[포프 tv 복습] C 자료구조 기초 (0) | 2020.11.29 |
[포프 tv 복습] 레지스터, 스택 & 힙, 동적 메모리. 다중 포인터 (0) | 2020.11.28 |
[포프 tv 복습] 구조체, 공용체, 함수 포인터 (0) | 2020.11.25 |