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

Rya*_*anR 3 c 64-bit gcc variadic-functions

我正在将 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;

}
Run Code Online (Sandbox Code Playgroud)

使用 GCC 64 位编译时运行它会导致:

Got [TestInt] with value [0]
Got [TestIntNegative] with value [4294967295]
Got [TestLong] with value [0]
Got [TestLongNegative] with value [-1]
Run Code Online (Sandbox Code Playgroud)

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

0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111
Run Code Online (Sandbox Code Playgroud)

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

nextValue = va_arg(args, int);
Run Code Online (Sandbox Code Playgroud)

这是一个碰巧起作用的 hack 还是 C 规范中的某些内容使它能够始终如一地工作?请注意,此应用程序可在 Unix/Linux 和 Windows 上运行,其中 Windows 的长度为 32 位,因此我不担心该函数传递的值无法由 32 位整数表示。我创建了一个基本的单元测试,它通过 INT_MIN --> INT_MAX 将整数/长整数的混合并读取为 va_arg(args, int) 传递给可变参数函数,它似乎可以工作(在 AIX、Solaris 和 RHEL 上测试),但我不确定这是否只是在这些平台上工作的未定义行为。

此处的正确解决方法是识别此函数的所有调用者,并确保它们在所有情况下都传递 long,但如果没有编译器支持识别此函数,则这些函数的使用相当普遍/难以识别。如果有一个 GCC 扩展,我试图将其视为一种替代方案,我可以利用它来指定类似于格式参数检查(sprintf、printf 等)所做的自定义可变参数类型检查。

too*_*ite 6

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

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

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

因此,由于您没有通过long,但是int您必须获取int. 如果两种类型具有相同的表示形式,则故障可能会被忽视(正如您所怀疑的那样)。

但是,反过来也不应该起作用:int如果long推了较大的,则取较小的。然而,对于典型的实现,这只会在获取下一个参数时被注意到。无论哪种方式,因为这是所有不确定的行为,这是至关重要的,以避免。

gcc 有一些支持将 function __attribute__s 用于类似printf/scanf的格式字符串,但是由于您的函数的调用者没有向调用者提供有关类型的提示,因此您在编译器支持方面迷失了(它怎么知道?)。

像您所提供的功能是骚乱程序的常见来源,最好避免使用,因为它们很容易出现与您现在注意到的完全一样的排版错误。将结构数组传递给适当的函数或调用固定参数函数会更好。它们通常是程序员为每一行代码而奋斗的时代遗留下来的放射性遗产,无论是运行时还是大小。

一种替代方法可能是 C11 使用宏_Generic为各种参数类型调用固定大小的函数。