सी

2008-09-29 3 views
143

सी में एक वैरिएडिक फ़ंक्शन का आविष्कार अग्रेषित करें, क्या एक भिन्न कार्य के आमंत्रण को आगे बढ़ाना संभव है?सी

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

ढंग से मंगलाचरण अग्रेषित कर रहा है के रूप में, ऊपर स्पष्ट रूप से इस मामले में सख्ती से आवश्यक नहीं है (क्योंकि आप अन्य तरीकों से आमंत्रण लॉग ऑन कर सकता है, या vfprintf का उपयोग करें), लेकिन codebase मैं पर काम कर रहा हूँ की आवश्यकता है कुछ वास्तविक काम करने के लिए रैपर, और vfprintf के समान एक सहायक कार्य नहीं है (और जोड़ा नहीं जा सकता)।

[अद्यतन: अब तक दिए गए उत्तरों के आधार पर कुछ भ्रम प्रतीत होता है। वाक्यांश सवाल एक और तरीका करने के लिए:। सामान्य रूप में, आपको लगता है कि समारोह की परिभाषा को संशोधित किए बिना कुछ मनमाने ढंग से variadic समारोह लपेट कर सकते हैं]

+1

भयानक सवाल - सौभाग्य से मैं एक vFunc अधिभार कि एक va_list लेता है जोड़ सकते हैं। पोस्ट करने का शुक्रिया। – Gishu

उत्तर

122

आप एक समारोह vfprintf के अनुरूप है कि बहस के परिवर्तनशील के बजाय एक va_list लेता नहीं है, तो, आप इसे नहीं कर सकते। http://c-faq.com/varargs/handoff.html देखें।

उदाहरण:

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

धन्यवाद, मेरे प्रश्न का पूरी तरह उत्तर दें। – Patrick

+2

इस मुद्दे के आधार पर, इनलाइन असेंबली द्वारा आमंत्रण अभी भी इस संभावना को प्रदान करता है। – daluege

3

उपयोग vfprintf:

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

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

धन्यवाद, लेकिन सवाल से: "कोडबेस जिस पर मैं काम कर रहा हूं [...] में vfprintf के समान एक सहायक कार्य नहीं है (और जोड़ा नहीं जा सकता है)।" – Patrick

10

लगभग, <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; 
} 

ध्यान दें कि आपको सादे printf के बजाय vprintf संस्करण का उपयोग करने की आवश्यकता होगी। va_list का उपयोग किए बिना इस स्थिति में एक भिन्न कार्य को सीधे कॉल करने का कोई तरीका नहीं है।

+1

धन्यवाद, लेकिन सवाल से: "कोडबेस जिस पर मैं काम कर रहा हूं [...] में vfprintf के समान एक सहायक कार्य नहीं है (और जोड़ा नहीं जा सकता है)।" – Patrick

2

ऐसी फ़ंक्शन कॉल अग्रेषित करने का कोई तरीका नहीं है क्योंकि केवल एक स्थान जहां आप कच्चे स्टैक तत्वों को पुनर्प्राप्त कर सकते हैं my_print() में है। इस तरह की कॉल को लपेटने का सामान्य तरीका दो कार्य होना है, जो कि तर्कों को विभिन्न varargs structs में परिवर्तित करता है, और दूसरा जो वास्तव में उन structs पर काम करता है। ऐसे डबल-फ़ंक्शन मॉडल का उपयोग करके, आप में va_start() के साथ structs को प्रारंभ करके printf() रैप कर सकते हैं (उदाहरण के लिए) vfprintf() पर पास करें।

+0

तो अगर आप जिस फ़ंक्शन को लपेटना चाहते हैं उसके कार्यान्वयन को संशोधित नहीं कर सकते हैं तो आमंत्रण को आगे बढ़ाने का कोई तरीका नहीं है? – Patrick

+0

दाएं। आपकी सबसे अच्छी शर्त एक vprintf-style wrapper को जोड़ने के लिए धक्का देना है। –

44

नहीं सीधे, लेकिन यह आम बात है (और आप लगभग सार्वभौमिक मानक पुस्तकालय में मामले मिलेगा) variadic कार्यों एक varargs शैली विकल्प समारोह के साथ जोड़ों में आने के लिए। जैसे printf/vprintf

वी ... फ़ंक्शंस एक va_list पैरामीटर लेते हैं, जिसका कार्यान्वयन अक्सर कंपाइलर विशिष्ट 'मैक्रो जादू' के साथ किया जाता है, लेकिन आपको गारंटी है कि वी को कॉल करना ... एक भिन्न कार्य से स्टाइल फ़ंक्शन जैसे यह काम करेगा:

#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 शैली साथी उपलब्ध कराने पर भी विचार करना चाहिए। जैसा कि आप अपने प्रश्न से देख सकते हैं, यह आपके उपयोगकर्ताओं के लिए उपयोगी साबित हो सकता है।

+0

मुझे यकीन है कि 'myargs' चर को किसी अन्य फ़ंक्शन में पास करने से पहले आपको' va_copy' को कॉल करने की आवश्यकता है। कृपया देखें [एमएससी 3 9-सी] (https://www.securecoding.cert.org/confluence/display/c/MSC39-C.+Do+not+call+va_arg() + + + + va_list + + + पर + है + एक + अनिश्चित + मूल्य), जहां यह बताता है कि आप जो कर रहे हैं वह अनिर्धारित व्यवहार है। – aviggiano

+1

@aviggiano: नियम "va_list' पर कॉल करें 'va_arg' पर कॉल न करें जिसका अनिश्चित मूल्य है", मैं ऐसा नहीं करता क्योंकि मैं कभी भी अपने कॉलिंग फ़ंक्शन में' va_arg' का उपयोग नहीं करता हूं। कॉल के बाद 'myargs' का _value_' (इस मामले में) 'vprintf' के लिए _indeterminate_ है (यह मानते हुए कि यह हमें' va_arg' करता है)। मानक कहता है कि 'myargs'' को इसके आगे संदर्भ के पहले 'va_end' मैक्रो को पास किया जाएगा "; जो वही है जो मैं करता हूं। मुझे उस तर्क की प्रतिलिपि बनाने की आवश्यकता नहीं है क्योंकि मुझे कॉलिंग फ़ंक्शन में उनके माध्यम से पुन: प्रयास करने का कोई इरादा नहीं है। –

+0

आप सही हैं। [लिनक्स मैन] (http://linux.die.net/man/3/va_arg) पृष्ठ पुष्टि करता है कि। _ अगर 'va_arg (ap, type)' का उपयोग करने वाले फ़ंक्शन को 'एपी' पास किया गया है, तो उस क्रिया की वापसी के बाद 'एपी' का मान अपरिभाषित है ._ – aviggiano

39

सी 99 macros with variadic arguments का समर्थन करता है; अपने संकलक पर निर्भर करता है, तो आप करता है कि आप क्या चाहते हैं किसी मैक्रो घोषित करने के लिए सक्षम हो सकता है:

#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 फार्म का उपयोग करने के लिए है लपेटें, एक मौजूद होना चाहिए। विषय से हटकर शेख़ी के लिए

1

क्षमा करें, लेकिन:

मेटा समस्या यह है कि सी में varargs इंटरफ़ेस मौलिक शुरू से ही टूट गया है। यह ओवरफ्लो और अमान्य मेमोरी एक्सेस को बफर करने का निमंत्रण है क्योंकि तर्क सूची का अंत स्पष्ट अंत सिग्नल के बिना नहीं पाया जा सकता है (जो वास्तव में आलस्य से बाहर नहीं होता है)। और यह हमेशा गूढ़ कार्यान्वयन-विशिष्ट मैक्रोज़ पर भरोसा करता है, महत्वपूर्ण va_copy() मैक्रो केवल पर कुछ आर्किटेक्चर पर समर्थित है।

+0

यह एक कड़वाहट है, लेकिन यह के एंड आर के लिए निष्पक्षता में है, एक यह ध्यान रखना होगा कि यह उस समय कहीं भी बेहतर था जो उस समय आसानी से उपलब्ध था। क्या कोई अन्य भाषा किसी भी तरह का कुशल वैरैडिक फ़ंक्शन समर्थन प्रदान करती है? मेरी केवल एकमात्र बड़ी शिकायतें लगातार va_copy उपलब्धता की कमी और सामान्य vxprintf के लिए खुला समर्थन की कमी (प्रत्येक वर्ण को निर्दिष्ट फ़ंक्शन पर भेजें)। मेरे पास सी भाषा के साथ कुछ पकड़ है (उदा। पूर्णांक तुलना नियम) लेकिन va_args के साथ इतना नहीं है। – supercat

1

हाँ आप इसे कर सकते हैं, लेकिन यह कुछ हद तक बदसूरत है और आपको तर्कों की अधिकतम संख्या जाननी है। इसके अलावा यदि आप एक आर्किटेक्चर पर हैं जहां तर्क x86 (उदाहरण के लिए, पावरपीसी) जैसे स्टैक पर पारित नहीं होते हैं, तो आपको यह जानना होगा कि "विशेष" प्रकार (डबल, फ्लोट्स, altivec आदि) का उपयोग किया जाता है और यदि तो, तदनुसार उनके साथ सौदा। यह जल्दी दर्दनाक हो सकता है लेकिन यदि आप x86 पर हैं या यदि मूल फ़ंक्शन में अच्छी तरह से परिभाषित और सीमित परिधि है, तो यह काम कर सकता है। यह अभी भी एक हैक होगा, इसे डीबगिंग उद्देश्य के लिए उपयोग करें। उस के आसपास सॉफ्टवेयर बनाओ मत। वैसे भी, यहाँ 86 पर एक काम उदाहरण है:

#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 साथ तैरता उपयोग नहीं कर सकते, जीसीसी कहते हैं कि वे लेकिन कार्यक्रम दुर्घटनाओं दोगुना करने के लिए बदल रहे हैं। वह अकेला दर्शाता है कि यह समाधान एक हैक है और कोई सामान्य समाधान नहीं है। मेरे उदाहरण में मैंने माना कि तर्कों की अधिकतम संख्या 8 थी, लेकिन आप उस संख्या को बढ़ा सकते हैं। लपेटा हुआ फ़ंक्शन भी केवल पूर्णांक का उपयोग करता है लेकिन यह अन्य 'सामान्य' पैरामीटर के साथ उसी तरह काम करता है क्योंकि वे हमेशा पूर्णांक में डालते हैं। लक्ष्य कार्य उनके प्रकारों को जानेंगे लेकिन आपके मध्यस्थ रैपर को इसकी आवश्यकता नहीं है। रैपर को तर्कों की सही संख्या जानने की भी आवश्यकता नहीं है क्योंकि लक्ष्य कार्य इसे भी जानता है। उपयोगी काम करने के लिए (केवल कॉल लॉगिंग को छोड़कर), आपको शायद दोनों को जानना होगा।

9

चूंकि इस तरह के कॉल को अच्छी तरह से आगे बढ़ाना संभव नहीं है, इसलिए हमने मूल स्टैक फ्रेम की एक प्रति के साथ एक नया स्टैक फ्रेम स्थापित करके इस पर काम किया। हालांकि यह अत्यधिक अप्राप्य है और सभी प्रकार की धारणाएं, उदाहरण के लिए बनाता है। कि कोड फ्रेम पॉइंटर्स और 'मानक' कॉलिंग सम्मेलनों का उपयोग करता है।

यह हेडर फ़ाइल x86_64 और i386 (GCC) के लिए वैरिएडिक फ़ंक्शन को लपेटने की अनुमति देती है। यह फ़्लोटिंग-पॉइंट तर्कों के लिए काम नहीं करता है, लेकिन उनको समर्थन देने के लिए आगे बढ़ना चाहिए।

#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; 
} 
0

अनिवार्य रूप से तीन विकल्प हैं।

कोई इसे पास नहीं करना है, लेकिन अपने लक्षित फ़ंक्शन के विविध कार्यान्वयन का उपयोग करना और लंबवृत्त पर नहीं जाना है। दूसरा एक वैरैडिक मैक्रो का उपयोग करना है। तीसरा विकल्प वह सब सामान है जिसे मैं याद कर रहा हूं।

मैं आमतौर पर विकल्प विकल्प के साथ जाता हूं क्योंकि मुझे लगता है कि यह संभालना वास्तव में आसान है। विकल्प दो में कमी है क्योंकि वैरैडिक मैक्रोज़ को कॉल करने के लिए कुछ सीमाएं हैं।

यहां कुछ उदाहरण कोड है:

#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"); 
}