상위 질문
타임라인
채팅
관점

가변 인자 함수

위키백과, 무료 백과사전

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.hva_list 타입을 선언하고, va_start, va_arg, va_copy, va_end의 네 가지 매크로를 정의한다. va_startva_copy의 각 호출은 해당 va_end 호출과 일치해야 한다. 가변 인자를 다룰 때, 함수는 일반적으로 매크로에 의해 조작될 va_list 타입의 변수(예시에서는 ap)를 선언한다.

  1. va_start는 두 개의 인수를 받는데, va_list 객체와 함수의 마지막 매개변수(생략 부호 앞의 매개변수; 매크로는 이를 사용하여 위치를 파악한다)에 대한 참조이다. C23에서는 두 번째 인수가 더 이상 필요하지 않으며, 가변 인자 함수는 생략 부호 앞에 명명된 매개변수가 더 이상 필요하지 않을 것이다.[5][6] 이는 va_arg 또는 va_copy에서 사용할 va_list 객체를 초기화한다. 컴파일러는 일반적으로 참조가 올바르지 않으면(예: 마지막 매개변수가 아닌 다른 매개변수에 대한 참조, 또는 완전히 다른 객체에 대한 참조) 경고를 발행하지만, 컴파일이 정상적으로 완료되는 것을 막지는 않는다.
  2. va_arg는 두 개의 인수를 받는데, va_list 객체(이전에 초기화됨)와 타입 서술자이다. 이는 다음 가변 인수로 확장되며, 지정된 타입을 갖는다. va_arg의 연속적인 호출은 각 가변 인자를 차례로 처리할 수 있도록 한다. 타입이 올바르지 않거나 다음 가변 인자가 없으면 지정되지 않은 동작이 발생한다.
  3. va_end는 하나의 인수, 즉 va_list 객체를 받는다. 이는 정리하는 역할을 한다. 예를 들어, 가변 인자를 두 번 이상 스캔하고 싶다면, 프로그래머는 va_end를 호출한 다음 다시 va_start를 호출하여 va_list 객체를 다시 초기화할 것이다.
  4. va_copy는 두 개의 인수를 받는데, 둘 다 va_list 객체이다. 이는 두 번째 객체(초기화되어야 함)를 첫 번째 객체로 복제한다. "가변 인자를 두 번 이상 스캔"하는 예시로 돌아가서, 이는 첫 번째 va_list에 대해 va_start를 호출한 다음, va_copy를 사용하여 이를 두 번째 va_list로 복제함으로써 달성될 수 있다. 첫 번째 va_listva_arg를 사용하여 가변 인자를 처음 스캔한 후(va_end로 처리함), 프로그래머는 두 번째 va_listva_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... argsString[] 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/readLnwrite/writeLn, 그리고 추가적으로 EP에서 readStr/writeStr)를 제외하고는 가변 인자 루틴을 지원하지 않는다.

그럼에도 불구하고, 파스칼 방언들은 가변 인자 루틴과 유사한 메커니즘을 구현한다. 델파이는 마지막 형식 매개변수와 연결될 수 있는 array of const 데이터 타입을 정의한다. 루틴 정의 내에서 array of const배열variant recordarray 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 프로그래밍 언어)
  • 가변 인자 템플릿

각주

외부 링크

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads