Ciekawy (?) problem

1

Hi ;>
Problem brzmi nastepujaco: jak otrzymac liczbe parametrow podanych do funkcji przyjmujacej wiele parametrow? (platforma zgodna z Intel x386)
Chodzi mi ofc o funkcje typu:

int func( costam a, ... )

gdzie "costam", dowolny typ max 4 bajty, ktory w sumie bardziej ma sluzyc bezproblemowemu otrzymaniu adresu na pierwszy parametr funkcji niz czemu kolwiek innemu ;>...
warunki w sumie sa takie: nie moze byc w zaden sposob podana liczba parametrow ;> chodzi o to zeby funkcja jakims sposobem sama sprawdzila ile jest parametrow ;>
i powiedzmy zwrocila liczbe parametrow ;>
np

func( 12, 3, 5, 1, 2 );

powinna zwrocic 5 ;>

Jakies pomysly ?
aa.. prosil bym o nie pisanie postow typu "nie da sie" ;> nawet jesli sie nie da to takie rozwazania sa bardzo pouczajace ;>

ponizej przedstawie dwa swoje ;>

Zkompilowany kod wywolania funkcji wyglada zazwyczaj (gcc/vc++) nastepujaco (BEZ OPTYMALIZACJI!):

func( 1, 2, 3, 4 ); <=- to wywolywane:
</c`code>`
push 4 <=- wrzucenie ostatniego parametru na stos
push 3
push 2
push 1 <=- wrzucenie pierwszego parametru
call func <=- wywolanie funkcji
add esp, 0x10 <=- korekcja stack pointera

Adres powrotny uzyskac jest w miare latwo:

int func(int a, ... )
{
  int *ret = &amp;a - 1;
  // lub int a, *b = &amp;a + 2;
...

po powrocie kolejnym rozkazem jest add, a jego parametrem 4 * ilosc parametrow wrzucona na stos, wystarczy wiec w sumie pobrac parametr add, podzielic przez 4 i mamy liczbe parametrow...
jednak sprawa sie komplikuje w przypadku kompilacji z optymalizacja, jako ze korekcja stosu nie jest robiona po kazdym call, a co iles tam wywolan, co powoduje ze w sumie nie mamy dostepu do add esp, XXX...

drugim pomyslem natomiast jest policzenie pushy ktore sa przed call'em.. jednak ta metoda ma rowniez sporo wad

  • nie wiadomo z ktorym opcodem pusha mamy do czynienia, jako ze push 1, push 12937714 i push eax maja troche rozne opcody, do tego nie wiemy jaka jest wielkosc opcodu, i mozliwe jest pomylenie push eax z parametrem dlugiego pusha np....
  • zaczami program przeplata pushe jakimis lea ;> co jeszcze bardziej utrudnia sprawe
    wiec ta metoda w sumie odpada ;>

czekam na propozycje ;>

0

Wykombinowałem coś takiego:

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

int params(...)
{
	int argcount = 0;
	va_list args;
	va_start(args, 0);
	while(va_arg(args, int) != 0)
		argcount++;
	va_end(arg);
	return argcount;
}

int main(void)
{
	printf("%d", params(2, 3, 4));
	return 0;
}

Jest tylko jeden problem: kod działa jedynie pod TC3.0 :-/

1
	va_start(args, 0);

ehm.. ehm... AFAIR va_start jako drugi parametr przyjmuje nazwe zmiennej ktorej typ program znal.. po to zeby uzyskac pointer do niej.. em em.. co sie dzieje jesli sie tam 0 wpisze???
w makrze pewnie wyjdzie &0 ne ? chyba ze jest jakis specialny warunek dla 0...
jaki ?
a moze bierze 0 jako adres? co jest na 0 w pamieci (TC akurat ma do niej dostep, dos mode ;p.. tj wydaje mu sie ze ma).. hmm nie czasem jakas tablica wektorow przerwan ? a noz cos tam ma 3*sizeof(int) wypelnione a pozniej sa zera; >>>

	while(va_arg(args, int) != 0)

a) mowilem ze nie moze byc w zaden sposob znana ilosc parametrow, a to DOTYTCZY ROWNIEZ TERMINATOROW.. czemu? zauwaz ze petla jest wykonywana az do napotkania wartosci 0...
czyli dla wywolania params( 0, 0, 0, 0, 0 ), zamiast 5 zwroci 0 ;pppp
poza tym to ze ta funkcja dziala dla 2,3,4 graniczy z cudem ;> po prostu na stosie za parametrami jest 0 jakims cudownym sposobem (w pamieci mozna caly przedzial liczb napotkac, a 0 najczesciej ;>)

Jest tylko jeden problem: kod działa jedynie pod TC3.0 :-/

bardziej dziwne jest to ze po TC3.0 to dziala ;pppp
pozadrawiam

0

W większości przypadków powinno wystarczyć coś takiego:

#define END -1
foo(1,2,3,4,5,END);

a w kodzie funkcji w pętli wystarczy porównać po kolei wszystkie elementy(va_arg) z wartością END.

ps. Ten cały problem mogłeś przedstawić w jednym zdaniu, ale wolałeś napisać wypracowanie i pokazać jakich to mądrych forumowiczów mamy [diabel]

0

Heh pomimo tych błędów, o których mówiłeś program działa dobrze i wcale nie trzeba wpisywać zera na końcu argumentów :)
<font color="green">//dopisane</span>
Jeżeli masz TC to sprawdź w helpie bo tam jest podobnie zrobione (mówie o while(...!= 0)) i działa w TC natomiast na innych kompilatorach daje zły wynik.

1

W większości przypadków powinno wystarczyć coś takiego:

#define END -1
foo(1,2,3,4,5,END);

a w kodzie funkcji w pętli wystarczy porównać po kolei wszystkie elementy(va_arg) z wartością END.

ps. Ten cały problem mogłeś przedstawić w jednym zdaniu, ale wolałeś napisać wypracowanie i pokazać jakich to mądrych forumowiczów mamy [diabel]

kurcze no, napisalem cale wypracowanie, a nikt go uwaznie nie przeczytal...
JAK PISALEM (2 razy na 2 posty juz).. zabawa polega na tym ze funkcja W ZADEN JAWNY SPOSOB (terminator, okreslenie w pierwszym paramatrze listy/ilosci parametrow) nie moze miec okreslonej liczby parametrow... twoj sposob OFC <font color="red">NIE JEST ZGODNY</span> z tym zalozeniem... czytaj uwaznie pytanie zanim na nie odpowiesz..

Imp obawiam sie ze twoj tez, tyle ze ow Nikt ciekawy uzywa jako terminator -1, a ty 0....
a wlasnie.. Imp a sproboj zrobic cos takiego:
w mainie w tym printf wstaw jeszcze ZA tym wypisywaniem ilosci parametrow:
%i, a jako parametr podaj 10, i powiedz jesli mozesz co funkcja wypisze; >

p.s.
nyoo... ludzie.. moze macie jakies pomysly (zgodne z zalozeniami?) ;> nie musza one dzialac wszedzie, wystarczy ze w specyficznych warunkach (zgodnych z zalozeniami) ;> nyo ;>
zapraszam do dyskusji ;>

0

Ja na razie kombinuję w ten sposób: Zmienna globalna (dostępna z funkcji ze zmienna liczbą parametrów) trzymająca adres czegoś ;> Przed wywołaniem funkcji tworzę zmienną i ona idzie na stos? Pobieram adres zmiennej na stosie, wywołuję funkcję z kilkoma parametrami, parametry idą na stos. I teraz pobieram adres powrotu (poszedł na stos jako ostatni?) wewnątrz wywołanej funkcji, patrzę na różnicę między adresami powrotu i zmiennej która poszła na stos przed wywołaniem funkcji, chcę podzielić to przez 4 i otrzymać liczbę parametrów, a co otrzymuję? Chyba g**** ;> W każdym razie próbowałem ;> Co jest źle? Parametry idą na stos nie w takiej kolejności w jakiej bym oczekiwał?

int *dupa;

int funkcja(int b, ... ) {
   dupa = (int*)(dupa-(&amp;b-1));
   return 0;
}

int main() {
   int help;
   dupa = &amp;help;
   funkcja(1,2,3,4,5,6);
   printf("%o\\n",(unsigned int)dupa);
   return 0;
}

pzdr,

y.
0
#include <stdarg.h>
#include <iostream.h>

int Dodaj(int, ...);

int main()
{
	cout << "Suma pierwszych 2 cyfr : " << Dodaj(2, 1, 2);
	cout << "\\nSuma pierwszych 3 cyfr : " << Dodaj(3, 1, 2, 3);
	cout << "\\nSuma pierwszych 4 cyfr : " << Dodaj(4, 1, 2, 3, 4);
	cout << "\\nSuma pierwszych 5 cyfr : " << Dodaj(5, 1, 2, 3, 4, 5);
	cout << "\\nSuma pierwszych 6 cyfr : " << Dodaj(6, 1, 2, 3, 4, 5, 6);
	cout << "\\nSuma pierwszych 7 cyfr : " << Dodaj(7, 1, 2, 3, 4, 5, 6, 7);
	cout << "\\nSuma pierwszych 8 cyfr : " << Dodaj(8, 1, 2, 3, 4, 5, 6, 7, 8);
	cout << "\\nSuma pierwszych 9 cyfr : " << Dodaj(9, 1, 2, 3, 4, 5, 6, 7, 8, 9);
	cout << "\\nSuma pierwszych 10 cyfr : " << Dodaj(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
	return 0;
}

int Dodaj(int ile, ...)
{
	int suma = 0;

	va_list lista;
	va_start(lista, ile);

	for (int i = 0; i < ile; ++i)
		suma += va_arg(lista, int);

	va_end(lista);

	return suma;
}

I to tyle

1

Nightmare: to samo co Nikt ciekawy, przeczytaj DOKLADNIE pytanie zanim odpowiesz na nie ;pppp Nie pytam jak uzywac ... z podana liczba parametrow, ale BEZ podanej liczby jak poznac ta liczbe ;p

y. o ;> wkoncu ktos z pomyslem ;>
hmm problem polega na tym ze funkcje maja w zwyczaju "alokowac" sobie np 0x100 bajtow na stosie za jednym zamachem (dec esp, 100h) a potem dopiero od poczatku tego "zaalokowanego" miejsca ukladac zmienne autotyczne/lokalne...
Aczkolwiek mimo tego jest to pewien pomysl..
Teoretycznie da sie trafic w ilosc bajtow zaalokowanych (tj ofc mozna to w debugu sprawdzic ile alokuje ;>) i wtedy wg adresu wyliczyc miejsce na stosie/ tj koniec/ od ktorego beda wrzucane parametry...
Reszta wyglada dobrze, z tym ze (blah te "ale" ;>) przy kazdym wywolaniu by trzeba koncowke stosu znajdowac i zapisywac ja w zminnej glob...
moze mala wstawka asma ? mov eax, esp; mov costam, esp ?
kurcze z tym ze to troche nadklada kodu.. ale jest to sposob ;>

0

Napisałem coś co działa, ale nie wiem czy bedzie Ci odpowiadał ten sposób, bo jest trochę na okrętkę.

#include <iostream.h>
#include <stdarg.h>
#include <stdlib.h>

#define MEMB_FUNC_OFF 88
#define NORM_FUNC_OFF 84

class CArgCnt
{
public:
	CArgCnt()
	{
		m_iOldSP=m_iNewSP=m_iLocVars;
	}
	void enter(); 
	void setup(int num);
	int exit();

public:
	short m_iOldSP;
	short m_iNewSP;
	int	  m_iLocVars;
};

void CArgCnt::enter()
{
	short tmp;
	__asm mov tmp, sp
	m_iOldSP=tmp+MEMB_FUNC_OFF+4; 
}

void CArgCnt::setup(int num)
{
	m_iLocVars = num;
}

int CArgCnt::exit()
{
	short tmp;
	__asm mov tmp, sp
	m_iNewSP=tmp+MEMB_FUNC_OFF+NORM_FUNC_OFF+4+(m_iLocVars*4);

	tmp = m_iNewSP-m_iOldSP;

	return -tmp/4;
}

CArgCnt g_ArgCnt;

int func(int f, ...)
{
	va_list lista;

	g_ArgCnt.setup(1);

	return g_ArgCnt.exit();
}

int main()
{
	ios::sync_with_stdio();

	g_ArgCnt.enter();
	int cnt = func(6);
	cout <<cnt<<endl;

	system("pause");
	return 0;
}

Co do działania:
CArgCnt::enter() - wywołuje się ją przed badaną funkcją
CArgCnt::setup(int) - wywoluje się ją aby podać ile jest zmiennych lokalnych w badanej funkcji
CArgCnt::exit() - wywoluje się ją wewnątrz badanej funkcji, aby otrzymać liczbę parametrów.

Mam nadzieje, że to pomoże

1

o, nice ;> widze ze masz implementacje tego o czym wyzej rozmawialismy ;> nice nice ;>
w sumie sposob moze i jest troche na okolo, ale dziala, poza tym jeden define i juz nic dodatkowego klepac nie trzeba ;>

nyo ;> tyo jeden sposob jest ;>
ma ktos jeszcze jakies inne pomysly?

1 użytkowników online, w tym zalogowanych: 0, gości: 1