هل هناك مسكتك باستخدام varargs مع المعلمات المرجعية

StackOverflow https://stackoverflow.com/questions/222195

سؤال

لدي هذا الجزء من الكود (ملخص) ...

AnsiString working(AnsiString format,...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

وعلى أساس أن المرور بالإشارة هو المفضل حيثما أمكن ذلك، قمت بتغييره على هذا النحو.

AnsiString broken(const AnsiString &format,...)
{
... the rest, totally identical ...
}

رمز الاتصال الخاص بي هو كالتالي:-

AnsiString s1, s2;
    s1 = working("Hello %s", "World");
    s2 = broken("Hello %s", "World");

لكن s1 يحتوي على "Hello World"، بينما يحتوي s2 على "Hello (null)".أعتقد أن هذا يرجع إلى الطريقة التي يعمل بها va_start، لكنني لست متأكدًا تمامًا مما يحدث.

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

المحلول

إذا نظرتم الى ما va_start يوسع إلى، سترى ما يحدث:

va_start(argptr, format); 

ويصبح (تقريبا)

argptr = (va_list) (&format+1);

إذا الشكل هو قيمة من نوع، ويتم وضعه على كومة الحق قبل كل الحجج variadic. إذا الشكل هو نوع مرجع، ويحصل على وضعها فقط العنوان على المكدس. عندما كنت تأخذ من عنوان المتغير إشارة، يمكنك الحصول على عنوان أو المتغير الأصلي (في هذه الحالة من ANSIString و المؤقتة التي تم إنشاؤها قبل استدعاء المكسور)، وليس عنوان الحجة.

إذا كنت لا تريد أن يمر حول فصول كاملة، لديك خيارات إما عن طريق تمرير المؤشر، أو وضعت في حجة وهمية:

AnsiString working_ptr(const AnsiString *format,...)
{
    ASSERT(format != NULL);
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format->c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

AnsiString format = "Hello %s";
s1 = working_ptr(&format, "World");

أو

AnsiString working_dummy(const AnsiString &format, int dummy, ...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, dummy);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

s1 = working_dummy("Hello %s", 0, "World");

نصائح أخرى

إليك ما يقوله معيار C++ (18.7 - دعم وقت التشغيل الآخر). va_start() (التأكيد على الألغام):

القيود التي يضعها ISO C على المعلمة الثانية إلى va_start() الماكرو في الرأس <stdarg.h> تختلف في هذا المعيار الدولي.المعلمة parmN هو معرف المعلمة أقصى اليمين في قائمة المعلمة المتغيرة لتعريف الوظيفة (واحد قبل ...). إذا كانت المعلمة parmN يُعلن عن وظيفة أو صفيف أو نوع مرجعي ، أو بنوع غير متوافق مع النوع الذي ينتج عند تمرير وسيطة لا توجد معلمة لها ، السلوك غير محدد.

كما ذكر آخرون، يعد استخدام varargs في C++ أمرًا خطيرًا إذا كنت تستخدمه مع عناصر غير C (وربما حتى بطرق أخرى).

ومع ذلك - ما زلت أستخدم printf() طوال الوقت ...

تم العثور على التحليل الجيد لماذا لا تريد هذا في <لأ href = "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1995/N0695.pdf" يختلط = "نوفولو noreferrer"> N0695

ووفقا لC ++ الترميز المعايير (سوتر، ألكسندريسكو):

ويجب عدم استخدام varargs مع C ++:

وانهم لا اكتب آمنة ولها سلوك غير معرف لكائنات من نوع فئة، والتي من المرجح مما تسبب في مشكلتك.

إليك يا من السهل الحل (المترجمة مع Visual C ++ 2010):

void not_broken(const string& format,...)
{
  va_list argptr;
  _asm {
    lea eax, [format];
    add eax, 4;
    mov [argptr], eax;
  }

  vprintf(format.c_str(), argptr);
}

ملاحظة جانبية:

ويمكن غير معروف السلوك لأنواع الفئة كما varargs الحجج، لكنه ثابت في تجربتي. المترجم يدفع sizeof (فئة) من ذاكرة الطبقة وإلى المكدس. أي في شبه كود:

alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);

لمثال مثير للاهتمام حقا هذا يجري استخدامها بطريقة خلاقة جدا، لاحظ أنك <م> يمكن تمرير مثيل CString بدلا من LPCTSTR إلى وظيفة varargs مباشرة، وأنها تعمل، وهناك لا صب المعنية. أترك الأمر باعتبارها ممارسة للقارئ لمعرفة كيفية جعل هذا العمل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top