저는 현재 데이터를로드하고 간단한 작업을 수행하고 결과를 출력하기 위해 스크립트를 구문 분석하는 90 년대 후반에 작성된 일부 C++ 코드를 디버깅하고 있습니다.템플릿이있는 펑터를 사용하여 printf 호출 segfaults (64 비트 전용, 32 비트에서 valgrind clean)
코드를 작성한 사람들은 Functors를 사용하여 구문 분석중인 파일에서 문자열 키워드를 실제 함수 호출에 매핑하고, 함수 인터페이스를 무수히 처리하기 위해 템플릿을 만듭니다 (최대 인수 8 개). 사용자가 스크립트에서 요청할 수 있습니다.
대부분이 부분은 잘 작동합니다. 근년에 일부 64 비트 빌드 시스템에서 segfault를하기 시작했습니다. 놀랍게도 valgrind를 통해 물건을 실행하면 오류가 "funf"내부에서 발생하는 것으로 나타났습니다.이 오류는 펑터 중 하나입니다. 이것이 작동하는 방법을 보여주는 몇 가지 코드 스 니펫이 있습니다.
첫째, 구문 분석입니다 스크립트는 다음 행을 포함
printf("%5.7f %5.7f %5.7f %5.7f\n", cos(j/10), tan(j/10), sin(j/10), sqrt(j/10));
곳 왜냐하면, 황갈색, 죄와 SQRT는 또한 내가 가진 사람을 대체 할 경우이 세부 사항, 중요하지 않습니다 (libm의에 해당 펑 있습니다 고정 된 수치 값은 같은 결과를 얻는다).
printf를 호출하면 다음과 같은 방식으로 수행됩니다. 첫째, 템플릿 펑터 :를 호출
template<class R, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
class FType
{
public :
FType(const void * f) { _f = (R (*)(T1,T2,T3,T4,T5,T6,T7,T8))f; }
R operator()(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8)
{ return _f(a1,a2,a3,a4,a5,a6,a7,a8); }
private :
R (*_f)(T1,T2,T3,T4,T5,T6,T7,T8);
};
그리고 코드는 다른 템플릿 클래스 안에 - 나는 프로토 타입 및 FTYPE를 사용하는 코드의 관련 부분 (물론 I가 넣어 약간의 추가 코드를 보여 디버깅) :
template<class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
static Token
evalF(
const void * f,
unsigned int nrargs,
T1 a1,
T2 a2,
T3 a3,
T4 a4,
T5 a5,
T6 a6,
T7 a7,
T8 a8,
vtok & args,
const Token & returnType)
{
Token result;
printf("Count: %i\n",++_count);
if(_count == 2) {
const char *fmt = *((const char **) &a1);
result = printf(fmt,a2,a3,a4,a5,a6,a7,a8);
FType<int, const void*,T2,T3,T4,T5,T6,T7,T8> f1(f);
result = f1("Hello, world.\n",a2,a3,a4,a5,a6,a7,a8);
result = f1("Hello, world2 %5.7f\n",a2,a3,a4,a5,a6,a7,a8);
result = f1(fmt,a2,a3,a4,a5,a6,a7,a8);
} else {
FType<int, T1,T2,T3,T4,T5,T6,T7,T8> f1(f);
result = f1(a1,a2,a3,a4,a5,a6,a7,a8);
}
}
(이 함수는 여러 번 호출되기 때문에) if (_count == 2) 비트가 삽입되었습니다. 정상적인 상황에서는 else 절에서 연산 만 수행합니다. 그것은 fType 생성자를 호출합니다 (어떤 템플릿은 반환 형식을 int로 사용합니다). printf의 펑터 인 "f"를 사용합니다 (디버거에서 검증 됨). F1이 구축되면,이 템플릿 모든 인수 오버로드 된 호출 연산자를 호출하고, Valgrind의 불평하기 시작 :
==29358== Conditional jump or move depends on uninitialised value(s)
==29358== at 0x92E3683: __printf_fp (printf_fp.c:406)
==29358== by 0x92E05B7: vfprintf (vfprintf.c:1629)
==29358== by 0x92E88D8: printf (printf.c:35)
==29358== by 0x5348C45: FType<int, void const*, double, double, double, double, void const*, void const*, void const*>::operator()(void const*, double, double, double, double, void const*, void const*, void const*) (Interpreter.cc:321)
==29358== by 0x51BAB6D: Token evalF<void const*, double, double, double, double, void const*, void const*, void const*>(void const*, unsigned int, void const*, double, double, double, double, void const*, void const*, void const*, std::vector<Token, std::allocator<Token> >&, Token const&) (Interpreter.cc:542)
그래서,이는 경우() 절 내부의 실험되었다. 첫째, printf를 똑같은 인자로 직접 호출 해 보았습니다. (매개 변수 a1을 가진 타입 캐스팅 트릭을 - 형식으로 - 컴파일하기 위해; 그렇지 않으면 T1이 아닌 많은 템플릿 인스턴스에 대해 불평합니다 (char *) printf 예상대로). 이것은 잘 작동합니다.
다음으로 변수가없는 대체 형식 문자열 (Hello, world)을 사용하여 f1을 호출 해 보았습니다. 이것은 또한 잘 작동합니다.
그런 다음 변수 (Hello, World2 % 5.7f) 중 하나를 추가 한 다음 위와 같이 valgrind 오류를 확인하기 시작합니다.
32 비트 시스템에서이 코드를 실행하면 valgrind clean (그렇지 않으면 동일한 버전의 glibc, gcc)입니다.
여러 Linux 시스템 (64 비트 모두)에서 실행되는 경우가 있는데, 때로는 segfault (예 : RHEL5.8/libc2.5 및 openSUSE11.2/libc-2.10.1)가 표시되는 경우가 있습니다. 예를 들어 Fedora 17 및 Ubunutu 12.04와 함께 libc2.15). 그러나 valgrind는 모든 시스템에서 항상 비슷한 방식으로 불평하며 충돌 여부에 관계없이 우연이라고 생각하게 만듭니다.
이 코드를 사용하면 누군가가이 코드를 잘못 찾으면 훨씬 행복해 지겠지만,이 모든 것이 64 비트 glibc에서 일종의 버그를 의심하게 만듭니다!
내가 가진 한 가지 점은 다양한 인수 목록을 파싱하는 것과 관련이 있다는 것입니다. 템플릿으로 얼마나 정확하게 재생됩니까? 런타임까지 형식 문자열을 알지 못하기 때문에 실제로 어떻게 작동하는지 명확하지 않습니다. 따라서 컴파일 할 때 템플릿의 특정 인스턴스를 어떻게 알 수 있습니까? 그러나 이것이 32 비트에서 모든 것이 정상적으로 보이는 이유를 설명하지는 않습니다. 의견
에 응답
업데이트이 정보가 도움이 토론을 위해 당신에게 모두 감사드립니다. 나는 아직 그것을 확인하지는 않았지만 % al 레지스터에 관한 awn의 대답은 아마도 올바른 설명 일 것이라고 생각한다. 그럼에도 불구하고 토론의 이익을 위해 다른 64 비트 시스템에서 재생할 수있는 오류를 재생산하는 완전하고 최소한의 프로그램이 있습니다. 상단에 #define _VOID_PTR
이 있으면 void * 포인터를 사용하여 원래 코드에서와 같이 함수 포인터를 전달합니다 (그리고 valgrind 오류가 발생 함). #define _VOID_PTR
을 주석으로 처리하면 대신 WhosCraig가 제안한대로 제대로 프로토 타입 화 된 함수 포인터가 사용됩니다. 이 경우의 문제는 컴파일러가 프로토 타입 불일치에 대해 불평하고 있기 때문에 단순히 int (*f)(const char *, double, double) = &printf;
을 넣을 수 없다는 것입니다 (어쩌면 저는 단지 두껍고 이것을 수행 할 방법이 있습니까?) - 이것이 원래 문제라고 생각합니다. 저자는 무효 포인터로 주위를 둘러 보려고했다). 이 특별한 경우를 다루기 위해 올바른 명시 적 인수 목록을 사용하여이 wrap_printf()
함수를 만듭니다. 이 버전의 코드를 실행하면 valgrind가 깨끗해집니다. 불행하게도 이것은 void * vs 함수 포인터 저장 문제인지 또는 % al 레지스터와 관련이 있는지 알려주지 않습니다. 나는 후자의 경우에 대부분의 증거 점을 생각하고, 나는 고정 된 인수 목록과 printf()
을 포장하는 것은 "옳은 일"할 수있는 컴파일러 강제 것으로 의심 : 사용되는 시스템 V의 AMD64 ABI에 의해
#include <cstdio>
#define _VOID_PTR // set if using void pointers to pass around function pointers
template<class R, class T1, class T2, class T3>
class FType
{
public :
#ifdef _VOID_PTR
FType(const void * f) { _f = (R (*)(T1,T2,T3))f; }
#else
typedef R (*FP)(T1,T2,T3);
FType(R (*f)(T1,T2,T3)) { _f = f; }
#endif
R operator()(T1 a1,T2 a2,T3 a3)
{ return _f(a1,a2,a3); }
private :
R (*_f)(T1,T2,T3);
};
template <class T1, class T2, class T3> int wrap_printf(T1 a1, T2 a2, T3 a3) {
const char *fmt = *((const char **) &a1);
return printf(fmt, a2, a3);
}
int main(void) {
#ifdef _VOID_PTR
void *f = (void *)printf;
#else
// this doesn't work because function pointer arguments don't match printf prototype:
// int (*f)(const char *, double, double) = &printf;
// Use this wrapper instead:
int (*f)(const char *, double, double) = &wrap_printf;
#endif
char a1[]="%5.7f %5.7f\n";
double a2=1.;
double a3=0;
FType<int, const char *, double, double> f1(f);
printf(a1,a2,a3);
f1(a1,a2,a3);
return 0;
}
542 행의 Interpreter.cc 행을 표시하십시오. 그리고 321 행에 대해서도 마찬가지입니까? –
컴파일러가 [variadic templates] (http://en.cppreference.com/w/cpp/language/parameter_pack) 및 ['std :: function'] (http : /en.cppreference.com/w/cpp/utility/functional/function). –
라인 542는 "Hello, world2"입니다. 그 대신 f1()을 호출하는 else 절의 코드를 실행하는 경우 동일한 valgrind 출력을 얻습니다. – echapin