动态创建va_list

时间:2010-07-28 08:20:45

标签: c++ qt variadic-functions

我有一个功能

void foo(int cnt, va_list ap);

我需要使用它,但要求非常严格,va_list的数量会有所不同,并且会在运行时发生变化。我想做的是:

创建一个va_list(期望char*)表格

QList<Contact*>

其中Contact是已定义的类

class Contact
{
   public:
      QString getName();
   private: 
      QString m_name;

}; 

我希望在循环va_list中填充例如:

for (int idx = 0; idx<contacts.count(); idx++)
{
    contacts.at(idx)->getName(); // this i would like to pass to va_list

}

有没有人知道如何做到这一点?

7 个答案:

答案 0 :(得分:6)

您要做的是模拟调用堆栈,以便将构造的va_list传递给foo()。这是编译器特有的(并且警告,甚至32位和64位编译器之间也存在差异)。以下代码仅适用于 娱乐目的!!! (如果它甚至适用于您的系统),它很容易破损。有了它,我使用一个平坦的内存缓冲区,并用一个计数和一堆字符串填充它。您可以使用指向字符串的指针填写它并将其移交。

它似乎适用于我的系统,Windows 7 w / Visual Studio 2008,仅适用于32位应用程序。

* BAD IDEA CODE跟随!!! *

#define PSEUDOSTACKSIZE ( sizeof(int) + 999 * sizeof(const char*) )
#pragma pack( push,1 )
union PSEUDOSTACK
{
    int count;
    char data[PSEUDOSTACKSIZE];
};
#pragma pack( pop )

void foo( int count, va_list args )
{
    for ( int i = 0; i < count; i++ )
    {
        char *s = va_arg( args, char* );
        printf( "%s\n", s);
    }
}

void bar( PSEUDOSTACK data, ... ) 
{ 
    va_list args; 
    va_start(args, data.count); 
    foo( data.count, args);
    va_end(args); 
} 
// And later on, the actual test case code.
PSEUDOSTACK barData;
barData.count = 999;
char *p = barData.data + sizeof(int);
for ( int i = 0; i < 999; i++, p += sizeof(char*) )
{
    *reinterpret_cast<char**>(p) = "ThisIsABadIdea";
}
bar( barData );

我现在会因为想到这样的想法而垂头丧气。

答案 1 :(得分:4)

......嗯...也许不便携......肯定不好......但可以解决你的问题...

  • va_list(至少对于visual c ++)只是char *的#define
  • →参数不需要在堆栈中
  • →参数只需要在内存中连续
  • →无需使用汇编程序和/或复制(请参阅我的'只是为了好玩的答案': - )
  • →无需担心清理
  • 高效!
  • 在w2k3 sp2 32bit + vc ++ 2010
  • 上测试

#include <stdarg.h>
#include <string>
#include <vector>
#include <iostream>

#define N 6 // test argument count

void foo(int n, va_list args);

int main(int, char*[])
{
    std::vector<std::wstring> strings;
    std::wstring s(L"a");
    int i(0);

    // create unique strings...
    for (; i != N; ++i)
    {
        strings.push_back(s);
        ++s.front();
    }
    foo(N, reinterpret_cast<va_list>(strings.data()));
    return 0;
}

void foo(int n, va_list args)
{
    int i(0);

    for (; i != n; ++i)
        std::wcout << va_arg(args, std::wstring) << std::endl;
}

答案 2 :(得分:3)

您的问题标记为C ++,并且有很好的方法(如流)可以完全避免在C ++中使用varargs。

这是va_args可能导致疼痛的一个很好的例子。如果您有机会更改foo的签名,那么这是您的最佳选择。采用std::vector<std::string>代替va_list只会解决您的问题。

如果foo在外部库中,您无法更改,我的下一个建议是找到一个不同的库。

如果这些都不是一个选项,似乎应该有一种方法来使用va_list递归建立调用列表,但我无法弄清楚如何使其工作。

答案 3 :(得分:1)

如果列表中的元素数量有限,我会根据元素的数量进行手动调度。

void call_foo(int count, ...) {
    va_list args;
    va_start(args, count);
    foo(count, args);
    va_end(args);
}

switch (contacts.count()) {
    case 0: return call_foo(contacts.count());
    case 1: return call_foo(contacts.count(),
                            contacts.at(0)->getName());
    case 2: return call_foo(contacts.count(),
                            contacts.at(0)->getName(),
                            contacts.at(1)->getName());
    case 3: return call_foo(contacts.count(),
                            contacts.at(0)->getName(),
                            contacts.at(1)->getName(),
                            contacts.at(2)->getName());
    default: /* ERROR HERE, ADD MORE CASES */ return call_foo(0);
}

答案 4 :(得分:0)

这取决于编译器什么是va_list类型,什么是va_start和va_end宏。你不能以标准的方式做到这一点。您必须使用特定于编译器的构造。

也许你可以改变'foo'功能?如果是这样,那么反过来 - 将va_list转换为QList并使'foo'接受QList。

//编辑

然后看看va_list类型是什么,va_start和va_end宏在特定编译器中是什么。然后以这些宏将对其起作用的方式构建你的va_list。

答案 5 :(得分:0)

&lt;只是为了好玩&gt;

  • 允许任意参数计数
  • 幸运的是sizeof(std :: wstring)是sizeof(int)
  • 的倍数
  • 在w2k3 sp2 32bit + visual c ++ 2010
  • 上测试

#include <stdarg.h>
#include <string>
#include <vector>
#include <iostream>

#define N 6 // test argument count

void foo(int n, ...);

int main(int, char*[])
{
    std::vector strings;
    std::wstring s(L"a");
    int i(0);

    // create unique strings...
    for (; i != N; ++i)
    {
        strings.push_back(s);
        ++s.front();
    }

    int n_stack_strings(N*sizeof(std::wstring)),    // space needed for strings
        n_stack(sizeof(int)+n_stack_strings);   // overall stack space...needed for cleanup

    __asm sub esp, n_stack_strings  ; reserve stack space

    std::wstring* p_stack(0);

    __asm mov p_stack, esp  ; get stack pointer

    std::wstring* p(p_stack);
    std::vector<std::wstring>::iterator string(strings.begin());

    // copy to stack
    for (; string != strings.end(); ++string, ++p)
        new (p) std::wstring(*string);
    __asm push N    ; argument count...arguments right to left (__cdecl)
    __asm call foo
    // cleanup
    for (p = p_stack; p != p_stack+N; ++p)
        p->~basic_string();
    __asm add esp, n_stack  ; caller has to cleanup the stack (__cdecl)
    return 0;
}

void foo(int n, ...)
{
    int i(0);
    va_list marker;

    va_start(marker, n);
    for (; i != n; ++i)
        std::wcout << va_arg(marker, std::wstring) << std::endl;
    va_end(marker);
}

&lt; /只是为了好玩&gt;

答案 6 :(得分:0)

您尝试使用的是allocava_list对象无法存储变量,函数调用存储变量,您只能通过va_list访问它。这些变量仅在调用期间有效,之后会得到ovverwriten。

这不起作用:

va_list func(int dummy, ...)
{
   va_list result;
   va_start(result, dummy);
   return result;
}

要在堆栈上分配内存,而不必编写可变参数函数,请使用alloca。它的工作方式或多或少类似于malloc,但您不必调用free,当您离开范围时,它会自动释放。

int * local = (int *) alloca(3 * sizeof(int));
local[0] = 10;
local[1] = 20;
local[2] = 30;

它与写作基本相同

int local[3];
local[0] = 10;
local[1] = 20;
local[2] = 30;

但是alloca 3不需要是常数。同样,你只能在封闭范围内使用它,所以从函数中返回它。

如果您想从va_list中获得的是一个列表中的多个类型,请考虑编写这样的联合:

union variant
{
    int          i;
    unsigned int u;
    float        f;
    double       d;
    const char * s;
    void *       v;
};