2012-03-30 14 views
8

Variadic parametrelerini va_list'a dönüştüren bir kod var, daha sonra listeyi vsnprintf olarak adlandırılan bir işleve iletir. Bu, Windows ve OS X üzerinde iyi çalışıyor, ancak Linux'ta garip sonuçlarla başarısız oluyor. Aşağıdaki kod örneğiva_list aksaklığı Linux'ta

:

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

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list *original = &params; 
    size_t length = vsnprintf(NULL, 0, message, *original); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, params); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

char *myPrintf(const char *message, ...) 
{ 
    va_list va_args; 
    va_start(va_args, message); 

    size_t length = vsnprintf(NULL, 0, message, va_args); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, va_args); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    va_end(va_args); 

    return final; 
} 

int main(int argc, char **argv) 
{ 
    char *test = myPrintf("This is a %s.", "test"); 
    char *actual = "This is a test."; 
    int result = strcmp(test, actual); 

    if (result != 0) 
    { 
     printf("%d: Test failure!\r\n", result); 
    } 
    else 
    { 
     printf("Test succeeded.\r\n"); 
    } 

    return 0; 
} 

ikinci vsnprintf aramanın çıkışı 17 ve strcmp sonucu 31 olduğu; ama vsnprintfThis is a test. olarak görülmeye 17 döneceğini neden 15 karakter olan olsun NULL eklemek ve gördüğüm ama konuyu ele almazlarsa 16.

İlgili konuları alamadım:


(ıyeniden ediyorumnesnesi, izin verilmez), bu, birbirine bağladığım ilk ilişkili iş parçacığı etrafında gelir. Bu yüzden yerine bu kodu teşebbüs:

Bir va_list bir işaretçi oluşturmak için izin verilir ve başka o işaretçi pass:

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list *original = &params; 
    size_t length = vsnprintf(NULL, 0, message, params); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, *original); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

hangisi, per the C99 spec (Bölüm 7.15 dipnotu), çalışmalıdır işlev, bu durumda orijinal işlev 'u, diğer işlev döndürdikten sonra orijinal listenin daha fazla kullanmasını sağlayabilir.

Ama (C99 modunda gcc 4.4.5) benim derleyici bana myPrintfInner ilk satırında ilgili bu hatayı veriyor:

test.c: In function ‘myPrintfInner’: 
test.c:8: warning: initialization from incompatible pointer type 

Ve çıkan ikili çevresinde ilk defa tam olarak aynı etkiyi üretir .


Bulunan bu: Is GCC mishandling a pointer to a va_list passed to a function?

önerilen geçici çözüm (çalışması garanti, ama pratikte yaptığımız değildi) kullanmaktır arg_copy ilk: Sorun olduğunu

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list args_copy; 
    va_copy(args_copy, params); 

    size_t length = vsnprintf(NULL, 0, message, params); 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, args_copy); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 
+0

'myPrintf' işlevinizde' return' ifadesi eksik. Derleyicinin sizi bu konuda uyarmasını beklerdim. –

+0

bah, humbug! Kopyalayıp yapıştırın. –

+1

Yeni kodunuz, eskiyle aynı şeyi yapıyor: "orijinal", "params" a işaret ediyor, bu yüzden, * orijinali, "param" kelimesinin geçmesiyle tamamen aynıdır. Asıl sorunun, va_list'in nasıl çalıştığını anlamıyor olmanız gibi görünüyor: aslında argüman yığıtının işaretçileridir ve işaretçi, kullanıldığı gibi artar. Yani, aynı va_list'i iki kez kullandığınızda, ikincisini, argüman listesinin sonundaki işaretçiyi artırdığınız zaman. –

cevap

11

, sorun va_list yeniden olduğumuz. O da anlaşılacağı gibi kodunuzu yeniden yapılandırmaya istemiyorsanız, bu gibi C99 va_copy() makro kullanabilirsiniz: use __va_copy() instead or define your own va_copy() implementation (ki mümkün olacak C99 desteği olmayan derleyici üzerinde

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list copy; 

    va_copy(copy, params); 
    size_t length = vsnprintf(NULL, 0, message, copy); 
    va_end(copy); 

    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, params); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

, sen olabilir Taşınabilir olmayan, ancak gerçekten ihtiyacınız varsa, bir başlık dosyasında derleyici/platform kokusunu kullanabilirsiniz. Ama gerçekten, 13 yıl — iyi bir derleyici bu günlerde C99'u desteklemeli, en azından doğru seçenekleri verirseniz (GCC için -std=c99).

+0

GCC w/C99 kullanıyorum, ancak gözden geçirilmiş sorumu görürseniz, bunu zaten denedim ve sonuçlar iyi değil. X86_64 üzerinde kırılmış gibi görünüyor. –

+1

Bu garip. Daha önce x86_64 Linux üzerinde verdiğim kodu denedim ve benim için çalışıyor. –

+0

İşte benim tam deneyimim: http://pastebin.ca/2133787 - Ben sizden farklı bir şey yapmadım. Hangi gcc sürümünü kullanıyorsunuz? Gönderiyi bu bilgi ile güncelledim. –

7

(Kayıp iade ifadesinden başka) sıfırlamaksızın va_list parametresini yeniden kullanıyorsunuz. Bu iyi değil. gibi

deneyin şey:

size_t myPrintfInnerLen(const char *message, va_list params) 
{ 
    return vsnprintf(NULL, 0, message, params); 
} 

char *myPrintfInner(size_t length, const char *message, va_list params) 
{ 
    char *final = (char *) malloc((length + 1) * sizeof(char)); 
    int result = vsnprintf(final, length + 1, message, params); 

    printf("vsnprintf result: %d\r\n", result); 
    printf("%s\r\n", final); 

    return final; 
} 

char *myPrintf(const char *message, ...) 
{ 
    va_list va_args; 
    va_start(va_args, message); 
    size_t length = myPrintfInnerLen(message, va_args); 
    va_end(va_args); 
    va_start(va_args, message); 
    char *ret = myPrintfInner(length, message, va_args); 
    va_end(va_args); 
    return ret; 
} 

(. Ve derleyici 'uyarılarına açmak)

sanmıyorum Eğer işaret dipnot sen öyle düşündüğün anlamına gelir. Ben şu şekilde okurum: va_list'u doğrudan (işaretçi değil, bir değer) geçirirseniz, arayana yapabileceğiniz tek şey va_end'dur.Ancak eğer bir işaretçi olarak iletirseniz, callee tüm va_list'u "tüketmediyse" arayana va_arg numaralı telefonu arayabilirsin. Ancak, va_copy ile deneyebilirsiniz. gibi bir şey: Mat notları gibi

char *myPrintfInner(const char *message, va_list params) 
{ 
    va_list temp; 
    va_copy(temp, params); 
    size_t length = vsnprintf(NULL, 0, message, temp); 
    ... 
+0

Teşekkürler. Benim gözden geçirilmiş gönderimi görebilir misin? –

+0

Cevabımı başka bir seçenekle düzenledim. – Mat

+0

GCC ile x86_64 üzerinde Me (tm) için çalışır, ancak önce kopyayı alıyorum. Ama aslında arayanın va_list'inin sıfırlanması ile yapışmayı öneriyorum. – Mat