source

중첩 함수 구현

lovecheck 2023. 10. 29. 19:48
반응형

중첩 함수 구현

최근에 gcc에서 nested function의 정의가 가능하다는 것을 알게 되었습니다.제 생각에 이것은 멋진 기능이지만 어떻게 구현해야 할지 궁금합니다.

컨텍스트 포인터를 숨겨진 인수로 전달함으로써 중첩 함수의 직접 호출을 구현하는 것은 확실히 어렵지 않지만, gcc는 또한 이 포인터를 중첩 함수로 전달하여 컨텍스트의 중첩 함수를 호출할 수 있는 임의의 다른 함수로 전달할 수 있습니다.중첩 함수를 호출하는 함수는 호출할 중첩 함수의 유형만 있기 때문에 컨텍스트 포인터를 전달할 수 없습니다.

하스켈과 같이 좀 더 복잡한 호출 규약을 가진 다른 언어들은 부분적으로 그러한 것들을 지원하는 것을 허용하지만, C에서는 그런 것을 할 방법이 없습니다.이것을 어떻게 구현할 수 있습니까?

다음은 문제를 설명하는 사례의 작은 예입니다.

int foo(int x,int(*f)(int,int(*)(void))) {
  int counter = 0;
  int g(void) { return counter++; }

  return f(x,g);
}

이 함수는 컨텍스트에서 카운터를 반환하는 함수를 호출하고 동시에 증가시키는 함수를 호출합니다.

GCC는 트램펄린이라고 불리는 것을 사용합니다.

정보 : http://gcc.gnu.org/onlinedocs/gccint/Trampolines.html

트램펄린은 중첩 함수에 대한 포인터가 필요할 때 사용하기 위해 GCC가 스택에 생성하는 코드입니다.당신의 코드에서 트램펄린은 당신이 통과하기 때문에 필요합니다.g함수 호출에 대한 매개 변수로 사용됩니다.트램펄린은 중첩 함수가 외부 함수의 변수를 참조할 수 있도록 일부 레지스터를 초기화한 다음 중첩 함수 자체로 점프합니다.트램펄린은 매우 작습니다. 트램펄린에서 떨어져 중첩된 기능의 몸체로 "튀겨" 들어갑니다.

이런 식으로 중첩 함수를 사용하려면 실행 가능한 스택이 필요하므로 요즘은 사용하지 않습니다.그것을 피할 방법이 없습니다.

트램펄린 해부:

다음은 GCC의 확장 C에 내포된 함수의 예입니다.

void func(int (*param)(int));

void outer(int x)
{
    int nested(int y)
    {
        // If x is not used somewhere in here,
        // then the function will be "lifted" into
        // a normal, non-nested function.
        return x + y;
    }
    func(nested);
}

아주 간단해서 어떻게 작동하는지 알 수 있습니다.다음은 다음과 같은 결과로 구성하는 어셈블리입니다.outer, 일부 항목 제외:

subq    $40, %rsp
movl    $nested.1594, %edx
movl    %edi, (%rsp)
leaq    4(%rsp), %rdi
movw    $-17599, 4(%rsp)
movq    %rsp, 8(%rdi)
movl    %edx, 2(%rdi)
movw    $-17847, 6(%rdi)
movw    $-183, 16(%rdi)
movb    $-29, 18(%rdi)
call    func
addq    $40, %rsp
ret

대부분의 작업이 레지스터와 상수를 스택에 쓰는 것임을 알 수 있습니다.따라서 SP+4에서 19 바이트 객체를 다음과 같은 데이터와 함께 배치할 수 있습니다(GAS 구문에서).

.word -17599.int $nested.1594.단어 - 17847.quad %rsp.단어 -.byte -29

이것은 분해기를 통해 실행하기에 충분히 쉽습니다.예를 들면$nested.1594이다.0x01234567그리고.%rsp이다.0x0123456789abcdef. 결과적인 분해는 다음에 의해 제공됩니다.objdump, 다음과 같습니다.

0: 41 bb 67 45 2301 이동 $0x1234567, %r11d6: 49 ba fc cd ab 89 67 mov $0x123456789 abc def, %r10d: 45 230110:49 fe3 rex.WB jmpq *%r11

따라서 트램펄린은 외부 기능의 스택 포인터를 다음으로 로드합니다.%r10중첩 함수의 본문으로 점프합니다.중첩 함수 본문은 다음과 같습니다.

movl    (%r10), %eax
addl    %edi, %eax
ret

보다시피, 내포된 함수는 다음과 같이 사용합니다.%r10외부 함수의 변수에 접근할 수 있습니다.

물론 트램펄린이 중첩 기능 자체보다 크다는 은 상당히 우스꽝스러운 일입니다.당신은 쉽게 더 잘할 수 있습니다.하지만 이 기능을 사용하는 사람은 그리 많지 않으며, 이렇게 하면 트램펄린은 아무리 큰 중첩 함수라도 크기(19바이트)를 그대로 유지할 수 있습니다.

마지막 노트:어셈블리 하단에는 다음과 같은 최종 지침이 있습니다.

.section .note.GNU-스택 "x", @progbits

이것은 스택을 실행 가능한 것으로 표시하도록 링커에 지시합니다.

언급URL : https://stackoverflow.com/questions/8179521/implementation-of-nested-functions

반응형