[英]Creating a `va_list` Using a Pointer of Packed Arguments on Clang and g++
我正在研究用於研究架構的周期精確模擬器。 我已經有一個生成程序集的交叉編譯器(基於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;
forbodes)。
我的問題是:將這個打包參數數組轉換為可在x86-64模式下由g ++和Clang使用的va_list
的最簡潔方法是什么?
我目前的想法是,分別解析每個格式說明符可能會更好,然后使用適當的參數將其轉發給printf
。 這不是那么強大(在支持printf
所有功能的意義上;在單一架構上工作僅對我們的目的而言足夠強大),但它也不是特別引人注目。
對於基線答案,這里有一些簡單的代碼(經過合理測試,但沒有保證),它實現了我提到的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;
}
以下是完整源代碼的鏈接,包括封閉測試: 鏈接 。 預期產量:
double: 3.1400, float: +3.1400, getting characters: ->, percent: %, int: 12345, string: "Hello world!"
Printed 54 characters before the marked point:
<-
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
提取的數據。
下一步是一次提取一個%[flags][width][.precision][length]specifier
格式命令。 獲取僅包含其中一個命令的子字符串,並將其提供給上面的命令。
計算那里有多少*
條目,並根據該數字要求那么多的int
。
最后,length和說明符被映射到C ++類型。
所需的技術映射運行時的值來編譯時間索引(或C ++類型)中可以看出這里其他的斑點中。
這有一個缺點,即產生了超過150個函數。
作為附帶好處,您實際上可以檢查緩沖區是否有足夠的數據,如果用完而不是讀取壞內存,則拋出或退出。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.