【问题标题】:Designing a better API for a variadic function为可变参数函数设计更好的 API
【发布时间】:2013-07-27 13:20:31
【问题描述】:

我想用更现代的 C++11 风格的 API 包装一个可变参数 C++ 函数。 函数是this one,来自Pin instrumentation tramework

VOID LEVEL_PINCLIENT::INS_InsertCall(INS ins,
                                     IPOINT action,
                                     AFUNPTR funptr,
                                     ...)   

AFUNPTR 声明为:

typedef VOID (*AFUNPTR)();

... 是传递 funptr 的参数列表。该列表由参数描述符 (IARG_TYPE 枚举)、可选参数值和一个终止符 IARG_END 构成,用于表示列表的结尾。

这是一个在给定指令 (ins) 之前检测函数的用法示例,它将打印 rAX 寄存器的内容:

void print_rax_and_tid(long rax, THREADID tid) {
    cout << rax << endl << tid << endl;
}

...

INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)print_rax_and_tid,
               IARG_REG_VALUE, REG_RAX, // the value of rAX register
               IARG_THREAD_ID,          // the thread id
               IARG_END)

这里我们声明我们的函数将接受一个参数,该参数将保存一个寄存器值。我们还要求工具将 rAX 寄存器的值传递给函数。

请注意,每个函数参数由一个或两个描述符参数描述:

  • (IARG_REG_VALUE, REG_RAX) 描述 (long rax)
  • (IARG_THREAD_ID) 描述 (THREADID tid)

Pin 框架设置描述符以了解在运行时传递给用户函数的内容。

还要注意,函数参数的类型不能从参数描述符中自动推断出来。在我的示例中,所有描述符都是枚举,但它们描述的是一个长的 THREADID 参数。

我想用 C++11 提供的所有东西来设计这个包装 API,可能能够传递 lambda 而不是函数指针,向参数列表添加一些类型安全性,使用可变参数模板等。

用法可能看起来像这样(但我愿意接受建议):

INS_InsertCall(ins, IPOINT_BEFORE, 
               [](long rax, THREADID tid) { cout << rax << endl << tid << endl; },
               IARG_REG_VALUE, REG_RAX,
               IARG_THREAD_ID)

【问题讨论】:

  • “插入”一个类似 lambda 的函数需要将状态存储在某个地方。如果INS_InsertCall 不会存储状态,API 将不得不要求调用者存储状态。
  • 这看起来是一个非常有趣的问题,我必须在几个小时后回到我的电脑前回来回答它
  • 如果您谈论捕获的状态,我可以接受 lambda 是无状态的。状态可以作为 lambda 的参数来处理。今天也是如此,因为 AFUNPTR 是一个静态函数指针。
  • 我不知道如何区分无状态 lambda 和有状态,也没有真正的方法可以在其他地方“存储”状态。被调用者必须将 lambda 本身存储在 INS_InsertCall 和实际调用之间(无论何时)。不是大问题。

标签: c++ api c++11 variadic-functions


【解决方案1】:

对于这个 API,我想不出太多东西:http://coliru.stacked-crooked.com/view?id=045edb71ffca8062a9e016506e4b51f7-4f34a5fd633ef9f45cb08f8e23efae0a

struct REG_VALUE {
    IARG_TYPE arg = IARG_REG_VALUE;
    REG_TYPE reg;
    REG_VALUE(REG_TYPE r) :reg(r) {}
};
template<REG_TYPE reg_v>
struct REGISTER : REG_VALUE {
    REGISTER() : REG_VALUE(reg_v) {}
};

template<class func_type, class ...param_types>
VOID InsertCall(INS ins, IPOINT action, func_type funptr, 
    param_types... param_values) 
{ INS_InsertCall(ins, action, (AFUNPTR)funptr, param_values..., IARG_END); }

然后

InsertCall(ins, IPOINT_BEFORE, print_rax_and_tid, 
    REGISTER<REG_RAX>(), IARG_THREAD_ID);

我将寄存器设置为模板类型,因此您不必拥有类型/值对,然后还将IARG_END 自动设置为自动,但除此之外,我对 API 的了解还不够多,无法理解还有什么可以自动化的。

【讨论】:

  • 感谢 Mooing Duck 的努力。我希望能够利用 lambdas,但到目前为止它似乎不可行。
  • @OmerMor:根据this answer,只有无状态 lambda 可以转换为函数指针,所以让无状态 lambda 工作的方法确实非常棘手。
【解决方案2】:

您可以使用模板方法

#include <iostream>

template <typename Runnable, typename... Types>
auto execute(Runnable f, Types... ts) -> decltype(f(ts...))
{
   return f(ts...);
}

// Some methods to test with:
void a() { std::cerr << __func__ << __LINE__ << "\n"; }
void b(int) { std::cerr << __func__ << __LINE__ << "\n"; }
void c(int, char) { std::cerr << __func__ << __LINE__ << "\n"; }
int d() { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
int e(int) { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
int f(int, char) { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
int g() { std::cerr << __func__ << __LINE__ << "\n"; return 0; }
void g(int) { std::cerr << __func__ << __LINE__ << "\n"; }

int main()
{
   int tmp = 1;
   char tmp_2 = '0';
   execute(a);
   execute(b, tmp);
   execute(c, tmp, tmp_2);
   execute(d);
   execute(e, tmp);
   execute(f, tmp, tmp_2);
   execute([](int){ std::cerr << __func__ << __LINE__ << "\n"; }, 0);
   execute(b); // This won't compile, as too few arguments provided.
   execute<int()>(g); // Explicit template instantiation needed (typename Runnable only)
   execute<void(int)>(g, 0); // Explicit template instantiation needed (typename Runnable only)
}

如果你想丢弃你的函数的返回值,模板就变得更简单了

template <typename Runnable, typename... Types>
void execute(Runnable f, Types... ts)
{
   f(ts...);
}

如您所见,这也适用于 lambda。如果函数名不明确,则无法避免显式模板实例化。

【讨论】:

  • 感谢您的详尽回答,但我可能对要求不是很清楚。 API 不应该只接受一个函数及其匹配的参数。它接受一个函数,以及描述我们应该传递给函数的元数据。有时单个值将描述一个参数,有时 2 个描述符将为单个参数完成工作,就像我的示例中的情况一样。这里 IARG_REG_VALUE 和 REG_RAX 是描述单个参数的 2 个枚举。第一个告诉我们接受一个寄存器值,第二个告诉我们它是哪个寄存器。我会改进我的问题。
  • @OmerMor 为什么要描述一个值?为什么REG_RAX 不够?
  • 我刚刚描述了现有的 API。我不介意在最终 API 中有一个描述符。查看IARG_TYPE 枚举文档以了解每个描述符的说明,它是可选的附加参数:software.intel.com/sites/landingpage/pintool/docs/58423/Pin/…。请记住,描述符类型!= 参数类型。描述符映射的类型记录在同一链接中。
  • @OmerMor 对不起,我没有得到你想要达到的目标。没有办法让编译时检查两个枚举的组合是否必须产生某种类型,如果那是你想要实现的。
  • 如果我不是很清楚,我很抱歉。您可能应该对检测工具的工作原理有所了解。它允许您在运行时使用自己的检测代码添加丰富的现有预编译应用程序二进制文件。那可以用它来测量、操作和做很多有趣的事情。例如 - 英特尔的 vTune 应用程序正在使用它来进行性能分析。仪表 API 是我想通过包装来改进的 API。更多信息在我提供的链接中。
猜你喜欢
  • 2019-04-07
  • 1970-01-01
  • 2018-10-02
  • 2010-11-28
  • 1970-01-01
  • 2012-02-27
  • 2016-04-03
  • 2015-10-06
相关资源
最近更新 更多