상위 질문
타임라인
채팅
관점
가변 인자 함수
위키백과, 무료 백과사전
Remove ads
수학 및 컴퓨터 프로그래밍에서 가변 인자 함수(variadic function)는 항수가 부정인 함수, 즉 가변 개수의 인자를 받는 함수이다. 가변 인자 함수에 대한 지원은 프로그래밍 언어마다 크게 다르다.
가변 인자(variadic)라는 용어는 1936년/1937년으로 거슬러 올라가는 신조어이다.[1] 이 용어는 1970년대까지 널리 사용되지 않았다.
개요
요약
관점
자연스럽게 가변 인자 함수로 나타나는 많은 수학적 및 논리적 연산이 있다. 예를 들어, 숫자의 합계 또는 문자열이나 다른 시퀀스의 문자열 연결은 임의의 수의 피연산자에 적용될 수 있다고 생각할 수 있는 연산이다 (이러한 경우 공식적으로 결합법칙이 적용되더라도).
많은 언어에서 가변 인자 함수로 구현된 또 다른 연산은 출력 포맷팅이다. C 함수 printf와 커먼 리스프 함수 format이 두 가지 예이다. 둘 다 출력의 포맷팅을 지정하는 하나의 인수를 받고, 포맷팅할 값을 제공하는 임의의 수의 인수를 받는다.
가변 인자 함수는 일부 언어에서 자료형 안전 문제를 노출할 수 있다. 예를 들어, C의 printf는 부주의하게 사용하면 포맷 스트링 공격으로 알려진 보안 취약점의 한 종류를 유발할 수 있다. 이 공격은 가변 인자 함수에 대한 언어 지원이 자료형 안전하지 않기 때문에 가능하다. 즉, 함수가 스택에 배치된 것보다 더 많은 인수를 꺼내려고 시도하여 스택을 손상시키고 예상치 못한 동작을 유발한다. 이로 인해 CERT 코디네이션 센터는 C의 가변 인자 함수를 높은 심각도의 보안 위험으로 간주한다.[2]
함수형 프로그래밍 언어에서 가변 인자 함수는 함수와 목록/시퀀스/배열을 인수로 받아 해당 목록에 제공된 인수로 함수를 호출하는 apply 함수와 상호 보완적으로 간주될 수 있다. 이로써 함수에 가변 개수의 인수를 전달한다. 함수형 언어인 하스켈에서는 가변 인자 함수가 타입 클래스 T의 값을 반환함으로써 구현될 수 있다. 만약 T의 인스턴스가 최종 반환 값 r과 함수 (T t) => x -> t라면, 이는 임의의 수의 추가 인수 x를 허용한다.
항 재작성 연구에서 관련 주제는 hedges 또는 hedge variables라고 불린다.[3] 인자를 가진 함수인 가변 인자 함수와 달리, hedges는 인자 시퀀스 그 자체이다. 또한 '4개 이하의 인자만 받기'와 같이 제약 조건('정확히 4개의 인자 받기'와 같이 변수 길이가 아님)을 가질 수 있으므로, 이를 가변 인자 함수라고 부르는 것은 오해의 소지가 있다. 그러나 이들은 동일한 현상을 지칭하며, 때로는 용어가 혼합되어 가변 변수(hedge와 동의어)와 같은 이름이 나오기도 한다. 함수형 프로그래밍 및 항 재작성에서 '변수'라는 단어의 이중 의미와 인자와 변수 간의 차이에 유의한다. 예를 들어, 한 항(함수)은 세 개의 변수를 가질 수 있고, 그 중 하나는 hedge이므로, 그 항은 세 개 이상의 인자(또는 hedge가 비어 있을 수 있다면 두 개 이상)를 받을 수 있다.
Remove ads
예시
요약
관점
C에서
C 언어에서 가변 인자 함수를 이식성 있게 구현하려면 표준 stdarg.h 헤더 파일을 사용한다. 이전 varargs.h 헤더는 stdarg.h를 선호하여 구식화되었다. C++에서는 헤더 파일 cstdarg가 사용된다.[4]
#include <stdarg.h>
#include <stdio.h>
double average(int count, ...) {
va_list ap;
double sum = 0;
va_start(ap, count); // Before C23: Requires the last fixed parameter (to get the address)
for (int j = 0; j < count; ++j) {
sum += va_arg(ap, int); /* Increments ap to the next argument. */
}
va_end(ap);
return sum / count;
}
int main(int argc, char* argv[]) {
printf("%f\n", average(3, 1, 2, 3));
return 0;
}
이것은 임의의 수의 인수의 평균을 계산할 것이다. 함수는 인수의 수나 그 타입을 알지 못한다. 위 함수는 타입이 int일 것이며, 인수의 수가 첫 번째 인수로 전달될 것이라고 예상한다 (이는 자주 사용되지만 언어나 컴파일러에 의해 강제되지는 않는다). 다른 경우, 예를 들어 printf에서는 인수의 수와 타입이 포맷 문자열에서 파악된다. 두 경우 모두, 이는 프로그래머가 올바른 정보를 제공하는 것에 달려 있다. (대안으로, 매개변수 목록의 끝을 나타내기 위해 NULL 또는 nullptr과 같은 센티넬 값이 사용될 수 있다.) 함수가 믿는 것보다 적은 인수가 전달되거나 인수의 타입이 올바르지 않으면, 메모리의 잘못된 영역을 읽을 수 있고 포맷 스트링 공격과 같은 취약점으로 이어질 수 있다. 시스템에 따라 NULL을 센티넬로 사용하는 것조차 그러한 문제를 야기할 수 있다; nullptr 또는 올바른 대상 타입의 전용 null 포인터가 이를 피하는 데 사용될 수 있다.
stdarg.h는 va_list 타입을 선언하고, va_start, va_arg, va_copy, va_end의 네 가지 매크로를 정의한다. va_start 및 va_copy의 각 호출은 해당 va_end 호출과 일치해야 한다. 가변 인자를 다룰 때, 함수는 일반적으로 매크로에 의해 조작될 va_list 타입의 변수(예시에서는 ap)를 선언한다.
va_start는 두 개의 인수를 받는데,va_list객체와 함수의 마지막 매개변수(생략 부호 앞의 매개변수; 매크로는 이를 사용하여 위치를 파악한다)에 대한 참조이다. C23에서는 두 번째 인수가 더 이상 필요하지 않으며, 가변 인자 함수는 생략 부호 앞에 명명된 매개변수가 더 이상 필요하지 않을 것이다.[5][6] 이는va_arg또는va_copy에서 사용할va_list객체를 초기화한다. 컴파일러는 일반적으로 참조가 올바르지 않으면(예: 마지막 매개변수가 아닌 다른 매개변수에 대한 참조, 또는 완전히 다른 객체에 대한 참조) 경고를 발행하지만, 컴파일이 정상적으로 완료되는 것을 막지는 않는다.va_arg는 두 개의 인수를 받는데,va_list객체(이전에 초기화됨)와 타입 서술자이다. 이는 다음 가변 인수로 확장되며, 지정된 타입을 갖는다.va_arg의 연속적인 호출은 각 가변 인자를 차례로 처리할 수 있도록 한다. 타입이 올바르지 않거나 다음 가변 인자가 없으면 지정되지 않은 동작이 발생한다.va_end는 하나의 인수, 즉va_list객체를 받는다. 이는 정리하는 역할을 한다. 예를 들어, 가변 인자를 두 번 이상 스캔하고 싶다면, 프로그래머는va_end를 호출한 다음 다시va_start를 호출하여va_list객체를 다시 초기화할 것이다.va_copy는 두 개의 인수를 받는데, 둘 다va_list객체이다. 이는 두 번째 객체(초기화되어야 함)를 첫 번째 객체로 복제한다. "가변 인자를 두 번 이상 스캔"하는 예시로 돌아가서, 이는 첫 번째va_list에 대해va_start를 호출한 다음,va_copy를 사용하여 이를 두 번째va_list로 복제함으로써 달성될 수 있다. 첫 번째va_list와va_arg를 사용하여 가변 인자를 처음 스캔한 후(va_end로 처리함), 프로그래머는 두 번째va_list와va_arg를 사용하여 가변 인자를 두 번째 스캔할 수 있다. 포함하는 함수가 반환되기 전에 복제된va_list에 대해서도va_end가 호출되어야 한다.
C#에서
C#는 params 키워드를 사용하여 가변 인자 함수를 설명한다. 인수에 대한 타입이 제공되어야 하지만, object[]는 모든 것을 포괄하는 타입으로 사용될 수 있다. 호출 지점에서는 인수를 하나씩 나열하거나 필요한 요소 타입을 가진 기존 배열을 전달할 수 있다. 가변 인자 형식을 사용하는 것은 후자에 대한 신택틱 슈거이다.
using System;
class Program
{
static int Foo(int a, int b, params int[] args)
{
// Return the sum of the integers in args, ignoring a and b.
int sum = 0;
foreach (int i in args)
sum += i;
return sum;
}
static void Main(string[] args)
{
Console.WriteLine(Foo(1, 2)); // 0
Console.WriteLine(Foo(1, 2, 3, 10, 20)); // 33
int[] manyValues = new int[] { 13, 14, 15 };
Console.WriteLine(Foo(1, 2, manyValues)); // 42
}
}
C++에서
C++의 기본 가변 인자 기능은 C의 기능과 거의 동일하다. C++26 이전에는 생략 부호 앞의 쉼표를 생략할 수 있었다. C++는 명명된 매개변수가 없는 가변 인자 함수를 허용하지만, va_start가 함수의 마지막 고정 인수의 이름을 요구하므로 해당 인수에 접근할 방법은 제공하지 않는다.
import std;
// before C++26, my_printf(const char* fmt...) was legal
void my_printf(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
while (*fmt != '\0') {
if (*fmt == 'd') {
int i = va_arg(args, int);
std::println("{}", i);
} else if (*fmt == 'c') {
// note automatic conversion to integral type
int c = va_arg(args, int);
std::println("{}", static_cast<char>(c));
} else if (*fmt == 'f') {
double d = va_arg(args, double);
std::println("{}", d);
}
++fmt;
}
va_end(args);
}
int main(int argc, char* argv[]) {
my_printf("dcff", 3, 'a', 1.999, 42.5);
}
가변 인자 템플릿 (매개변수 팩)은 C++에서 언어 내장 폴드 표현식과 함께 사용될 수도 있다.
import std;
template <typename... Ts>
void fooPrint(Ts... args) {
((std::print("{} ", args)), ...);
}
int main(int argc, char* argv[]) {
fooPrint(1, 3.14f); // 1 3.14
fooPrint("Foo", 'b', true, nullptr); // Foo b true nullptr
}
CERT 코딩 표준 for C++는 오용 위험이 낮기 때문에 C++에서 C 스타일 가변 인자 함수보다 가변 인자 템플릿 (매개변수 팩)을 사용하는 것을 강력히 선호한다.[7] 가변 인자 템플릿은 자바 스타일의 자료형 안전 가변 매개변수를 달성하는 유일한 방법이다.
포트란에서
포트란 90 개정 이후, 포트란 함수나 서브루틴은 선택적 인수를 받아들일 수 있다.[8] 인수 목록은 여전히 고정되어 있지만, optional 속성을 가진 인수는 함수/서브루틴 호출에서 생략될 수 있다. 내장 함수 present()는 선택적 인수의 존재를 감지하는 데 사용될 수 있다. 선택적 인수는 인수 목록의 어느 곳에나 나타날 수 있다.
program test
implicit none
real :: x
!> all arguments are passed:
call foo( 1, 2, 3.0, 4, x )
!< outputs 1 \ 2 \ 3.0 \ 4 \ 6.0 (the "\" denotes a newline)
!> the last 2 arguments are omitted:
call foo( 1, 2, 3.0 )
!< outputs 1 \ 2 \ 3.0
!> the 2nd and 4th arguments are omitted: the arguments that are positioned after
!> an omitted argument must be passed with a keyword:
call foo( 1, c=3.0, e=x )
!< outputs 1 \ 3.0 \ 6.0
!> alternatively, the Fortran 2023 revision has introduced the .NIL. pseudo constant
!> to denote an omitted argument
call foo( 1, .NIL., 3.0, .NIL., x )
!< outputs 1 \ 3.0 \ 6.0
contains
!> the subroutine foo() has 2 mandatory and 3 optional arguments
subroutine foo( a, b, c, d, e )
integer, intent(in) :: a
integer, intent(in), optional :: b
real, intent(in) :: c
integer, intent(in), optional :: d
real, intent(out), optional :: e
print*, a
if (present(b)) print*, b
print*, c
if (present(d)) print*, d
if (present(e)) then
e = 2*c
print*, c
end if
end subroutine
end program
Go에서
Go의 가변 인자 함수는 어떤 수의 후행 인수와 함께 호출될 수 있다.[9] fmt.Println은 흔한 가변 인자 함수이며, 모든 것을 포괄하는 타입으로 빈 인터페이스를 사용한다.
package main
import "fmt"
// This variadic function takes an arbitrary number of ints as arguments.
func sum(nums ...int) {
fmt.Print("The sum of ", nums) // Also a variadic function.
total := 0
for _, num := range nums {
total += num
}
fmt.Println(" is", total) // Also a variadic function.
}
func main() {
// Variadic functions can be called in the usual way with individual
// arguments.
sum(1, 2) // "The sum of [1 2] is 3"
sum(1, 2, 3) // "The sum of [1 2 3] is 6"
// If you already have multiple args in a slice, apply them to a variadic
// function using func(slice...) like this.
nums := []int{1, 2, 3, 4}
sum(nums...) // "The sum of [1 2 3 4] is 10"
}
출력:
The sum of [1 2] is 3 The sum of [1 2 3] is 6 The sum of [1 2 3 4] is 10
자바에서
C#과 마찬가지로, 자바의 Object 타입은 모든 것을 포괄하는 타입으로 사용할 수 있다.
자바에서 매개변수는 생략 부호 표기법을 사용하여 가변 인자로 지정할 수 있다. 이는 본질적으로 배열과 동일하지만 배열로 래핑할 필요가 없다. 예를 들어, String... args와 String[] args는 본질적으로 동일하다.
public class Program {
// Variadic methods store any additional arguments they receive in an array.
// Consequentially, `printArgs` is actually a method with one parameter: a
// variable-length array of `String`s.
private static void printArgs(String... strings) {
for (String s : strings) {
System.out.println(s);
}
}
public static void main(String[] args) {
printArgs("hello"); // short for printArgs(new String[] {"hello"})
printArgs("hello", "world"); // short for printArgs(new String[] {"hello", "world"})
}
}
자바스크립트에서
자바스크립트는 가변 인자의 타입에 신경 쓰지 않는다.
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(3, 2)); // 5
console.log(sum()); // 0
function 키워드로 생성된 함수에서만 사용할 수 있지만, arguments 객체를 사용하여 가변 인자 함수를 만드는 것도 가능하다.
function sum() {
return Array.prototype.reduce.call(arguments, (a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(3, 2)); // 5
console.log(sum()); // 0
루아에서
루아 함수는 return 키워드를 사용하여 다른 값들과 동일한 방식으로 가변 인자를 다른 함수에 전달할 수 있다. 테이블은 Lua 버전 5.2 이상에서[10] table.unpack, 또는 Lua 5.1 이하에서[11] unpack을 사용하여 가변 인자 함수로 전달될 수 있다. 가변 인자는 가변 인자를 값으로 사용하여 테이블을 구성함으로써 테이블로 사용될 수 있다.
function sum(...) --... designates varargs
local sum=0
for _,v in pairs({...}) do --creating a table with a varargs is the same as creating one with standard values
sum=sum+v
end
return sum
end
values={1,2,3,4}
sum(5,table.unpack(values)) --returns 15. table.unpack should go after any other arguments, otherwise not all values will be passed into the function.
function add5(...)
return ...+5 --this is incorrect usage of varargs, and will only return the first value provided
end
entries={}
function process_entries()
local processed={}
for i,v in pairs(entries) do
processed[i]=v --placeholder processing code
end
return table.unpack(processed) --returns all entries in a way that can be used as a vararg
end
print(process_entries()) --the print function takes all varargs and writes them to stdout separated by newlines
파스칼에서
파스칼은 ISO 표준 7185("표준 파스칼") 및 10206("확장 파스칼")에 의해 표준화되었다.
표준화된 파스칼 형식은 특정 내장 함수(read/readLn 및 write/writeLn, 그리고 추가적으로 EP에서 readStr/writeStr)를 제외하고는 가변 인자 루틴을 지원하지 않는다.
그럼에도 불구하고, 파스칼 방언들은 가변 인자 루틴과 유사한 메커니즘을 구현한다.
델파이는 마지막 형식 매개변수와 연결될 수 있는 array of const 데이터 타입을 정의한다.
루틴 정의 내에서 array of const는 배열인 variant record의 array of TVarRec이다.[12]
앞서 언급된 record 데이터 타입의 VType 멤버는 인수의 데이터 타입을 검사하고 적절하게 처리할 수 있게 한다.
프리 파스칼 컴파일러도 델파이의 가변 인자 루틴을 지원한다.[13]
그러나 이 구현은 기술적으로 단일 인수, 즉 array를 필요로 한다.
파스칼은 배열이 동질적이어야 한다는 제한을 부과한다.
이 요구사항은 variant record를 활용하여 우회된다.
GNU 파스칼은 생략 부호(...)를 사용하여 실제 가변 인자 형식 매개변수 사양을 정의하지만, 2022년 현재 그러한 것을 사용하기 위한 이식 가능한 메커니즘은 정의되지 않았다.[14]
GNU 파스칼과 프리파스칼 모두 외부에서 선언된 함수가 생략 부호(...)를 사용하여 가변 인자 형식 매개변수 사양을 사용할 수 있도록 한다.
PHP에서
PHP는 인수가 타입이 지정되지 않은 한 가변 인자의 타입에 신경 쓰지 않는다.
function sum(...$nums): int
{
return array_sum($nums);
}
echo sum(1, 2, 3); // 6
그리고 타입이 지정된 가변 인자:
function sum(int ...$nums): int
{
return array_sum($nums);
}
echo sum(1, "a", 3); // TypeError: Argument 2 passed to sum() must be of the type int (since PHP 7.3)
파이썬에서
파이썬은 가변 인자의 타입에 신경 쓰지 않는다.
from typing import Any
def foo(a: Any, b: Any, *args: tuple[Any, ...]) -> None:
print(args) // args는 튜플 (변경 불가능한 시퀀스)이다.
if __name__ == "__main__":
foo(1, 2) // ()
foo(1, 2, 3) // (3,)
foo(1, 2, 3, "hello") // (3, "hello")
키워드 인수는 딕셔너리에 저장될 수 있다.
def bar(*args: tuple[Any, ...], **kwargs: dict[str, Any]]) -> Any:
// 함수 본문
라쿠에서
라쿠에서 가변 인자 함수를 생성하는 매개변수의 타입은 슬러피 배열 매개변수라고 알려져 있으며, 세 가지 그룹으로 분류된다:
평탄화된 슬러피
이 매개변수는 단일 별표(*)로 선언되며, 반복 가능한 요소(즉, Iterables)의 하나 이상의 계층을 해체하여 인수를 평탄화한다.
sub foo($a, $b, *@args) {
say @args.perl;
}
foo(1, 2) # []
foo(1, 2, 3) # [3]
foo(1, 2, 3, "hello") # [3 "hello"]
foo(1, 2, 3, [4, 5], [6]); # [3, 4, 5, 6]
비평탄화된 슬러피
이 매개변수들은 두 개의 별표(**)로 선언되며, 목록 내의 반복 가능한 인수를 평탄화하지 않고 인수를 거의 있는 그대로 유지한다:
sub bar($a, $b, **@args) {
say @args.perl;
}
bar(1, 2); # []
bar(1, 2, 3); # [3]
bar(1, 2, 3, "hello"); # [3 "hello"]
bar(1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]]
문맥적 슬러피
이 매개변수들은 더하기(+) 기호로 선언되며, 문맥에 따라 슬러피 인수를 처리하는 방법을 결정하는 "단일 인수 규칙"을 적용한다. 간단히 말해서, 단일 인수만 전달되고 그 인수가 반복 가능하면, 그 인수는 슬러피 매개변수 배열을 채우는 데 사용된다. 다른 경우에는 +@가 **@처럼 작동한다(즉, 비평탄화된 슬러피).
sub zaz($a, $b, +@args) {
say @args.perl;
}
zaz(1, 2); # []
zaz(1, 2, 3); # [3]
zaz(1, 2, 3, "hello"); # [3 "hello"]
zaz(1, 2, [4, 5]); # [4, 5], 단일 인수가 배열을 채운다
zaz(1, 2, 3, [4, 5]); # [3, [4, 5]], **@처럼 작동한다
zaz(1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]], **@처럼 작동한다
루비에서
루비는 가변 인자의 타입에 신경 쓰지 않는다.
def foo(*args)
print args
end
foo(1)
# prints `[1]=> nil`
foo(1, 2)
# prints `[1, 2]=> nil`
러스트에서
러스트는 함수에서 가변 인수를 지원하지 않는다. 대신, 가변 인수를 지원하는 매크로를 사용한다.[15] 이것이 println!이 함수가 아닌 매크로인 이유이다. 매크로는 형식을 지정하기 위해 가변 인수를 받기 때문이다.
macro_rules! calculate {
// The pattern for a single `eval`
(eval $e:expr) => {{
{
let val: usize = $e; // Force types to be integers
println!("{} = {}", stringify!{$e}, val);
}
}};
// Decompose multiple `eval`s recursively
(eval $e:expr, $(eval $es:expr),+) => {{
calculate! { eval $e }
calculate! { $(eval $es),+ }
}};
}
fn main() {
calculate! {\
eval 1 + 2,
eval 3 + 4,
eval (2 * 3) + 1
}
}
러스트는 c_variadic 기능 스위치를 통해 C의 가변 시스템과 상호작용할 수 있다. 다른 C 인터페이스와 마찬가지로, 이 시스템은 러스트에게 unsafe로 간주된다.[16]
스칼라에서
object Program {
// Variadic methods store any additional arguments they receive in an array.
// Consequentially, `printArgs` is actually a method with one parameter: a
// variable-length array of `String`s.
private def printArgs(strings: String*): Unit = {
strings.foreach(println)
}
def main(args: Array[String]): Unit = {
printArgs("hello"); // short for printArgs(["hello"])
printArgs("hello", "world"); // short for printArgs(["hello", "world"])
}
}
스위프트에서
스위프트는 가변 인자의 타입에 신경 쓰지만, 모든 것을 포괄하는 Any 타입을 사용할 수 있다.
func greet(timeOfTheDay: String, names: String...) {
// here, names is [String]
print("Looks like we have \(names.count) people")
for name in names {
print("Hello \(name), good \(timeOfTheDay)")
}
}
greet(timeOfTheDay: "morning", names: "Joseph", "Clara", "William", "Maria")
// Output:
// Looks like we have 4 people
// Hello Joseph, good morning
// Hello Clara, good morning
// Hello William, good morning
// Hello Maria, good morning
Tcl에서
Tcl 프로시저 또는 람다는 마지막 인수가 args일 때 가변 인자이다. 이것은 나머지 모든 인수의 목록(비어 있을 수도 있음)을 포함할 것이다. 이 패턴은 많은 다른 프로시저와 유사한 메서드에서 흔하다.[17][18]
proc greet {timeOfTheDay args} {
puts "Looks like we have [llength $args] people"
foreach name $args {
puts "Hello $name, good $timeOfTheDay"
}
}
greet "morning" "Joseph" "Clara" "William" "Maria"
# Output:
# Looks like we have 4 people
# Hello Joseph, good morning
# Hello Clara, good morning
# Hello William, good morning
# Hello Maria, good morning
Remove ads
같이 보기
- 자바 프로그래밍 언어의 가변 인수
- 가변 매크로 (C 프로그래밍 언어)
- 가변 인자 템플릿
각주
외부 링크
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads