使用Clang和g ++上的压缩参数指针创建`va_list`

时间:2015-04-14 03:04:38

标签: c++ c++11 gcc clang

我正在研究用于研究架构的周期精确模拟器。我已经有一个生成程序集的交叉编译器(基于MIPS)。出于调试目的,我们有一个printf内在函数,最终,当在模拟器中运行时,调用一个内置方法,该方法可以访问在连续数组中打包的参数列表(例如将由此代码创建):

template <typename type> inline static void insert(char buffer[], size_t* i, type value) {
    memcpy(buffer+*i,&value, sizeof(type)); *i+=sizeof(type);
}
int main(int /*argc*/, char* /*argv*/[]) {
    char buffer[512]; size_t i=0;
    insert<double>(buffer,&i, 3.14);
    insert<int>(buffer,&i, 12345);
    insert<char const*>(buffer,&i, "Hello world!");

    return 0;
}

在MSVC中,可以创建va_list并像这样调用vprintf

union { va_list list; char* arguments; } un;
un.arguments = buffer;
vprintf(format_string, un.list);

目标体系结构是x86-64,它基于x86,因此产生明显正确的结果(MSVC提供的va_list只是char*的typedef。

然而,在g ++上(可能是Clang;我还没有尝试过),代码段错误。发生这种情况是因为基础类型(它的编译器提供:在gcc 4.9.2中,它看起来是__gnuc_va_list的类型定义,而__builtin_va_list又是un.list=buffer;的类型定义,可能是编译器内在的)是不同的(因为编译器错误,你只需要va_list forbodes)。


我的问题是:将这个打包参数数组转换为可在x86-64模式下用于g ++和Clang的printf 的最简洁方法是什么? >

我目前的想法是,分别解析每个格式说明符可能会更好,然后使用适当的参数将其转发给printf。这并不健全(在支持{{1}}的所有功能的意义上;在单一架构上工作仅对我们的目的而言足够强大),但它也不是特别引人注目。

2 个答案:

答案 0 :(得分:1)

对于基线答案,这里有一些简单的代码(经过合理测试,但没有保证),它实现了我提到的parse-the-format-string方法。我将其发布到公共领域。

如果有人写了一个实际上解决问题的答案(我这样做,但使用va_list;即一个更清洁的解决方案),那么我会接受这个答案。

static void printf_buffer(char const*__restrict format_string, char*__restrict argument_buffer) {
    int num_chars = 0;
    PARSE_CHAR:
        switch (*format_string) {
            case '\0': return;
            case '%': {
                int i = 1;
                char c;
                PARSE_SPECIFIER:
                    c = format_string[i++];
                    switch (c) {
                        case 'd': case 'i':
                        case 'u': case 'o': case 'x': case 'X':
                        case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A':
                        case 'c': case 's': case 'p':
                            goto PRINT_SPECIFIER;
                        case 'n':
                            assert(i==2,"\"%%n\" must contain no intermediary characters!");
                            **reinterpret_cast<int**>(argument_buffer) = num_chars;
                            argument_buffer += sizeof(int*);
                            goto DONE_SPECIFIER;
                        case '%':
                            assert(i==2,"\"%%%%\" must contain no intermediary characters!");
                            putchar('%'); ++num_chars;
                            goto DONE_SPECIFIER;
                        case '\0': assert(false,"Expected specifier before end of string!");
                        default: goto PARSE_SPECIFIER;
                    }
                PRINT_SPECIFIER: {
                    char* temp = new char[i+1];
                    strncpy(temp,format_string,i); temp[i]='\0';
                    #define PRINTBRK(TYPE) num_chars+=printf(temp,*reinterpret_cast<TYPE*>(argument_buffer)); argument_buffer+=sizeof(TYPE); break;
                    switch (c) {
                        case 'd': case 'i': PRINTBRK(int)
                        case 'u': case 'o': case 'x': case 'X': PRINTBRK(unsigned int)
                        case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': PRINTBRK(double)
                        case 'c': PRINTBRK(char)
                        case 's': PRINTBRK(char const*)
                        case 'p': PRINTBRK(void*)
                        default: assert(false,"Implementation error!");
                    }
                    #undef PRINTBRK
                    delete [] temp;
                }
                DONE_SPECIFIER:
                    format_string += i;
                    break;
            }
            default:
                putchar(*format_string); ++format_string; ++num_chars;
                break;
        }
        goto PARSE_CHAR;
}

以下是完整来源的链接,包括封闭测试:link。预期产出:

double: 3.1400, float: +3.1400, getting characters: ->, percent: %, int:      12345, string: "Hello world!"
Printed 54 characters before the marked point:
                                                      <-

答案 1 :(得分:0)

struct buffer {
  const char* ptr = 0;
  size_t count = 0;
};
template<class T>
T const* get_arg( buffer& b ) {
  T const* r = reinterpret_cast<T const*>(b.ptr);
  b.ptr += sizeof(T);
  b.count -= sizeof(T);
  return r;
}
template<class...Ts, size_t...Is>
void print( const char* format, std::index_sequence<Is...>, buffer& b ) {
  std::tuple<Ts const*...> tup;
  using discard=int[];
  (void)discard{0,(
    std::get<Is>(tup) = get_arg<Ts>(b)
  ,void(),0)...};
  printf( format, (*std::get<Is>(tup))... );
}
template<class...Ts>
void print( const char* format, buffer& b ) {
  print(format, std::index_sequence_for<Ts...>{}, b)
}

如果上面提到了<Ts...>类型和buffer类型,则会调用printf( format, ts... ),其中ts...是从buffer中提取的数据。< / p>

下一步是一次提取一个%[flags][width][.precision][length]specifier格式命令。获取仅包含其中一个命令的子字符串,并将其提供给上面的命令。

计算其中有多少*条目,并根据该数字请求那么多int条。

最后,长度和说明符被映射到C ++类型。

将运行时值映射到编译时索引(或C ++类型)所需的技术可以在其他位置看到here

这有缺点,即生成了超过150个函数。

作为附带好处,您实际上可以检查您的缓冲区是否有足够的数据,如果用完而不是读取坏内存,则抛出或退出。