2021. 12. 15. 16:45ㆍC
가변 인수란 인수의 개수와 타입이 정해져 있지 않은 인수를 말한다. 대표적으로는 printf와 scanf 등이 있다.
printf("Hello, World!");
printf("%d:%d",a,b);
printf의 원형 : int _cdecl printf(const char*_Format, ...)
가변인수 함수에서는 반드시 고정 인수를 한 개 이상 가지고 있어야 하며, 가변 인수가 올 자리에는 ...가 있다.
Format가 문자열 상수를 의미하고 고정 인수다. 고정 인수는 const char* 타입의 문자열이어야만 한다.
cdecl은 함수를 호출할 때마다 호출원이 인수를 전달한 스택을 정리한다.
printf는 인수의 수가 정해져 있지 않기 때문에 호출된 함수가 스택을 정리하는 _stdcall을 쓸 수가 없다.
이것은 모든 가변 인수에서 동일하게 적용되며, 만약 _stdcall로 작성했다면 컴파일 시 _cdecl로 바꾸어 버린다.
여기서 _stdcall 이란?
함수 호출 규약 (_cdecl, _stdcall, _fastcall)
: 함수 실행 시 인자 값을 전달하고 스택을 정리하는 과정에서 함수를 호출하는 쪽과 호출당하는 함수 사이의 혼란을 방지하기 위해 함수 호출 과정에서는 일정한 규약이 존재한다. 이를 함수 호출 규약이라고 한다. 이러한 함수 호출 규약에는 _cdecl, _stdcall, _fastcall 등이 존재한다.
_cdecl
: x86 구조에서 주로 사용하는 호출 규약으로 C/C++ 컴파일러에서 기본적으로 사용한다. 함수 호출 시 오른쪽 인자부터 스택에 전달하며, 호출자(caller)가 스택을 정리하는 특징이 있다. 이를 디버깅을 통해 좀 더 알아보면, _cdecl 은 push 명령어를 통해 인자 값을 역순으로 스택에 저장한 뒤 함수를 호출한다. 또한 함수 호출 이후에는 ADD ESP,8 명령어를 통해 인자 값 2개를 위해 사용했던 스택 공간을 함수 호출 이전의 상태로 복구한다.
_stdcall
:MS Win32API 표준 규약으로, 함수 호출 시 오른쪽 인자부터 스택에 전달하고 호출당한 함수(피호출자)가 사용한 스택을 정리한다는 특징이 있다. _stdcall 호출 규약 방식을 디버깅을 통해 보면 _cdecl과 달리 ADD ESP,8과 같은 호출자가 스택을 직접 정리하는 행위가 생략되어 있다.
함수 내부를 확인해보면 내부에서 return과 동시에 스택을 정리하는 것을 확인할 수 있다. 그냥 ret명령만을 실행하는 것이 아닌 ret 8 명령어를 실행하여 함수 종료 전에 사용한 인자값을 정리하기 위해 할당된 스택 공간을 정리하는 것이다.
_fastcall
:매개 변수의 일부를 스택이 아닌 ECX, EDX와 같은 레지스터로 전달한다. 때문에 함수 호출 시 다른 규약에 비해 빠른 편이며, _stdcall 방식과 마찬가지로 호출 당한 함수가 사용한 스택을 정리하는 특징이 있다.
가변인자를 사용하는 데 쓸 수 있는 매크로 함수들이 존재한다.
모두 <stdarg.h> 헤더파일을 호출하면 사용할 수 있다.
va_list
: 함수로 전달되는 인수들은 스택에 저장한 뒤 거기서 매개변수를 꺼내 사용하는데 변수를 읽으려면 포인터 변수가 필요하며 그 포인터가 va_list형이다.
va_start
: 포인터 변수가 첫번째 가변인수를 가리키도록 초기화시킨다.
va_arg
:va_start로 포인터 변수가 첫번째 가변인수를 가리키고 읽을 때 가변 인자를 읽는다.
va_end
:가변 인수를 다 읽은 다음에 정리하는 역할인데 없어도 큰 상관은 없다
#include<stdarg.h>
int Func(int size, ...){
va_list Nstart;
va_start(Nstart,size);
for(int a=0; a<size; a++){
va_arg(Nstart,int);
}
va_end(Nstart);
1. Func 함수가 첫 번째 인수로 size를 전달받았다.
2. va_list로 Nstart라는 포인터 변수(char*형)을 지정했다.
3. va_start로 Nstart가 첫번째 인수 size를 가리킨다.
4. for문이 돌아가며 모든 인수를 다 읽을 때까지 계속 반복하며 읽는다.
5. 모든 인수를 다 읽었으면 va_end로 마무리한다.
'C' 카테고리의 다른 글
GetStdHandle 함수, SetConsoleTextAttribute 함수 (0) | 2022.01.06 |
---|---|
c언어 배열초기화, 비트연산자 (0) | 2021.12.15 |
scanf 사용법 관련 (0) | 2021.12.12 |
Error - scanf 반환값이 무시되었습니다. (0) | 2021.09.07 |