2016-07-22 32 views
7

Chcę wykonać pewne ćwiczenie na temat va_list. To jest mój kod.Jak zastąpić wartości w va_list?

int myscanf(char* fmt, ...) { 
    va_list ap; 
    va_start (ap, fmt); 
    vfscanf (stdin, fmt, ap); 
    va_end (ap); 
} 

int main() { 
    int a, b; 
    myscanf("%d %d", &a, &b); 
} 

Jak pokazano powyżej, napisałem scanf() i to jest praca.

Teraz chcę przekierować wartość argumentów w myscanf().

Na przykład, mogę przekierować FMT do przestrzeni, która jest przeznaczona na myscanf()

int myscanf(char* fmt, ...) { 
    char newFmt[10] = "%d %d"; 
    va_list ap; 
    va_start (ap, fmt); 
    vfscanf (stdin, newFmt, ap); 
    va_end (ap); 
} 

Jednak czuję się zdezorientowany, gdy próbuję zmienić wartość innych argumentów.

Mogę pobrać te zmienne argument przez va_arg(), ale nie mogę ich zmodyfikować, ponieważ va_arg() jest makrem.

int myscanf(char* fmt, ...) { 
    va_list ap; 
    va_start (ap, fmt); 

    int* arg1 = (int)va_arg(ap, int*); // get the value of &a in main() 
    int newA; // I want to read a value by scanf() and store it to &newA 
    // ??? = &newA // <- how to do? 

    vfscanf (stdin, fmt, ap); 
    va_end (ap); 
} 

Jakieś sugestie?

----------- edit -----------

Dzięki za odpowiedzi,

Ale coś powinno być wyjaśnione.

"Wartość" w tym przypadku to "adres". Dlatego moim celem jest zmiana adresu docelowego, tak aby vfscanf() odczytał i zapisał wartość do innej przestrzeni adresowej.

Na przykład

int gA, gB, gC, gD; 

int myscanf(char* fmt, ...) { 
    va_list ap; 
    va_start (ap, fmt); 

    // do something for making the following vfscanf() to write value into gC and gD directly 
    vfscanf (stdin, fmt, ap); 

    // don't assign *gA to *gC and *gB to *gD after performing vfscanf() 
    va_end (ap); 
} 

int main() { 
    myscanf("%d %d", &gA, &gB); 
} 

Jak FMT zmienić newFmt chcemy zmienić wartość (w tym przypadku jest adres) w va_list bezpośrednio.

Problem z analizą jest rozwiązany, ponieważ mogę przydzielić przestrzeń dynamicznie, pisząc "% ..." z ciągu formatu. Te adresy spacji wielokrotnie będą zastępować dane wejściowe, jeśli powyższe pytanie zostanie rozwiązane.

+1

'va_arg (ap, int); // pobierz wartość & a w main() '- Jest to niepoprawne samo, ponieważ typem argumentu jest' int * ', a nie' int'. –

+0

W języku C++ użyj szablonu variadic zamiast elipsis. – Jarod42

+0

Zamierzam usunąć znacznik C++, ponieważ zmienne z elipsami nie są istotne dla współczesnego C++. Jeśli naprawdę chcesz to zrobić w C++, wziąłbym radę @ Jarod42. – erip

Odpowiedz

0

Jedynym sposobem jest przekazywanie zaktualizowanych argumentów bezpośrednio, ponieważ va_list nie można zmodyfikować. W twoim przypadku powinieneś parsować ciąg formatu, aby mieć pojęcie o rzeczywistej zawartości va_list, a następnie przekazać kompatybilny zestaw argumentów do fscanf() (nie vfscanf()) bezpośrednio.

1

Nie jest to możliwe bezpośrednio, ale można zrobić, jak poniżej.

int myscanf(char* fmt, ...) { 
    va_list ap; 
    va_start (ap, fmt); 
    int newA; 
    scanf("%d",&new); 
    vfscanf (stdin, fmt, ap); 
    va_end (ap); 
} 

Myślę, że zrobi to tak, jak chcesz.

+0

Dzięki, ale to podejście spowodowałoby, że zachowanie programu różni się od pierwotnego celu. (program będzie czekał na wejście 2 razy) – hwliu

+0

Wydaje się to sensowne. @ hwliu Program prawdopodobnie będzie czekał na więcej danych wejściowych w 'getchar' w' scanf' – Sebivor

6

Funkcje o zmiennej liczbie argumentów

Argumenty scanf zawsze będą wskaźniki, a nie wartości jak w przykładzie. Prawidłowym sposobem uzyskania argumentu o numerze scanf jest int *arg1 = va_arg(ap, int*); - i nie trzeba go przesyłać.

Jeśli chcesz manipulować sposobem, w jaki zachowuje się scanf, musisz najpierw wiedzieć, jak działa variadic functions (możesz go uzyskać, czytając instrukcję z jednej z rodzin funkcji va_*). Zmienna ap w większości architektur jest wskaźnikiem do ramki stosu funkcji.W tym przypadku wskazuje następną zmienną po fmt.


Twój przykład

W przypadku scanf W przykładzie będzie to wskazywać na liście wskaźników (bo wszystkiescanf argumenty muszą być wskaźnikami). Powinieneś umieścić to w swoich wskazówkach:

int *a = va_arg(ap, int*); 

/* Then you can modify it like this: */ 
*a = 666; 

Występują pewne problemy z tym.

Po zakończeniu manipulowania argumenty, należy zdać fmt i ap do vfscanf, które będą następnie zanalizować fmt i oczekują n elementów (ilość elementów w łańcuchu format). Problem polega na tym, że teraz ap poda nam tylko elementy n - x (to liczba elementów, które "wypełniłeś" w swojej własnej funkcji). Mały przykład:

myscanf("%d %d", &a, &b); 
/* n = 2 */ 

... 

int *a = va_arg(ap, int *); 
/* x = 1 */ 

... 
vfscanf(stdin, fmt, ap); 
/* n = 2 cause fmt is still the same, however 
* x = 1, so the number of elements "popable" from the stack is only 
* n - x = 2 - 1 = 1. 
*/ 

W tym prostym przykładzie widać już problem. vfscanf wywoła va_arg dla każdego znalezionego elementu w ciągu formatu, który jest n, ale tylko n - x są dostępne. Oznacza to, że nieokreślone zachowanie zostanie zapisane w miejscu, w którym nie powinno i najprawdopodobniej spowoduje awarię programu.


Hack Około

Aby przezwyciężyć że proponuję trochę pracy z va_copy. Podpis va_copy jest:

void va_copy(va_list dest, va_list src); 

I coś o tym wiedzieć (z podręcznika):

Każde wywołanie va_copy() musi być dopasowana przez odpowiedni pw va_end () w tej samej funkcji. Niektóre systemy, które nie dostarczają va_copy(), zamiast tego mają nazwę używaną w projekcie wniosku.

Rozwiązanie:

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

int myscanf(char *fmt, ...) 
{ 
    va_list ap, hack; 

    /* start our reference point as usual */ 
    va_start(ap, fmt); 

    /* make a copy of it */ 
    va_copy(hack, ap); 

    /* get the addresses for the variables we wanna hack */ 
    int *a = va_arg(hack, int*); 
    int *b = va_arg(hack, int*); 

    /* pass vfscanf the _original_ ap reference */ 
    vfscanf(stdin, fmt, ap); 

    va_end(ap); 
    va_end(hack); 

    /* hack the elements */ 
    *a = 666; 
    *b = 999; 
} 

int main(void) 
{ 
    int a, b; 
    printf("Type two values: "); 
    myscanf("%d %d", &a, &b); 

    printf("Values: %d %d\n", a, b); 
    return 0; 
} 


Wnioski i ostrzeżenia

Istnieje kilka rzeczy, które należy pamiętać. Po pierwsze, jeśli wpiszesz hakowanie elementów przed wywołaniem vfscanf, ustawione wartości zostaną utracone, ponieważ vfscanf nadpisze te lokalizacje.

Następnie należy również zauważyć, że jest to bardzo konkretny przypadek użycia. I wiedział wcześniej,, że zamierzałem przekazać dwie liczby całkowite jako argumenty, więc z myślą o tym zaprojektowałem myscanf. Ale oznacza to, że potrzebujesz przechodzącej kolejki danych, aby dowiedzieć się, jakie argumenty należą do danego typu - jeśli tego nie zrobisz, ponownie wprowadzisz niezdefiniowane zachowanie . Niezdefiniowane zachowanie. Pisanie tego rodzaju analizatora jest bardzo proste i nie powinno stanowić problemu.


Po edycji

Po tym, co powiedział w swojej edycji klarowania, mogę jedynie zaproponować trochę funkcję otoki wokół vfscanf(), ponieważ nie można bezpośrednio manipulować va_list zmienne. Nie możesz pisać bezpośrednio na stosie (w teorii, nie możesz, ale jeśli zrobiłeś inline-assembly mógłbyś, ale to będzie brzydki hack i bardzo nieprzenośny).

Powodem, dla którego będzie to wyjątkowo brzydkie i nieprzenośne, jest to, że wbudowany zespół będzie musiał wziąć pod uwagę sposób, w jaki architektura traktuje argumenty przemijające. Samo pisanie inline-assembly jest już bardzo brzydkie ... Zapoznaj się z oficjalną instrukcją GCC dotyczącą jej wbudowanego zestawu.

Powrót do Twojego problemu:

To odpowiedź wyjaśnia całe mnóstwo, więc nie powiem tutaj ponownie. Ostateczna konkluzja odpowiedzi jest ** nie, nie rób tego”Co ty _can nie jest jednak wrapper jak ten:..

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

int a, b, c, d; 

void ms_wrapper(char *newfmt, ...) 
{ 
    va_list ap; 
    va_start(ap, newfmt); 

    vfscanf(stdin, newfmt, ap); 

    va_end(ap); 
} 

int myscanf(char *fmt, ...) 
{ 
    /* manipulate fmt.... */ 
    char *newfmt = "%d %d"; 

    /* come up with a way of building the argument list */ 

    /* call the wrapper */ 
    ms_wrapper(newfmt, &c, &d); 
} 

int main(void) 
{ 
    a = 111; 
    b = 222; 
    c = 000; 
    d = 000; 

    printf("Values a b: %d %d\n", a, b); 
    printf("Values c d: %d %d\n\n", c, c); 

    printf("Type two values: "); 
    myscanf("%d %d", &a, &b); 

    printf("\nValues a b: %d %d\n", a, b); 
    printf("Values c d: %d %d\n", c, d); 

    return 0; 
} 

Pamiętaj, że można tylko budować listy argumentów dla funkcji o zmiennej liczbie argumentów w swoim czasie kompilacji Nie możesz mieć dynamicznie zmieniającej się listy parametrów, innymi słowy, będziesz musiał mocno kodować każdy przypadek, z którym kiedykolwiek chciałbyś sobie poradzić. Jeśli użytkownik wejdzie w coś innego, twój program będzie się zachowywał bardzo dziwnie i najprawdopodobniej awarii

+1

Podam +2 dla tej odpowiedzi. – xenteros

+0

@xenteros Dzięki! –

+0

To jest po prostu bardzo dobrze zredagowana odpowiedź! – xenteros

1

na danej platformie można korzystać z niektórych trudne Hack:.

  1. va_list jest po prostu wskaźnik do niektórych danych (zazwyczaj char *)
  2. va_arg jest w zasadzie arytmetyki wskaźników i rzucać

Tak, można przydzielić tablicę dwóch wskaźników do int, ustaw wartości i połączenia vfscanf z tym. Coś jak:

int *hack[2]; 
hack[0] = &gC; 
hack[1] = &gD; 
vscanf(stdin, fmt, (va_list)hack); 

UWAGA jest bardzo non przenośny, bardzo skomplikowane i podatne na błędy. Jest z tym problem, nawet jeśli zasadniczo działa na wielu platformach.

+0

Ten podstępny hack inspiruje mnie do myślenia. Dzięki :) – hwliu