繁体   English   中英

可变参数函数传递的时间很长,但读为va_arg(argList,int)

[英]Variadic function passing long but reading as va_arg(argList, int)

我正在将32位应用程序转换为64位的过程中,我遇到的难题之一是可变参数函数,该函数期望一个很长的时间,但可能会传递一个整数,例如,参数被硬编码为-1而不是-1L从长尺寸的64位更改为64位。 请看以下示例代码:

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

long varargsExample(int input, ...);

int main(int argc, char **argv)
{
    varargsExample(5,
    "TestInt", 0,
    /* This will fail if read as a long */
    "TestIntNegative", -1,
    "TestLong", 0L,
    "TestLongNegative", -1L,
    NULL); 
}

long varargsExample(int firstArg, ...)
{
    va_list args;
    char * name;
    long nextValue;

    va_start(args, firstArg);
    while ((name = va_arg(args, char *)) != 0)
    {
        /* If the type is changed to read in an int instead of long this works */
        nextValue = va_arg(args, long);

        printf("Got [%s] with value [%ld]\n", name, nextValue);

    }
    va_end(args);
    return 0;

}

使用GCC 64位编译时运行此命令将导致:

Got [TestInt] with value [0]
Got [TestIntNegative] with value [4294967295]
Got [TestLong] with value [0]
Got [TestLongNegative] with value [-1]

这是有道理的,因为我猜这被解释为:

0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111

因此,填充一个额外的32位来表示long,我们得到2 ^ 32-1而不是负数。 但是我想知道的是,如果我更改va_arg读取以将值读取为int值,则无论传递了int还是long值,这似乎都有效,例如:

nextValue = va_arg(args, int);

这是只是偶然发生的骇客吗,还是C规范中有一些使它行之有效的东西,以便它能够始终如一地工作? 请注意,此应用程序可在Unix / Linux和Windows上运行,而Windows上的Windows长为32位,因此我不担心函数传递的值无法用32位整数表示。 我创建了一个基本的单元测试,该测试通过INT_MIN-> INT_MAX传递给可变参数函数,该函数混合使用整数/长整数并将其读取为va_arg(args,int),并且看起来可以正常工作(在AIX,Solaris和RHEL上进行了测试),但是我不确定这是否只是在这些平台上起作用的不确定行为。

正确的解决方法是识别该函数的所有调用者,并确保它们在所有情况下都经过很长的时间,但是在没有编译器支持的情况下,很难识别这些函数的使用。 我试图作为一种替代方案,是否有一个GCC扩展名,我可以利用它来指定自定义可变参数类型检查,类似于对格式参数检查(sprintf,printf等)所做的操作。

编译器不知道可变参数函数从列表中获取哪种类型,因此它依赖于给定参数的类型。 它对参数执行默认的参数提升

对于整数类型,thes基本上将“较小”类型提升为intunsigned ,并将int / unsigned和“较大”类型不变地传递。

在获取参数时, 您有责任从可变参数获取正确的类型。 其他任何事情都会引起未定义的行为

因此,由于您没有传递long ,而是int您必须获取int 如果两种类型具有相同的表示形式,则故障很可能不会被注意到(如您所怀疑的)。

然而,另一种方式“也圆不应该工作:采取更小的int ,如果较大的long一直推。 但是,对于典型的实现,只有在获取下一个参数时才会注意到这一点。 无论哪种方式,因为这是所有不确定的行为 ,这是为了避免至关重要

gcc使用函数__attribute__ s来支持类似printf / scanf的格式字符串,但是在函数的调用者没有向调用者提供有关类型的提示时,gcc会为您提供关于编译器支持的帮助(应该怎么知道?) 。

像您介绍的那样的功能是骚乱程序的常见来源,应避免使用,因为它们容易出现印刷错误,就像您现在注意到的那样。 最好将结构数组传递给适当的函数或调用固定参数的函数。 从程序员每次为每行代码而战(无论是运行时还是大小)时,它们通常都是放射性的遗产。

一种替代方法可能是C11使用带有_Generic宏的宏,该宏针对各种参数类型调用固定大小的函数。

如果您的编译器支持C99,则可以将可变参数函数更改为接受作为复合文字提供的单个参数的函数。 这样的文字可以是未指定长度的数组,因此您可以执行以下操作:

#include <stdio.h>

typedef struct NameAndLong {
    const char* name;
    long value;
} NameAndLong;

long varargsExample(NameAndLong things[]);

int main(int argc, char **argv)
{
    varargsExample((NameAndLong[]){
      {"TestInt", 0},
      {"TestIntNegative", -1},
      {"TestLong", 0},
      {"TestSomethingBig",1L<<62},
      {"TestLongNegative", -1},
      {NULL}});
    return 0;
}

long varargsExample(NameAndLong things[])
{
    const char * name;
    long nextValue;
    while ((name = things->name) != 0)
    {
        nextValue = things++->value;
        printf("Got [%s] with value [%ld]\n", name, nextValue);
    }
    return 0;
}

当然,您必须替换所有的函数调用,但是如果您不替换任何一个函数,编译器会告诉您,因为显然存在原型不匹配的情况。 然后,您不必担心人们会添加新的呼叫而忘记添加L

就个人而言,我发现额外的配对括号有助于提高可读性,但是演员阵容有点笨重。 您可能要使用宏,但要注意以下事实:宏参数由未括在括号中的任何逗号分隔。 大括号不计数。 您可以使用可变参数解决该问题:

#define VA_EXAMPLE(...) (varargsExample((NameAndLong[]){__VA_ARGS__}))
// ...
VA_EXAMPLE({"TestInt", 0},
           {"TestIntNegative", -1},
           {"TestLong", 0},
           {NONE});

这是在ideone上运行的 (匹配编译环境,使用long long而不是long )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM