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
'va_arg (ap, int); // pobierz wartość & a w main() '- Jest to niepoprawne samo, ponieważ typem argumentu jest' int * ', a nie' int'. –
W języku C++ użyj szablonu variadic zamiast elipsis. – Jarod42
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