具有未知类型名称的va_list - 只知道字节大小!

时间:2010-07-09 14:04:38

标签: c variadic-functions

我有一个C编程问题:我想编写一个带有变量参数列表的函数,其中每个参数的特定类型不知道 - 只有它的大小(以字节为单位)。这意味着,如果我想得到一个int-Parameter,我(之前的某个地方)定义:将有一个带sizeof(int)的参数,用回调函数xyz处理。

我的变量参数函数现在应该从其调用中收集所有信息,真正的数据类型特定操作(也可以是用户定义的数据类型)仅通过回调函数进行处理。

在标准的va_arg函数中,不可能说“从我的参数列表中获取X字节的值”,所以我想这样做。在这种情况下,我的数据类型是double,但它可以是任何其他数量的字节(甚至是可变的字节)。

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

int fn( int anz, ... )
{
        char*   p;
        int             i;
        int             j;
        va_list args;
        char*   val;
        char*   valp;
        int             size = sizeof( double ); 

        va_start( args, anz );

        val = malloc( size );

        for( i = 0; i < anz; i++ )
        {
                memcpy( val, args, size );
                args += size;

                printf( "%lf\n", *( (double*)val ) );
        }

        va_end( args );
}

int main()
{
        fn( 1, (double)234.2 );
        fn( 3, (double)1234.567, (double)8910.111213, (double)1415.161718 );

        return 0;
}

它适用于我,在Linux(gcc)下。但我现在的问题是:这真的很便携吗?或者它会在其他系统和编译器下失败吗?

我的替代方法是替换

                memcpy( val, args, size );
                args += size;

            for( j = 0; j < size; j++ )
                    val[j] = va_arg( args, char );

然而,我的价值观出错了。

有关此的任何想法或帮助吗?

6 个答案:

答案 0 :(得分:1)

这不便携,对不起。 va_list的格式取决于编译器/平台。

您必须使用va_arg()来访问va_list,并且必须将正确类型的参数传递给va_list。

但是,我相信如果你将一个正确大小的类型传递给va_arg,那就可行了。即。这种类型通常不相关,只有它的大小。但是,即使这并不能保证在所有系统中都能正常工作。

我想我会建议您重新审视您的设计,看看您是否可以找到另一种设计 - 是否有更多详细信息,说明为什么要尝试这样做以便分享?你可以将va_list传递给回调吗?

<强>更新

逐字节方法不起作用的原因可能非常复杂。就C标准而言,它不起作用的原因是因为它是不允许的 - 你只能使用va_arg来访问传递给函数的相同类型。

但我怀疑你想知道幕后发生了什么:)

第一个原因是当你读取一个“char”传递给一个函数时,它实际上会自动提升为一个int,因此作为一个int存储在va_arg中。所以当你读一个char时,你正在读一个“int”的内存,而不是一个“char” - 所以你实际上并没有一次读取一个字节。

另一个原因与对齐有关 - 在某些体系结构上(一个例子是非常新的ARM处理器),“double”必须与64位(有时甚至是128位)边界对齐。也就是说,对于指针值p,p%16(p模数16,以字节为单位 - 即128位)必须等于0.因此,当这些打包在va_arg上时,编译器可能会确保任何double值都有空格(填充)添加,所以它们只出现正确的对齐 - 但是当你一次读取一个字节的条目时,你没有考虑它的数量。

(可能还有其他原因 - 我对va_arg的内部作品并不熟悉。)

答案 1 :(得分:1)

va_list执行算术是在非便携式的最末端。您应该使用va_arg通常使用与参数相同大小的类型,可能可以在任何地方使用。为了“更接近可移植”,您应该使用无符号整数类型(uint32_t等)。

答案 2 :(得分:1)

非科学测试。

  • AIX 5.3 with GCC 4.2 - 工作
  • HP-UX 11.23 with a cc 5.56 - 不
  • Linux(SUSE 10.2)与GCC 4.1 - 不
  • 带有CC 5.9的Solaris 10 - 不

所有Linux,Solaris和HP-UX都抱怨args += size;行。

否则,很明显va_arg()包含了原因。例如。在SPARC堆栈上使用完全不同。

答案 3 :(得分:0)

在这种情况下,避免使用va_args可能会更清晰,因为您仍然最终绑定到调用点的代码中的特定数量的参数。我会去传递参数数组。

  struct arg
  {
     void* vptr;
     size_t len;
  };

  void fn( struct arg* args, int nargs );

就此而言,我也会携带数据定义,如前面在评论中提到的int或者指向结构的指针,如果它是更复杂的数据def。

答案 4 :(得分:0)

我可以建议用一个void指针数组替换变量参数吗?

答案 5 :(得分:0)

对于C99, if 我可能会认为你的所有参数都是整数类型,但可能只是宽度或符号不同,你可以使用宏。通过这种方式,你甚至可以将你的参数列表转换为一个数组而不会给调用它的用户带来痛苦。

#define myFunc(...) myRealFunc(NARGS(__VA_ARGS__), (uintmax_t const[]){ __VA_ARGS__})

,其中

void myRealFunc(size_t len, uintmax_t const param*);

并且NARGS是一个宏,它为您提供__VA_ARGS__参数的长度。这样的事情可以调用,就像接收va_list的函数一样。

解释一下宏的作用:

  • 它将参数的数量放在myRealFunc
  • 的第一个参数上
  • 它创建一个正确大小的临时数组(复合文字)并使用参数初始化它(全部转换为uintmax_t
  • 如果NARGS正确完成,则为此 仅在。评估您的参数列表 预处理时间,作为标记,和 不是在运行时间。所以你的参数 不是运行时评估超过 一旦

现在你的回调函数将由myRealFunc通过使用你想放置的任何魔法来调用。因为在调用它们时需要一个不同整数类型的参数,uintmax_t参数param[i]将被转换回该类型。