C स्वरूपित स्ट्रिंग्स बनाना (उन्हें प्रिंट नहीं करना)


101

मेरे पास एक फ़ंक्शन है जो एक स्ट्रिंग को स्वीकार करता है, वह है:

void log_out(char *);

इसे कॉल करने में, मुझे मक्खी पर एक स्वरूपित स्ट्रिंग बनाने की आवश्यकता है जैसे:

int i = 1;
log_out("some text %d", i);

मैं एएनएसआई सी में यह कैसे करूं?


केवल, चूंकि sprintf()एक इंट रिटर्न देता है, इसका मतलब है कि मुझे कम से कम 3 कमांड लिखना होगा, जैसे:

char *s;
sprintf(s, "%d\t%d", ix, iy);
log_out(s);

इसे छोटा करने का कोई तरीका?


1
मुझे विश्वास है कि फ़ंक्शन प्रोटोटाइप वास्तव में है: बाहरी शून्य log_out (const char *, ...); क्योंकि अगर ऐसा नहीं है, तो इसके लिए कॉल गलत है (बहुत सारे तर्क)। इसे एक कॉन्स्टेंट पॉइंटर लेना चाहिए क्योंकि स्ट्रिंग को संशोधित करने के लिए log_out () का कोई कारण नहीं है। बेशक, आप कह रहे होंगे कि आप फ़ंक्शन को एक स्ट्रिंग पास करना चाहते हैं - लेकिन नहीं कर सकते। एक विकल्प तो log_out () फ़ंक्शन का एक varargs संस्करण लिखने के लिए है।
जोनाथन लेफ्लर

जवाबों:


91

स्प्रिंट का उपयोग करें ।

int sprintf ( char * str, const char * format, ... );

स्ट्रिंग के लिए स्वरूपित डेटा लिखें एक ही पाठ के साथ एक स्ट्रिंग लिखें जो कि प्रिंटफ पर प्रारूप का उपयोग करने पर मुद्रित किया जाएगा, लेकिन मुद्रित होने के बजाय, सामग्री को स्ट्रिंग द्वारा इंगित बफर में सी स्ट्रिंग के रूप में संग्रहीत किया जाता है।

पूरे परिणामी स्ट्रिंग को शामिल करने के लिए बफर का आकार काफी बड़ा होना चाहिए (एक सुरक्षित संस्करण के लिए स्निपर देखें)।

एक समाप्ति अशक्त चरित्र सामग्री के बाद स्वचालित रूप से जोड़ा जाता है।

प्रारूप पैरामीटर के बाद, फ़ंक्शन अपेक्षा करता है कि प्रारूप के लिए कम से कम अतिरिक्त तर्क दिए जाएं।

पैरामीटर:

str

एक बफर को इंगित करता है जहां परिणामस्वरूप सी-स्ट्रिंग संग्रहीत होता है। परिणामी स्ट्रिंग को शामिल करने के लिए बफर काफी बड़ा होना चाहिए।

format

सी स्ट्रिंग जिसमें एक प्रारूप स्ट्रिंग होती है जो प्रिंटफ़ में प्रारूप के समान विनिर्देशों का पालन करती है (विवरण के लिए प्रिंटफ़ देखें)।

... (additional arguments)

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

उदाहरण:

// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");

35
ओह! यदि संभव हो तो 'एन' भिन्नता वाले कार्यों का उपयोग करें। यानी स्नार्फ़। वे आपको अपने बफर आकार की गणना करेंगे और इस तरह ओवररन के खिलाफ बीमा करेंगे।
dmckee --- पूर्व-मध्यस्थ ने बिल्ली का बच्चा

7
हां, लेकिन उन्होंने ANSI C फ़ंक्शन के लिए कहा और मुझे यकीन नहीं है कि क्या स्निप्रफ एनी या पॉज़िक्स भी है।
एकप्पा

7
आह। मैंने यह खो दिया। लेकिन मुझे नए मानक द्वारा बचाया गया है: 'n' वेरिएंट C99 में आधिकारिक है। FWIW, YMMV, आदि
dmckee --- पूर्व-संचालक बिल्ली का बच्चा

1
स्निप्रफ़ सबसे सुरक्षित तरीका नहीं है। आपको snprintf_s के साथ जाना चाहिए। देखें msdn.microsoft.com/en-us/library/f30dzcf6(VS.80).aspx
Joce

2
@ जॉस - कार्यों का C99 स्निपर () परिवार बहुत सुरक्षित है; हालाँकि, snprintf_s () परिवार के पास अलग-अलग व्यवहार है (खासकर ट्रंक को कैसे संभाला जाता है) के बारे में। BUT - Microsoft का _snprintf () फ़ंक्शन सुरक्षित नहीं है - क्योंकि यह संभावित रूप से परिणामी बफर को अनधिकृत रूप से छोड़ सकता है (C99 snprintf () हमेशा समाप्त होता है)।
माइकल बूर 1

16

यदि आपके पास POSIX-2008 अनुरूप प्रणाली (कोई भी आधुनिक लिनक्स) है, तो आप सुरक्षित और सुविधाजनक asprintf()फ़ंक्शन का उपयोग कर सकते हैं : यह malloc()आपके लिए पर्याप्त मेमोरी होगी , आपको अधिकतम स्ट्रिंग आकार के बारे में चिंता करने की आवश्यकता नहीं है। इसे इस तरह उपयोग करें:

char* string;
if(0 > asprintf(&string, "Formatting a number: %d\n", 42)) return error;
log_out(string);
free(string);

यह एक न्यूनतम प्रयास है जिसे आप एक सुरक्षित फैशन में स्ट्रिंग का निर्माण करने के लिए प्राप्त कर सकते हैं। sprintf()कोड आप प्रश्न में दिया गहरा त्रुटिपूर्ण है:

  • पॉइंटर के पीछे कोई आवंटित मेमोरी नहीं है। आप स्मृति में एक यादृच्छिक स्थान के लिए स्ट्रिंग लिख रहे हैं!

  • भले ही आपने लिखा था

    char s[42];

    आप गहरे संकट में होंगे, क्योंकि आप नहीं जान सकते कि कोष्ठक में किस संख्या को रखा जाए।

  • यहां तक ​​कि अगर आपने "सुरक्षित" संस्करण का उपयोग किया था snprintf(), तो आप अभी भी इस खतरे को चलाएंगे कि आपके तार काट दिए जाएंगे। लॉग फ़ाइल में लिखते समय, यह एक अपेक्षाकृत मामूली चिंता है, लेकिन इसमें उस जानकारी को ठीक से काटने की क्षमता है जो उपयोगी होगी। इसके अलावा, यह अनुगामी एंडलाइन वर्ण को काट देगा, अगली लॉग लाइन को आपके असफल लिखित पंक्ति के अंत में जोड़ देगा।

  • यदि आप सभी मामलों में एक संयोजन का उपयोग करने malloc()और snprintf()सही व्यवहार का उत्पादन करने की कोशिश करते हैं , तो आप लगभग दो बार उतना ही कोड देते हैं जितना मैंने दिया है asprintf(), और मूल रूप से कार्यक्षमता को दोहराते हैं asprintf()


यदि आप एक रैपर प्रदान करना चाह रहे log_out()हैं तो एक printf()स्टाइल पैरामीटर सूची खुद ले सकते हैं , आप उस वेरिएंट का उपयोग कर सकते हैं vasprintf()जो va_listएक तर्क के रूप में लेता है । यहाँ इस तरह के रैपर का पूरी तरह से सुरक्षित कार्यान्वयन है:

//Tell gcc that we are defining a printf-style function so that it can do type checking.
//Obviously, this should go into a header.
void log_out_wrapper(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

void log_out_wrapper(const char *format, ...) {
    char* string;
    va_list args;

    va_start(args, format);
    if(0 > vasprintf(&string, format, args)) string = NULL;    //this is for logging, so failed allocation is not fatal
    va_end(args);

    if(string) {
        log_out(string);
        free(string);
    } else {
        log_out("Error while logging a message: Memory allocation failed.\n");
    }
}

2
ध्यान दें कि asprintf()न तो मानक C 2011 का हिस्सा है, न ही POSIX का हिस्सा, न ही POSIX 2008 या 2013 का हिस्सा। यह टीआर 27431-2 का हिस्सा है: क्या आप TR 24731 'सुरक्षित' कार्यों का उपयोग करते हैं?
जोनाथन लेफ़लर

आकर्षण की तरह काम करता है! मैं इस मुद्दे का सामना कर रहा था, जब "चार *" मूल्य लॉग में ठीक से मुद्रित नहीं हो रहा था, अर्थात प्रारूपित स्ट्रिंग किसी भी तरह से उपयुक्त नहीं थी। कोड का उपयोग कर रहा था, "asprintf ()"।
परसेंट

11

यह मुझे लगता है कि आप आसानी से उस फ़ंक्शन को प्रिंटफ-स्टाइल फ़ॉर्मेटिंग का उपयोग करके बनाए गए स्ट्रिंग को पास करने में सक्षम होना चाहते हैं जो आपके पास पहले से ही एक साधारण स्ट्रिंग लेता है। आप stdarg.hसुविधाओं का उपयोग करके एक आवरण फ़ंक्शन बना सकते हैं और vsnprintf()(जो आपके कंपाइलर / प्लेटफॉर्म के आधार पर आसानी से उपलब्ध नहीं हो सकता है):

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

// a function that accepts a string:

void foo( char* s);

// You'd like to call a function that takes a format string 
//  and then calls foo():

void foofmt( char* fmt, ...)
{
    char buf[100];     // this should really be sized appropriately
                       // possibly in response to a call to vsnprintf()
    va_list vl;
    va_start(vl, fmt);

    vsnprintf( buf, sizeof( buf), fmt, vl);

    va_end( vl);

    foo( buf);
}



int main()
{
    int val = 42;

    foofmt( "Some value: %d\n", val);
    return 0;
}

प्लेटफार्मों के लिए जो snprintf()दिनचर्या के परिवार का अच्छा कार्यान्वयन (या कोई भी कार्यान्वयन) प्रदान नहीं करते हैं, मैंने सफलतापूर्वक होल्जर वीज़ से लगभग एक सार्वजनिक डोमेन काsnprintf() उपयोग किया है


वर्तमान में, कोई vsnprintf_s का उपयोग करने पर विचार कर सकता है।
समामेलन

3

यदि आपके पास कोड है log_out(), तो उसे फिर से लिखें। सबसे अधिक संभावना है, आप कर सकते हैं:

static FILE *logfp = ...;

void log_out(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(logfp, fmt, args);
    va_end(args);
}

यदि अतिरिक्त लॉगिंग जानकारी की आवश्यकता है, तो उसे दिखाए गए संदेश से पहले या बाद में मुद्रित किया जा सकता है। यह स्मृति आवंटन और संदिग्ध बफर आकार और इतने पर और आगे बचाता है। आपको शायद logfpशून्य (नल पॉइंटर) को इनिशियलाइज़ करना होगा और यह जांचना होगा कि क्या यह अशक्त है और लॉग फ़ाइल को उपयुक्त के रूप में खोलें - लेकिन मौजूदा में कोडlog_out() वैसे भी इससे निपटना चाहिए।

इस समाधान का लाभ यह है कि आप बस इसे कॉल कर सकते हैं जैसे कि यह एक प्रकार था printf(); वास्तव में, यह एक मामूली संस्करण हैprintf()

यदि आपके पास कोड नहीं है log_out(), तो विचार करें कि क्या आप इसे ऊपर दिए गए संस्करण जैसे संस्करण के साथ बदल सकते हैं। चाहे आप उसी नाम का उपयोग कर सकते हैं जो आपके एप्लिकेशन फ्रेमवर्क और वर्तमान log_out()फ़ंक्शन के अंतिम स्रोत पर निर्भर करेगा । यदि यह एक अन्य अपरिहार्य फ़ंक्शन के रूप में समान ऑब्जेक्ट फ़ाइल में है, तो आपको एक नए नाम का उपयोग करना होगा। यदि आप इसे ठीक से दोहराने के तरीके से काम नहीं कर सकते हैं, तो आपको कुछ ऐसे वेरिएंट का उपयोग करना होगा जैसे कि अन्य उत्तरों में दिए गए हैं जो उचित मात्रा में मेमोरी आवंटित करते हैं।

void log_out_wrapper(const char *fmt, ...)
{
    va_list args;
    size_t  len;
    char   *space;

    va_start(args, fmt);
    len = vsnprintf(0, 0, fmt, args);
    va_end(args);
    if ((space = malloc(len + 1)) != 0)
    {
         va_start(args, fmt);
         vsnprintf(space, len+1, fmt, args);
         va_end(args);
         log_out(space);
         free(space);
    }
    /* else - what to do if memory allocation fails? */
}

जाहिर है, अब आप log_out_wrapper()इसके बजाय कहते हैं log_out()- लेकिन मेमोरी आवंटन और इसी तरह एक बार किया जाता है। मैं एक अनावश्यक बाइट द्वारा अंतरिक्ष को ओवर-आवंटन करने का अधिकार सुरक्षित रखता हूं - मैंने डबल-चेक नहीं किया है कि क्या लंबाई वापस आ गई है vsnprintf()जिसमें समाप्ति नल शामिल है या नहीं।


3

स्प्रिंटफ का उपयोग न करें।
यह आपके स्ट्रिंग-बफर को ओवरफ्लो करेगा और आपके प्रोग्राम को क्रैश करेगा।
हमेशा स्निपर का उपयोग करें


0

मैंने ऐसा नहीं किया है, इसलिए मैं सिर्फ सही जवाब देने जा रहा हूं।

C में उन कार्यों के लिए प्रावधान हैं, जो <stdarg.h>हेडर का उपयोग करके ऑपरेंड की अनिर्दिष्ट संख्याएँ लेते हैं । आप अपने फ़ंक्शन को इस रूप में परिभाषित कर सकते हैं void log_out(const char *fmt, ...);, और va_listफ़ंक्शन के अंदर प्राप्त कर सकते हैं । फिर आप मेमोरी को आवंटित कर सकते हैं और vsprintf()आवंटित मेमोरी, प्रारूप और के साथ कॉल कर सकते हैंva_list

वैकल्पिक रूप से, आप इसे एक फ़ंक्शन लिखने के लिए उपयोग कर सकते हैं sprintf()जो कि मेमोरी को आवंटित करेगा और स्वरूपित स्ट्रिंग को लौटाएगा, इसे ऊपर या नीचे कम से कम उत्पन्न करेगा। यह एक स्मृति रिसाव होगा, लेकिन अगर आप अभी लॉग आउट कर रहे हैं तो इससे कोई फर्क नहीं पड़ता।


-2

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html , stderr पर प्रिंट करने के लिए निम्न उदाहरण देता है। आप इसके बजाय अपने लॉग फ़ंक्शन का उपयोग करने के लिए इसे संशोधित कर सकते हैं:

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

 void
 eprintf (const char *template, ...)
 {
   va_list ap;
   extern char *program_invocation_short_name;

   fprintf (stderr, "%s: ", program_invocation_short_name);
   va_start (ap, template);
   vfprintf (stderr, template, ap);
   va_end (ap);
 }

Vfprintf के बजाय आपको vsprintf का उपयोग करने की आवश्यकता होगी जहां आपको प्रिंट करने के लिए पर्याप्त बफर प्रदान करने की आवश्यकता होती है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.