我正在研究用于研究架构的周期精确模拟器。我已经有一个生成程序集的交叉编译器(基于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}}的所有功能的意义上;在单一架构上工作仅对我们的目的而言足够强大),但它也不是特别引人注目。
答案 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个函数。
作为附带好处,您实际上可以检查您的缓冲区是否有足够的数据,如果用完而不是读取坏内存,则抛出或退出。