使用模板化函子segfaults调用printf(仅64位,32位为valgrind clean) 我目前正在调试一些在90年底编写的C++代码,它解析脚本来加载数据,执行简单操作,打印结果等。
编写代码的人使用函子将正在解析的文件中的字符串关键字映射到实际的函数调用,并对它们进行模板化(最多8个参数),以处理用户在脚本中可能请求的无数函数接口 在大多数情况下,这一切都很好,除了近年来它开始在我们的一些64位构建系统上出现故障。让我惊讶的是,通过valgrind运行程序,我发现错误似乎发生在“printf”内部,printf是上述函子之一。下面是一些代码片段来说明这是如何工作的 首先,正在分析的脚本包含以下行:使用模板化函子segfaults调用printf(仅64位,32位为valgrind clean) 我目前正在调试一些在90年底编写的C++代码,它解析脚本来加载数据,执行简单操作,打印结果等。,c++,templates,printf,functor,C++,Templates,Printf,Functor,编写代码的人使用函子将正在解析的文件中的字符串关键字映射到实际的函数调用,并对它们进行模板化(最多8个参数),以处理用户在脚本中可能请求的无数函数接口 在大多数情况下,这一切都很好,除了近年来它开始在我们的一些64位构建系统上出现故障。让我惊讶的是,通过valgrind运行程序,我发现错误似乎发生在“printf”内部,printf是上述函子之一。下面是一些代码片段来说明这是如何工作的 首先,正在分析的脚本包含以下行: printf( "%5.7f %5.7f %5.7f %5.7f\n", c
printf( "%5.7f %5.7f %5.7f %5.7f\n", cos( j / 10 ), tan( j / 10 ), sin( j / 10 ), sqrt( j / 10 ) );
其中cos、tan、sin和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);
};
在64位Linux(和许多其他Unix)使用的SystemV amd64 ABI中,具有固定参数数和可变参数数的函数具有略微不同的调用约定 引用“System V应用程序二进制接口AMD64体系结构处理器补充”草案0.99.5[2],第3.2.3章“参数传递”: 对于可能调用使用varargs或stdargs的函数的调用(无原型调用或对声明中包含省略号(…)的函数的调用)%al用作隐藏参数,以指定使用的向量寄存器数 现在,按照3步顺序:
[1]
[2] 如果您的编译器与C++11兼容,因此可以处理,并且可以重新排列参数的顺序,那么您可以执行以下操作:
template<typename F, typename ...A>
static Token evalF(vtok& args, const Token& resultType, F f, A... a)
{
Token result;
f(a...);
return result;
}
模板
静态令牌evalF(vtok和args、const-Token和resultType、F、A…A)
{
象征性结果;
f(a…);
返回结果;
}
工作正常,如果您看到,例如…您能指出解释器的哪一行吗。cc是第542行吗?第321行也是如此?您可能还想看看编译器是否支持。第542行是“Hello,world2”。如果valgrind执行调用f1()的else子句中的代码,则会得到相同的valgrind输出。目前,我们正试图尽可能远离较新的标准,因为这是一个跨平台项目(目前有各种风格的linux和osx)。但是,如果需要的话,我们将沿着这条路走下去……您应该知道:最终,这段代码将代码指针传递给
const void*
,然后将其转换回代码指针。前者是标准允许的;后者并非如此。i、 e.func ptr到void ptr是可以的,但void ptr到func ptr不是。这最终适用于代码指针和数据指针具有不同位表示的平台,并且可能解释了为什么它在32位上工作,但在64位上失败。我认为这听起来是给出证据后最合理的解释。如果在新测试代码中进入printf后立即在调试器中检查%rax的值,则该值为2;调试器指示其值在进入printf后刚刚更改。如果我先进入f1,然后进入printf,则值为0x3ff0000000000000(因此我认为这意味着%al为0),并且在进入printf后,该值没有立即更改。我认为这是相当有说服力的证据——你觉得这个测试正确吗?@echapin-Yes.%al是通过%xmm寄存器传递的参数数。你通过了2次双打铝含有2。对我来说一切似乎都很好。感谢你的建议——我认为我们在所有的构建器上都使用了足够新的gcc版本,这样的解决方案就可以实现了。然而,因为我的问题更多的是:“为什么这个代码失败了”,而不是“做这件事的更好方法是什么”,所以我选择@awn的响应作为正确的响应。
==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)
#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;
}
template<typename F, typename ...A>
static Token evalF(vtok& args, const Token& resultType, F f, A... a)
{
Token result;
f(a...);
return result;
}