program tip

기술적으로 가변 함수는 어떻게 작동합니까?

radiobox 2020. 12. 31. 08:10
반응형

기술적으로 가변 함수는 어떻게 작동합니까? printf는 어떻게 작동합니까?


va_arg나만의 가변 함수를 작성 하는 사용할 수 있다는 것을 알고 있지만 가변 함수는 내부에서, 즉 어셈블리 명령 수준에서 어떻게 작동합니까?

예를 들어 printf가변 개수의 인수를받는 것이 어떻게 가능 합니까?


* 예외없이 규칙은 없습니다. C / C ++ 언어는 없지만이 질문에 모두 답할 수 있습니다.

* 참고 : 원래 printf 함수가 출력하는 동안 변수 매개 변수를 숫자로 취할 수있는 방법에 대한 답변입니다 . ,하지만 질문자에게 적용되지 않은 것 같습니다.


C 및 C ++ 표준에는 작동 방식에 대한 요구 사항이 없습니다. 준수하는 컴파일러는 체인 된 목록 std::stack<boost::any>또는 심지어 마법의 조랑말 먼지 (@Xeo의 의견에 따라)를 후드 아래에 내보내 도록 결정할 수 있습니다 .

그러나 CPU 레지스터에서 인수를 인라인하거나 전달하는 것과 같은 변환이 논의 된 코드를 남기지 않을 수 있지만 일반적으로 다음과 같이 구현됩니다.

이 답변은 아래 영상에서 아래쪽으로 성장하는 스택을 구체적으로 설명합니다. 또한이 답변은 체계를 보여주기위한 단순화입니다 ( https://en.wikipedia.org/wiki/Stack_frame 참조 ).

고정되지 않은 수의 인수로 함수를 호출하는 방법

이는 기본 머신 아키텍처에 모든 스레드에 대해 소위 "스택"이 있기 때문에 가능합니다. 스택은 함수에 인수를 전달하는 데 사용됩니다. 예를 들어 다음과 같은 경우 :

foobar("%d%d%d", 3,2,1);

그런 다음 이것은 이와 같은 어셈블러 코드로 컴파일됩니다 (예시적이고 개략적으로 실제 코드는 다를 수 있습니다). 인수는 오른쪽에서 왼쪽으로 전달됩니다.

push 1
push 2
push 3
push "%d%d%d"
call foobar

이러한 푸시 작업은 스택을 채 웁니다.

              []   // empty stack
-------------------------------
push 1:       [1]  
-------------------------------
push 2:       [1]
              [2]
-------------------------------
push 3:       [1]
              [2]
              [3]  // there is now 1, 2, 3 in the stack
-------------------------------
push "%d%d%d":[1]
              [2]
              [3]
              ["%d%d%d"]
-------------------------------
call foobar   ...  // foobar uses the same stack!

하단 스택 요소는 "TOS"로 약칭되는 "Top of Stack"이라고합니다.

foobar기능은 이제 TOS에서 시작, 스택에 액세스하는 것, 즉 당신이 기억하는 형식 문자열은 마지막으로 밀렸다. stack스택 포인터, stack[0]TOS의 값, TOS stack[1]위의 값 등을 상상해보십시오 .

format_string <- stack[0]

... 그런 다음 format-string을 구문 분석합니다. 구문 분석하는 동안 -token을 다시 %d인식하고 각각에 대해 스택에서 하나 이상의 값을로드합니다.

format_string <- stack[0]
offset <- 1
while (parsing):
    token = tokenize_one_more(format_string)
    if (needs_integer (token)):
        value <- stack[offset]
        offset = offset + 1
    ...

물론 이것은 함수가 스택에서로드하고 제거해야하는 양을 파악하기 위해 전달 된 인수에 의존해야하는 방법을 보여주는 매우 불완전한 의사 코드입니다.

보안

사용자가 제공 한 인수에 대한 이러한 의존도 존재하는 가장 큰 보안 문제 중 하나입니다 ( https://cwe.mitre.org/top25/ 참조 ). 사용자는 문서를 읽지 않았거나 형식 문자열이나 인수 목록을 조정하는 것을 잊었거나, 평범한 사악함 등으로 인해 가변 함수를 쉽게 잘못 사용할 수 있습니다. Format String Attack을 참조하십시오 .

C 구현

C 및 C ++에서는 가변 함수가 va_list인터페이스 와 함께 사용됩니다 . 스택에 대한 푸시는 해당 언어에 내재되어 있지만 ( K + RC에서는 인수를 지정하지 않고 함수를 포워드 선언 할 수도 있지만 여전히 임의의 숫자와 종류 인수로 호출 할 수 있음) 이러한 알 수없는 인수 목록에서 읽는 것은 인터페이스됩니다. 관통 va_...-macros와 va_list기본적 저레벨 스택 프레임에 액세스 추상화 형.


Variadic 함수는 표준에 의해 정의되며 명시적인 제한이 거의 없습니다. 다음은 cplusplus.com에서 가져온 예입니다.

/* va_start example */
#include <stdio.h>      /* printf */
#include <stdarg.h>     /* va_list, va_start, va_arg, va_end */

void PrintFloats (int n, ...)
{
  int i;
  double val;
  printf ("Printing floats:");
  va_list vl;
  va_start(vl,n);
  for (i=0;i<n;i++)
  {
    val=va_arg(vl,double);
    printf (" [%.2f]",val);
  }
  va_end(vl);
  printf ("\n");
}

int main ()
{
  PrintFloats (3,3.14159,2.71828,1.41421);
  return 0;
}

가정은 대략 다음과 같습니다.

  1. 이름이 지정된 첫 번째 고정 인수가 (적어도 하나 이상) 있어야합니다. ...실제로 옳은 일을 컴파일러에게 제외하고는 아무것도하지 않습니다.
  2. The fixed argument(s) provide information about how many variadic arguments there are, by an unspecified mechanism.
  3. From the fixed argument it is possible for the va_start macro to return an object that allows arguments to be retrieved. The type is va_list.
  4. From the va_list object it is possible for va_arg to iterate over each variadic argument, and coerce its value it into a compatible type.
  5. Something weird might have happened in va_start so va_end makes things right again.

In the most usual stack-based situation, the va_list is merely a pointer to the arguments sitting on the stack, and va_arg increments the pointer, casts it and dereferences it to a value. Then va_start initialises that pointer by some simple arithmetic (and inside knowledge) and va_end does nothing. There is no strange assembly language, just some inside knowledge of where things lie on the stack. Read the macros in the standard headers to find out what that is.

Some compilers (MSVC) will require a specific calling sequence, whereby the caller will release the stack rather than the callee.

Functions like printf work exactly like this. The fixed argument is a format string, which allows the number of arguments to be calculated.

Functions like vsprintf pass the va_list object as a normal argument type.

If you need more or lower level detail, please add to the question.

ReferenceURL : https://stackoverflow.com/questions/23104628/technically-how-do-variadic-functions-work-how-does-printf-work

반응형