سؤال

في لغة C، هل من الممكن إعادة توجيه استدعاء دالة متغيرة؟كما في،

int my_printf(char *fmt, ...) {
    fprintf(stderr, "Calling printf with fmt %s", fmt);
    return SOMEHOW_INVOKE_LIBC_PRINTF;
}

من الواضح أن إعادة توجيه الاستدعاء بالطريقة المذكورة أعلاه ليس ضروريًا تمامًا في هذه الحالة (نظرًا لأنه يمكنك تسجيل الاستدعاءات بطرق أخرى، أو استخدام vfprintf)، لكن قاعدة التعليمات البرمجية التي أعمل عليها تتطلب أن يقوم المجمع ببعض الأعمال الفعلية، ولا 'ليس لديها (ولا يمكن إضافتها) وظيفة مساعدة مشابهة لـ vfprintf.

[تحديث:يبدو أن هناك بعض الالتباس بناءً على الإجابات التي تم تقديمها حتى الآن.لصياغة السؤال بطريقة أخرى:بشكل عام، يمكنك التفاف بعض الوظائف التعسفية المتغيرة دون تعديل تعريف تلك الوظيفة.]

هل كانت مفيدة؟

المحلول

إذا لم يكن لديك وظيفة مماثلة ل vfprintf هذا يأخذ va_list بدلاً من عدد متغير من الوسائط، لا يمكنك أن تفعل ذلك.يرى http://c-faq.com/varargs/handoff.html.

مثال:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}

نصائح أخرى

ليس بشكل مباشر، ومع ذلك فمن الشائع (وستجد هذا هو الحال عالميًا تقريبًا في المكتبة القياسية) أن تأتي الدوال المتغيرة في أزواج مع varargs وظيفة بديلة للأسلوب.على سبيل المثال printf/vprintf

الخامس...تأخذ الوظائف معلمة va_list، والتي يتم تنفيذها غالبًا باستخدام "سحر الماكرو" الخاص بالمترجم، ولكن نضمن لك أن استدعاء v...ستعمل وظيفة النمط من وظيفة متغيرة مثل هذا:

#include <stdarg.h>

int m_printf(char *fmt, ...)
{
    int ret;

    /* Declare a va_list type variable */
    va_list myargs;

    /* Initialise the va_list variable with the ... after fmt */

    va_start(myargs, fmt);

    /* Forward the '...' to vprintf */
    ret = vprintf(fmt, myargs);

    /* Clean up the va_list */
    va_end(myargs);

    return ret;
}

يجب أن يمنحك هذا التأثير الذي تبحث عنه.

إذا كنت تفكر في كتابة وظيفة مكتبة متنوعة، فيجب عليك أيضًا التفكير في إتاحة رفيق على طراز va_list كجزء من المكتبة.كما ترون من سؤالك، يمكن أن يكون مفيدًا للمستخدمين.

يدعم C99 وحدات الماكرو مع الوسائط المتغيرة;اعتمادًا على المترجم الخاص بك، قد تتمكن من الإعلان عن ماكرو يقوم بما تريد:

#define my_printf(format, ...) \
    do { \
        fprintf(stderr, "Calling printf with fmt %s\n", format); \
        some_other_variadac_function(format, ##__VA_ARGS__); \
    } while(0)

بشكل عام، الحل الأفضل هو استخدام va_list شكل الوظيفة التي تحاول تغليفها، في حالة وجودها.

تقريبا، وذلك باستخدام المرافق المتاحة في <stdarg.h>:

#include <stdarg.h>
int my_printf(char *format, ...)
{
   va_list args;
   va_start(args, format);
   int r = vprintf(format, args);
   va_end(args);
   return r;
}

لاحظ أنك سوف تحتاج إلى استخدام vprintf الإصدار بدلا من عادي printf.لا توجد طريقة لاستدعاء دالة متغيرة مباشرة في هذه الحالة دون استخدامها va_list.

نظرًا لأنه ليس من الممكن حقًا إعادة توجيه مثل هذه الاستدعاءات بطريقة لطيفة، فقد عملنا على حل هذه المشكلة عن طريق إعداد إطار مكدس جديد مع نسخة من إطار المكدس الأصلي.ومع ذلك هذا غير قابل للنقل إلى حد كبير ويقوم بجميع أنواع الافتراضات, ، على سبيل المثال.أن الكود يستخدم مؤشرات الإطار واصطلاحات الاتصال "القياسية".

يسمح ملف الرأس هذا بتغليف الوظائف المتنوعة لـ x86_64 وi386 (دول مجلس التعاون الخليجي).لا يعمل هذا مع وسيطات الفاصلة العائمة، ولكن يجب أن يكون مباشرًا للتوسع لدعم تلك الوسائط.

#ifndef _VA_ARGS_WRAPPER_H
#define _VA_ARGS_WRAPPER_H
#include <limits.h>
#include <stdint.h>
#include <alloca.h>
#include <inttypes.h>
#include <string.h>

/* This macros allow wrapping variadic functions.
 * Currently we don't care about floating point arguments and
 * we assume that the standard calling conventions are used.
 *
 * The wrapper function has to start with VA_WRAP_PROLOGUE()
 * and the original function can be called by
 * VA_WRAP_CALL(function, ret), whereas the return value will
 * be stored in ret.  The caller has to provide ret
 * even if the original function was returning void.
 */

#define __VA_WRAP_CALL_FUNC __attribute__ ((noinline))

#define VA_WRAP_CALL_COMMON()                                        \
    uintptr_t va_wrap_this_bp,va_wrap_old_bp;                        \
    va_wrap_this_bp  = va_wrap_get_bp();                             \
    va_wrap_old_bp   = *(uintptr_t *) va_wrap_this_bp;               \
    va_wrap_this_bp += 2 * sizeof(uintptr_t);                        \
    size_t volatile va_wrap_size = va_wrap_old_bp - va_wrap_this_bp; \
    uintptr_t *va_wrap_stack = alloca(va_wrap_size);                 \
    memcpy((void *) va_wrap_stack,                                   \
        (void *)(va_wrap_this_bp), va_wrap_size);


#if ( __WORDSIZE == 64 )

/* System V AMD64 AB calling convention */

static inline uintptr_t __attribute__((always_inline)) 
va_wrap_get_bp()
{
    uintptr_t ret;
    asm volatile ("mov %%rbp, %0":"=r"(ret));
    return ret;
}


#define VA_WRAP_PROLOGUE()           \
    uintptr_t va_wrap_ret;           \
    uintptr_t va_wrap_saved_args[7]; \
    asm volatile  (                  \
    "mov %%rsi,     (%%rax)\n\t"     \
    "mov %%rdi,  0x8(%%rax)\n\t"     \
    "mov %%rdx, 0x10(%%rax)\n\t"     \
    "mov %%rcx, 0x18(%%rax)\n\t"     \
    "mov %%r8,  0x20(%%rax)\n\t"     \
    "mov %%r9,  0x28(%%rax)\n\t"     \
    :                                \
    :"a"(va_wrap_saved_args)         \
    );

#define VA_WRAP_CALL(func, ret)            \
    VA_WRAP_CALL_COMMON();                 \
    va_wrap_saved_args[6] = (uintptr_t)va_wrap_stack;  \
    asm volatile (                         \
    "mov      (%%rax), %%rsi \n\t"         \
    "mov   0x8(%%rax), %%rdi \n\t"         \
    "mov  0x10(%%rax), %%rdx \n\t"         \
    "mov  0x18(%%rax), %%rcx \n\t"         \
    "mov  0x20(%%rax),  %%r8 \n\t"         \
    "mov  0x28(%%rax),  %%r9 \n\t"         \
    "mov           $0, %%rax \n\t"         \
    "call             *%%rbx \n\t"         \
    : "=a" (va_wrap_ret)                   \
    : "b" (func), "a" (va_wrap_saved_args) \
    :  "%rcx", "%rdx",                     \
      "%rsi", "%rdi", "%r8", "%r9",        \
      "%r10", "%r11", "%r12", "%r14",      \
      "%r15"                               \
    );                                     \
    ret = (typeof(ret)) va_wrap_ret;

#else

/* x86 stdcall */

static inline uintptr_t __attribute__((always_inline))
va_wrap_get_bp()
{
    uintptr_t ret;
    asm volatile ("mov %%ebp, %0":"=a"(ret));
    return ret;
}

#define VA_WRAP_PROLOGUE() \
    uintptr_t va_wrap_ret;

#define VA_WRAP_CALL(func, ret)        \
    VA_WRAP_CALL_COMMON();             \
    asm volatile (                     \
    "mov    %2, %%esp \n\t"            \
    "call  *%1        \n\t"            \
    : "=a"(va_wrap_ret)                \
    : "r" (func),                      \
      "r"(va_wrap_stack)               \
    : "%ebx", "%ecx", "%edx"   \
    );                                 \
    ret = (typeof(ret))va_wrap_ret;
#endif

#endif

في النهاية يمكنك إنهاء المكالمات مثل هذا:

int __VA_WRAP_CALL_FUNC wrap_printf(char *str, ...)
{
    VA_WRAP_PROLOGUE();
    int ret;
    VA_WRAP_CALL(printf, ret);
    printf("printf returned with %d \n", ret);
    return ret;
}

استخدم ففبرينتف:

int my_printf(char *fmt, ...) {
    va_list va;
    int ret;

    va_start(va, fmt);
    ret = vfprintf(stderr, fmt, va);
    va_end(va);
    return ret;
}

لا توجد طريقة لإعادة توجيه استدعاءات الوظائف هذه لأن الموقع الوحيد الذي يمكنك من خلاله استرداد عناصر المكدس الأولية موجود فيه my_print().الطريقة المعتادة لالتفاف المكالمات بهذه الطريقة هي أن يكون لديك وظيفتان، إحداهما تقوم فقط بتحويل الوسائط إلى الوسائط المختلفة varargs البنيات، وأخرى تعمل فعليًا على تلك البنيات.باستخدام هذا النموذج ذو الوظيفة المزدوجة، يمكنك (على سبيل المثال) التفاف printf() عن طريق تهيئة الهياكل في my_printf() مع va_start(), ، ثم قم بتمريرها إلى vfprintf().

عذرا على الطرح الخارج عن الموضوع ولكن:

المشكلة الفوقية هي أن واجهة varargs في لغة C قد تم كسرها بشكل أساسي منذ البداية.إنها دعوة لتجاوز سعة المخزن المؤقت والوصول إلى الذاكرة غير الصالحة لأنه لا يمكن العثور على نهاية قائمة الوسيطات بدون إشارة نهاية صريحة (والتي لا يستخدمها أحد حقًا بسبب الكسل).واعتمد دائمًا على وحدات ماكرو خاصة بالتنفيذ، مع دعم الماكرو الحيوي va_copy() فقط على بعض أبنية.

نعم يمكنك القيام بذلك، لكنه قبيح إلى حد ما ويجب عليك معرفة الحد الأقصى لعدد الوسائط.علاوة على ذلك، إذا كنت تستخدم بنية لا يتم فيها تمرير الوسائط على المكدس مثل x86 (على سبيل المثال، PowerPC)، فسيتعين عليك معرفة ما إذا كانت الأنواع "الخاصة" (مزدوجة، أو عائمة، أو altivec، وما إلى ذلك) مستخدمة وما إذا كان يتم استخدامها أم لا. لذا، تعامل معهم وفقًا لذلك.يمكن أن يكون الأمر مؤلمًا بسرعة ولكن إذا كنت تستخدم الإصدار x86 أو إذا كانت الوظيفة الأصلية لها محيط محدد ومحدد جيدًا، فيمكن أن تعمل.فإنه لا يزال سوف يكون الاختراق, ، استخدمه لغرض التصحيح.لا تبني برنامجًا لك حول ذلك.على أية حال، إليك مثال عملي على x86:

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

int old_variadic_function(int n, ...)
{
  va_list args;
  int i = 0;

  va_start(args, n);

  if(i++<n) printf("arg %d is 0x%x\n", i, va_arg(args, int));
  if(i++<n) printf("arg %d is %g\n",   i, va_arg(args, double));
  if(i++<n) printf("arg %d is %g\n",   i, va_arg(args, double));

  va_end(args);

  return n;
}

int old_variadic_function_wrapper(int n, ...)
{
  va_list args;
  int a1;
  int a2;
  int a3;
  int a4;
  int a5;
  int a6;
  int a7;
  int a8;

  /* Do some work, possibly with another va_list to access arguments */

  /* Work done */

  va_start(args, n);

  a1 = va_arg(args, int);
  a2 = va_arg(args, int);
  a3 = va_arg(args, int);
  a4 = va_arg(args, int);
  a5 = va_arg(args, int);
  a6 = va_arg(args, int);
  a7 = va_arg(args, int);

  va_end(args);

  return old_variadic_function(n, a1, a2, a3, a4, a5, a6, a7, a8);
}

int main(void)
{
  printf("Call 1: 1, 0x123\n");
  old_variadic_function(1, 0x123);
  printf("Call 2: 2, 0x456, 1.234\n");
  old_variadic_function(2, 0x456, 1.234);
  printf("Call 3: 3, 0x456, 4.456, 7.789\n");
  old_variadic_function(3, 0x456, 4.456, 7.789);
  printf("Wrapped call 1: 1, 0x123\n");
  old_variadic_function_wrapper(1, 0x123);
  printf("Wrapped call 2: 2, 0x456, 1.234\n");
  old_variadic_function_wrapper(2, 0x456, 1.234);
  printf("Wrapped call 3: 3, 0x456, 4.456, 7.789\n");
  old_variadic_function_wrapper(3, 0x456, 4.456, 7.789);

  return 0;
}

لسبب ما، لا يمكنك استخدام العوامات مع va_arg، يقول gcc أنه تم تحويلها إلى double لكن البرنامج يتعطل.وهذا وحده يوضح أن هذا الحل عبارة عن اختراق وأنه لا يوجد حل عام.في المثال الخاص بي، افترضت أن الحد الأقصى لعدد الوسائط هو 8، ولكن يمكنك زيادة هذا العدد.تستخدم الدالة الملتفة أيضًا الأعداد الصحيحة فقط ولكنها تعمل بنفس الطريقة مع المعلمات "العادية" الأخرى نظرًا لأنها دائمًا ما يتم تحويلها إلى أعداد صحيحة.ستعرف الوظيفة المستهدفة أنواعها ولكن غلاف الوسيط الخاص بك لا يحتاج إلى ذلك.لا يحتاج برنامج التضمين أيضًا إلى معرفة العدد الصحيح من الوسائط نظرًا لأن الوظيفة المستهدفة ستعرف ذلك أيضًا.للقيام بعمل مفيد (باستثناء مجرد تسجيل المكالمة)، ربما يتعين عليك معرفة كليهما بالرغم من ذلك.

هناك في الأساس ثلاثة خيارات.

الأول هو عدم تمريرها ولكن استخدام التنفيذ المتنوع لوظيفتك المستهدفة وعدم تمرير علامات الحذف.والآخر هو استخدام ماكرو متغير.الخيار الثالث هو كل الأشياء التي أفتقدها.

عادةً ما أختار الخيار الأول لأنني أشعر أنه من السهل حقًا التعامل مع هذا الأمر.الخيار الثاني له عيب لأن هناك بعض القيود على استدعاء وحدات الماكرو المتغيرة.

فيما يلي بعض الأمثلة على التعليمات البرمجية:

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

#define Option_VariadicMacro(f, ...)\
    printf("printing using format: %s", f);\
    printf(f, __VA_ARGS__)

int Option_ResolveVariadicAndPassOn(const char * f, ... )
{
    int r;
    va_list args;

    printf("printing using format: %s", f);
    va_start(args, f);
    r = vprintf(f, args);
    va_end(args);
    return r;
}

void main()
{
    const char * f = "%s %s %s\n";
    const char * a = "One";
    const char * b = "Two";
    const char * c = "Three";
    printf("---- Normal Print ----\n");
    printf(f, a, b, c);
    printf("\n");
    printf("---- Option_VariadicMacro ----\n");
    Option_VariadicMacro(f, a, b, c);
    printf("\n");
    printf("---- Option_ResolveVariadicAndPassOn ----\n");
    Option_ResolveVariadicAndPassOn(f, a, b, c);
    printf("\n");
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top