2013-03-29 3 views
1

나는 va_start(), va_arg() 매크로 뒤에 무엇이 있는지 알아 내려고하고있다. 아래의 코드는 잘 작동합니다. 나는이 같은 출력 준 예상대로c 가변 함수 혼동

#include <iostream> 
#include <cstdarg> 

void f(double a, double b, ...) 
{ 
    va_list arg; 
    va_start(arg, b); 
    double d; 
    while((d = va_arg(arg, double)) != 0) 
     { 
     std::cout << d << '\n'; 
     } 
} 

int main(int argc, char *argv[]) 
{ 
    f(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0); 
    return 0; 
} 

: 3 4 5 6 7 8 9 그럼 내가 인터넷에 내 헤더와 stdarg.h이 신비의 원인 (즉 macroses의 정의를 발견 -이 매크로를 정의를 va_arg를 (v, l)처럼 _builtin_va_arg (v, l), 마지막에 정의되어 있지 않고 stdarg.h는 아무것도 포함하지 않으므로 일부 라이브러리 ???)에 있습니다. 그럼에도 불구하고, "cstdarg"대신에 내가 쓴 :

typedef char* va_list; 

#define _INTSIZEOF(n) \ 
((sizeof(n)+sizeof(int)-1) &~(sizeof(int)-1)) 

#define va_start(ap,v) \ 
(ap = (va_list)&v + _INTSIZEOF(v)) 

#define va_arg(ap,t) \ 
(*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) 

#define va_end(ap) (ap = (va_list)0) 

출력은 1 -0.0409377 -0.0409377 4.88084e-270 4.85706e-270 4로, 이상한되었다 9. 가변 매개 변수는 마지막으로 선언 된 매개 변수 옆에 배치되지만 분명히 더 복잡한 상황이 있다고 생각했습니다. 누군가 내가 잘못되었거나 실제로 일어난 일을 공개한다면 나는 매우 기뻐할 것입니다.

+0

전에는 언급하지 않았지만 필기체 매크로는 int 형식으로 잘 작동합니다. 즉, 모든 매개 변수가 마지막으로 정의 된 인수에 상대적으로 예상대로 배치됩니다. 더블 타입 매개 변수의 경우에도 스택에 있지만 이상한 방식으로, 즉 올바른 결과를 얻을 수있는 마지막 여섯 번째 포인터를 포인터에 처음 6 추가. 어떤 종류의 패딩입니까? 더 이상, 나는 정의 된 주장이 두 번 있다는 것을 발견했다.첫 번째 인수에 시작 포인터를 놓는 것을 볼 수 있습니다. 여러분은 그것들을 얻습니다. 그리고 네 가지 물건 번호와 다시 한번 모든 매개 변수를 하나씩 얻습니다. – Nephew

+0

나는 그것이 그것을 알기에는 너무 멀리 떨어져 있지만 여전히 호기심이 있다는 것을 이해합니다. 감사. – Nephew

답변

1

va_start, va_arg 및 잊지 말고 va_end은 컴파일러에 따라 다릅니다. 다른 곳에서 가져 가서 일할 것을 기대할 수는 없습니다. 매뉴얼 페이지를 사용하여 컴파일러 엔지니어 일 경우에만 내부 작업을 이해하려고 노력하는 것이 좋습니다.

P.S : 오, 그리고 그 정의는 일반적으로 미묘한 트릭을 사용하여 매우 신비합니다.

P.S2 : 그것은, 컴파일러에 의해 알려진 그래서 내장라고 : _builtin_va_arg가 정의 귀하의 질문에 대답합니다. 컴파일러 소스에서 찾을 수 있습니다.)

+1

컴파일러의 소스에서'_builtin_va_arg'라는 * 함수 *를 찾지 못할 것입니다. 당신은 구현 정의 된 키워드를 인식하고 적절한 것을 수행하는 코드를 발견 할 것이다. '+'와'sizeof' 연산자가있는 것과 거의 같은 방식으로 컴파일러에 내장되어 있습니다. –

+0

그래, 나는 그것을 실제로 발견 할 수 없다. 이런 식으로 생각해 줘서 고마워. – Nephew

1

필기 입력 매크로는 항상 스택의 모든 인수를 전달하고 더 작은 형식을 sizeof (int)에 패딩하는 컴퓨터에서 작동합니다. 그러나 많은 기계 (요즘?)는 스택에 인수를 전달하지 않습니다. 레지스터에 인수를 전달하고 너무 많이 레지스터에 들어갈 경우에만 스택을 사용합니다.

그래서 va_args를 처리하기 위해 컴파일러는 ABI와 어떤 상황에서 어떤 인수가 배치 될지 알아야합니다. 일반적으로 va_list에 많은 수의 배열 (args를 포함 할 수있는 모든 레지스터를 담을 수있을만큼)과 여러 개의 포인터 (일반적으로 각 레지스터 유형과 스택에 대해 하나씩)가 포함됩니다 .va_start는 모든 인수를 덤프합니다. 인수 레지스터를 배열에 저장하고 포인터를 초기화하면 va_arg는 주어진 인수 유형이 전달되는 레지스터의 종류를 파악하고 해당 위치에서 값을 가져옵니다. 따라서 정수/포인터 인수에 대한 8 개 reg가있는 가정용 프로세서의 경우 플로트/더블 인수 8 REGS, 당신은 뭔가를해야 할 수도 있습니다 :

_builtin_va 기능이 모두 C로 작성 될 수 있음을
typedef struct { 
    intptr_t iregs[8], *iptr; 
    double fregs[8], *fptr; 
    char  *spptr; 
} va_list; 

inline void _builtin_va_start(va_list &ap, arg) { 
    // dump the registers might be used to pass args into ap->iregs and ap-fregs, 
    // setup iptr and fptr to point into iregs and fregs after the arguments that 
    // correspond to 'arg' and those before it. spptr points into the stack space 
    // used for arguments after regs run out 
} 
inline _builtin_va_arg(va_list &ap, type) { 
    if (type is integer or pointer) { 
     if (ap->iptr == ap->iregs+8) { 
      rv = *(type *)ap->spptr; 
      ap->spptr += sizeof(type); 
     } else { 
      rv = *ap->iptr++; 
     } 
    } else if (type is float or double) { 
     if (ap->fptr == ap->fregs+8) { 
      rv = *(type *)ap->spptr; 
      ap->spptr += sizeof(type); 
     } else { 
      rv = *ap->fptr++; 
     } 
    } else { 
     // some other type (struct?) deal with it 
    } 
} 

공지 사항, 그들은 컴파일러에 내장 할 필요가

+0

답변 해 주셔서 감사합니다 (불행히도 아직 투표 할 수 없습니다)! – Nephew