【问题标题】:How can I call (not define) a function with a variable number of arguments in C?如何在 C 中调用(不定义)具有可变数量参数的函数?
【发布时间】:2011-03-20 06:17:09
【问题描述】:

有什么办法可以缩短这段代码?

long call_f(int argc, long *argv) {
  switch (argc) {
    case 0:
      return f();
      break;
    case 1:
      return f(argv[0]);
      break;
    case 2:
      return f(argv[0], argv[1]);
      break;
    case 3:
      return f(argv[0], argv[1], argv[2]);
      break;
    case 4:
      return f(argv[0], argv[1], argv[2], argv[3]);
      break;
    // ...
  }
  return -1;
}

【问题讨论】:

  • 为什么不能将参数传递给 'f' ?
  • 因为调用call_f的函数没有传递argc的常量值。
  • 我的意思是 argc 和 argv...你需要那个包装函数吗?
  • f 是您编写的函数,因此可以更改,还是 f 是其他人编写的函数,因此您必须传递可变参数?我以为你不会问这个问题,除非你不能改变f
  • 请注意,您的代码看起来不安全(f 将如何知道最后一个参数是什么)。您可能应该致电f(NULL)f(argv[0], NULL) 等。

标签: c function arguments


【解决方案1】:

我将在此处发布与 duplicated question 发布的相同答案,但您应该看看那里的讨论:

什么是 libffi?

有些程序在编译时可能不知道要传递给函数的参数。例如,解释器可能会在运行时被告知用于调用给定函数的参数的数量和类型。 'libffi' 可用于此类程序,以提供从解释程序到编译代码的桥梁。

“libffi”库为各种调用约定提供了可移植的高级编程接口。这允许程序员在运行时调用由调用接口描述指定的任何函数。

FFI 代表外部功能接口。外来函数接口是接口的通称,它允许用一种语言编写的代码调用用另一种语言编写的代码。 “libffi”库实际上只提供了功能齐全的外来函数接口的最低、机器相关层。 “libffi”之上必须存在一个层,用于处理在两种语言之间传递的值的类型转换。

‘libffi’假设你有一个指向你想要调用的函数的指针,并且你知道要传递它的参数的数量和类型,以及函数的返回类型。


历史背景

libffi 最初由 Anthony Green(SO 用户:anthony-green)开发,灵感来自 Silicon Graphics 的 Gencall 库。 Gencall 由 Gianni Mariani 开发,然后被 SGI 雇用,目的是允许按地址调用函数并为特定调用约定创建调用框架。 Anthony Green 改进了这个想法并将其扩展到其他架构和调用约定以及开源 libffi。


用 libffi 调用 pow

#include <stdio.h>
#include <math.h>
#include <ffi.h>

int main()
{
  ffi_cif     call_interface;
  ffi_type    *ret_type;
  ffi_type    *arg_types[2];

  /* pow signature */
  ret_type = &ffi_type_double;
  arg_types[0] = &ffi_type_double;
  arg_types[1] = &ffi_type_double;

  /* prepare pow function call interface */
  if (ffi_prep_cif(&call_interface, FFI_DEFAULT_ABI, 2, ret_type, arg_types) == FFI_OK)
  {
    void *arg_values[2];
    double x, y, z;

    /* z stores the return */
    z = 0;

    /* arg_values elements point to actual arguments */
    arg_values[0] = &x;
    arg_values[1] = &y;

    x = 2;
    y = 3;

    /* call pow */
    ffi_call(&call_interface, FFI_FN(pow), &z, arg_values);

    /* 2^3=8 */
    printf("%.0f^%.0f=%.0f\n", x, y, z);
  }

  return 0;
}

我认为我可以断言 libffi 是一种可移植的方式来做我所要求的,这与 Antti Haapala 的断言相反,即没有这样的方式。如果我们不能将 libffi 称为可移植技术,考虑到它在编译器和架构之间的移植/实现程度,以及哪个接口符合 C 标准,我们也不能将 C 或任何东西称为可移植的。

信息和历史摘录自:

https://github.com/atgreen/libffi/blob/master/doc/libffi.info

http://en.wikipedia.org/wiki/Libffi

【讨论】:

    【解决方案2】:

    如果您知道函数的调用约定以及它接收哪些参数,那么实际上有一种方法可以在运行时调用函数。然而,这超出了标准 C/C++ 语言范围。

    对于 x86 汇编器:

    假设如下:

    1. 您知道将函数的所有参数准备在一个固态缓冲区中,完全按照将它们打包到堆栈中的方式。
    2. 您的函数不会按值获取/返回 C++ 对象。

    您可以使用以下功能:

    int CallAnyFunc(PVOID pfn, PVOID pParams, size_t nSizeParams)
    {
        // Reserve the space on the stack
        // This is equivalent (in some sense) to 'push' all the parameters into the stack.
        // NOTE: Don't just subtract the stack pointer, better to call _alloca, because it also takes
        // care of ensuring all the consumed memory pages are accessible
        _alloca(nSizeParams);
    
        // Obtain the stack top pointer
        char* pStack;
        _asm {
            mov pStack, esp
        };
    
        // Copy all the parameters into the stack
        // NOTE: Don't use the memcpy function. Because the call to it
        // will overwrite the stack (which we're currently building)
        for (size_t i = 0; i < nSizeParams; i++)
            pStack[i] = ((char*) pParams)[i];
    
        // Call your function
        int retVal;
        _asm {
            call pfn
            // Most of the calling conventions return the value of the function (if anything is returned)
            // in EAX register
            mov retVal, eax
        };
    
        return retVal;
    }
    

    您可能需要调整此函数,具体取决于使用的调用约定

    【讨论】:

      【解决方案3】:

      不,没有任何好的方法可以做到这一点。看这里: http://c-faq.com/varargs/handoff.html

      您可以编写一个带有标记粘贴的宏来隐藏此行为,但该宏不会比此代码简单,因此只有当您有多个函数(如 f())时才值得编写,否则您必须复制此 case 语句.

      【讨论】:

        【解决方案4】:

        我不知道如何让你的代码更短,但我在你的代码中看到了这一行:

         return f();
        

        从对f 函数的下一次调用来看,似乎 f 是一个接受可变数量参数的函数。

        您可以在wikipedia 中读到:

        可变参数函数必须至少有 一个命名参数,例如,

        char *wrong(...);

        在 C 中不允许。

        基于此,也许return f(); 语句给您带来了麻烦?

        【讨论】:

        • 好眼光。 +1 票。 OP 是否可以使用 C++ 重载函数,而不是我们似乎都猜到的可变参数?
        • 不,这只是示例代码。真实代码中,没有case 0:
        【解决方案5】:

        f 是否必须接受可变数量的指向 long 的指针?你可以重写它来接受一个数组和一个计数吗?

        【讨论】:

        • 回答问题?我会选择 TMN
        • @fazo:TMN 的回答到底是什么意思?
        • 不,fsyscall。我没有将其包含在原始问题中,因为我认为这可能会使问题变得复杂。
        • 有一个答案 ny TMN.. 使用 stdarg.h
        • @fazo,具体如何使用它?调用消耗 va_list 的系统调用的变体?存在吗?
        【解决方案6】:

        你可以看看我的回答:

        Best Way to Store a va_list for Later Use in C/C++

        这似乎有效,但会吓到人们。它不能保证跨平台或便携,但它似乎至少可以在几个平台上工作。 ;)

        【讨论】:

        • 您必须调整我的系统调用方法,但可以使用指定系统调用参数的静态字符串数组来完成。如果您正在进行渗透测试,那么我构建参数缓冲区的方法将非常适合您。只需将它从堆中 bitblt 到堆栈并跳转。
        猜你喜欢
        • 1970-01-01
        • 2011-01-07
        • 1970-01-01
        • 2011-12-24
        • 1970-01-01
        • 2011-01-21
        • 1970-01-01
        • 1970-01-01
        • 2021-04-10
        相关资源
        最近更新 更多