C语言 用数组调用vararg函数?

iyfjxgzm  于 2022-12-02  发布在  其他
关注(0)|答案(5)|浏览(172)

在下面的例子中,我想把数组的内容传递给一个接收变量的函数。
换句话说,我想通过值将foo的内容传递给printf,从而在堆栈上传递这些参数。

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

void main()
{
    int foo[] = {1,2,3,4};

    printf("%d, %d, %d, %d\n", foo);
}

我知道这个例子看起来很愚蠢,因为我可以使用printf("%d, %d, %d, %d\n", 1,2,3,4);。想象一下,我调用的是void bar(char** a, ...),而数组是我从RS232接收的...

编辑

换句话说,我想避免这样:

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

void main()
{
    int foo[] = {1,2,3,4};

    switch(sizeof(foo))
    {
       case 1: printf("%d, %d, %d, %d\n", foo[0]); break;
       case 2: printf("%d, %d, %d, %d\n", foo[0], foo[1]); break;
       case 3: printf("%d, %d, %d, %d\n", foo[0], foo[1], foo[2]); break;
       case 4: printf("%d, %d, %d, %d\n", foo[0], foo[1], foo[2], foo[3]); break;
       ...
    }
}
4smxwvx5

4smxwvx51#

我想把foo的内容按值传递给printf,这样就可以在堆栈上传递这些参数。
你不能通过值传递数组,不能通过“普通”函数调用传递,也不能通过varargs传递(基本上,这只是一种不同的读取堆栈的方式)。
当你使用数组作为函数的参数时,被调用的函数 * 接收到的 * 是一个指针。
最简单的例子是char数组,也称为“string”。

int main()
{
    char buffer1[100];
    char buffer2[] = "Hello";
    strcpy( buffer2, buffer1 );
}

strcpy()“看到”的不是两个数组,而是两个指针:

char * strcpy( char * restrict s1, const char * restrict s2 )
{
    // Yes I know this is a naive implementation in more than one way.
    char * rc = s1;
    while ( ( *s1++ = *s2++ ) );
    return rc;
}

(This这就是为什么数组的 size 只有在数组 * 声明 * 的作用域中才是已知的。一旦传递它,它就只是一个指针,没有地方放置大小信息。)
将数组传递给varargs函数时也是如此:在堆栈上结束的是指向数组(的第一个元素)的 * 指针 *,而不是整个数组。

您 * 可以 * 通过引用传递数组 * 并在被调用的函数中 * 使用它做有用的事情,如果:

1.传递数组 *(指向数组的指针)和元素计数 *(考虑argc/argv),或者
1.呼叫方和被呼叫方就固定大小达成一致,或
1.调用者和被调用者同意以某种方式“终止”数组。

标准printf()"%s"和字符串(以'\0'结尾)执行最后一个操作,但是不具备使用int[]数组执行此操作的能力,如您的示例所示。因此,您必须编写自己的自定义printme()

在任何情况下,你都不会“按值”传递数组。如果你仔细想想,对于更大的数组,将所有元素复制到堆栈中没有多大意义。

at0kjp5o

at0kjp5o2#

如前所述,你不能直接在va_arg中通过值传递数组。如果它被打包在struct中,这是可能的。它是不可移植的,但是当实现已知时,你可以做一些事情。
这里举个例子,可能会有帮助。

void call(size_t siz, ...);

struct xx1 { int arr[1]; };
struct xx10 { int arr[10]; };
struct xx20 { int arr[20]; };

void call(size_t siz, ...)
{
  va_list va;
  va_start(va, siz);

  struct xx20 x = va_arg(va, struct xx20);
  printf("HEXDUMP:%s\n", HEXDUMP(&x, siz));
  va_end(va);
}

int main(void)
{
  struct xx10 aa = { {1,2,3,4,5,[9]=-1}};
  struct xx20 bb = { {[10]=1,2,3,4,5,[19]=-1}};
  struct xx1  cc = { {-1}};

  call(sizeof aa, aa);
  call(sizeof bb, bb);
  call(sizeof cc, cc);
 }

将打印以下内容(HEXDUMP()是我的调试函数之一,它的作用显而易见)。

HEXDUMP:
  0x7fff1f154160:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ................
  0x7fff1f154170:05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  0x7fff1f154180:00 00 00 00 ff ff ff ff                         ........

HEXDUMP:
  0x7fff1f154160:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  0x7fff1f154170:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  0x7fff1f154180:00 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 ................
  0x7fff1f154190:03 00 00 00 04 00 00 00 05 00 00 00 00 00 00 00 ................
  0x7fff1f1541a0:00 00 00 00 00 00 00 00 00 00 00 00 ff ff ff ff ................

在使用gcc 5.1编译的Linux x86_64和使用gcc 3.4编译的Solaris SPARC9上进行测试
我不知道它是否有用,但它可能是一个开始。正如可以看到的,在函数va_arg中使用最大的struct数组允许处理更小的数组,如果大小已知的话。但是要小心,它可能充满了未定义的行为(例如,如果调用的函数的结构体数组大小小于4int,则它在Linux x86_64上不起作用,因为该结构体是通过寄存器传递的,不是作为堆栈上数组,而是在您的嵌入式处理器上,它可能会工作)。

72qzrwbm

72qzrwbm3#

简短回答:不,你做不到,这不可能。
稍长的回答:好吧,也许你能做到,但这是非常棘手的。你基本上是试图用一个直到运行时才知道的参数列表来调用函数。有一些库可以帮助你动态地构造参数列表并用它们来调用函数;一个库是libffi:https://sourceware.org/libffi/
另请参见C FAQ列表中的问题15.13:How can I call a function with an argument list built up at run time?
另请参阅这些先前的Stackoverflow问题:
C late binding with unknown arguments
How to call functions by their pointers passing multiple arguments in C?
Calling a variadic function with an unknown number of parameters

qyyhg6bp

qyyhg6bp4#

看这个例子,从我的代码中。这是一个简单的方法。

void my_printf(char const * frmt, ...)
{
    va_list argl;
    unsigned char const * tmp;
    unsigned char chr;
    va_start(argl,frmt);
    while ((chr = (unsigned char)*frmt) != (char)0x0) {
        frmt += 1;
        if (chr != '%') {
            dbg_chr(chr);
            continue;
        }
        chr = (unsigned char)*frmt;
        frmt += 1;
        switch (chr) {
        ...
        case 'S':
            tmp = va_arg(argl,unsigned char const *);
            dbg_buf_str(tmp,(uint16_t)va_arg(argl,int));
            break;
        case 'H':
            tmp = va_arg(argl,unsigned char const *);
            dbg_buf_hex(tmp,(uint16_t)va_arg(argl,int));
            break;
        case '%':   dbg_chr('%');   break;
        }
    }
    va_end(argl);
}

dbg_chr(uint8_t字节)将字节丢弃到USART并启用发送器。
使用示例:

#define TEST_LEN   0x4
uint8_t test_buf[TEST_LEN] = {'A','B','C','D'};
my_printf("This is hex buf: \"%H\"",test_buf,TEST_LEN);
amrnrhlw

amrnrhlw5#

如上所述,变量参数可以作为结构封装数组传递:

void logger(char * bufr, uint32_t * args, uint32_t argNum) {
    memset(buf, 0, sizeof buf);
    struct {
     uint32_t ar[16];
    } argStr;
    
        for(uint8_t a = 0; a < argNum; a += 1)
        argStr.ar[a] = args[a];
        snprintf(buf, sizeof buf, bufr, argStr);
        strcat(buf, '\0');
        pushStr(buf, strlen(buf));
}

经过测试,可与gnu C编译器配合使用

相关问题