Forum: PC-Programmierung Funktionen / Funktionsaufrufe rückwärts auflösen ?


von ich&er (Gast)


Lesenswert?

gibt es ein tool o.ä. um folgendes zu erreichen:



int main()
{
  function1();
  function2();
  function1();

  return(0);
}


void function1()
{
  befehl11;
  befehl12;
  befehl13;
}

void function2()
{
  befehl21;
  befehl22;
  befehl23;
}

    ||
    || Auflösen der
    || Funktionsaufrufe
    \/

int main()
{
  befehl11;
  befehl12;
  befehl13;

  befehl21;
  befehl22;
  befehl23;

  befehl11;
  befehl12;
  befehl13;

  return(0);
}

ich will also in der main (oder wo auch immer) den jeweiligen 
funktionsaufruf durch den inhalt der entsprechenden funktion ersetzen.
vielleicht kennt ja jemand ein tool welches sowas ermöglicht - ich 
möchte mir einfach nur die zeit sparen es selbst schreiben zu müssen, 
ich brauche das eigentlich nur zum schnellen debuggen und möchte die 
funktionen dazu aufgelöst haben. hintergrund ist (um den sinn vll etwas 
zu verdeutlichen) folgender: ich rufe zb die function1() mehrmals im 
programm auf, will zum debuggen dieser function dann einen "trigger" 
setzen. dieser würde ja aber immer aufgerufen werden, wenn ich die 
function1() rufe, deswegen will ich alle funktions-aufrufe aufgelöst 
haben, damit ich den trigger manuell an die richtige stelle setzen kann, 
und will nicht jedesmal mit copy'n'paste alles hin&her kopieren müssen.

mfg

von Kola (Gast)


Lesenswert?

Je nach compiler kannst du unter "inline" oder "inlining" was finden. 
Dann das ganze durch den Präprozessor jagen, und du hast, was du willst.

von Karl heinz B. (kbucheg)


Lesenswert?

Ist aber trotzdem eine bescheuerte Idee.

von Wolfgang Pelst (Gast)


Lesenswert?

...diese Antwort wird ihm sicher weiterhelfen Karl.
Ich kenne mich in diesem Zusammenhang nicht sonderlich gut aus, aber 
sollte es nicht möglich sein ein make-file dementsprechend abzuändern, 
dass dieses die von Kola angesprochene Lösung bietet? Das wäre doch 
zumindest ein wenig mehr Bequemlichkeit, auch wenn es kein wirkliches 
"Tool" ist.

von Stefan (Gast)


Lesenswert?

Theoretisch ist vieles möglich.

In der Praxis kann man sich z.B. Probleme mit lokalen Variablen 
einhandeln. Noch schlimmer wird es, wenn z.B. rekursive Funktionen im 
Spiel sind.

Im Einzelfall mag es möglich und vielleicht auch sinnvoll sein, 
Funktionen derart zu entrollen. Aber wie gesagt im Einzelfall bei 
genauer Analyse der Source und wenn man genau weiss wofür.

Als sinnvollen Einzelfall zähle ich nicht "die Erleichterung beim 
Debuggen". Im Gegenteil - das Debuggen wird denke ich deutlich 
schwieriger.

Mit den Funktionen kann man prima Breakpoints an den Funktionsanfang 
setzen und man debuggt dann genau eine Codestelle. woher man kommt, kann 
man prima mit dem Stackinspektor sehen. Und die lokalen Variablen und 
Funktionsargumente auch...

Bei entrollten Funktioen muss man Breakpoints an alle (!) Codestellen 
setzen, wo die Funktion eingesetzt wurde (oder man debuggt sich mühsam 
sequentiell durchs Programm). Bei µC mit ggf. einer begrenzten Zahl von 
möglichen Breakpoints kann das schon das Killerargument sein.

von Stefan (Gast)


Lesenswert?

Ach so - das mit dem Trigger kann man auch anders lösen.

Im einfachen Fall z.B. so:

Du kannst eine globale Variable debuglevel anlegen und dann zur Laufzeit 
mit dem Debugger ändern. Innerhalb der zu untersuchenden Funktion machst 
du eine Abfrage der Variable und setzt einen Breakpoint auf den Fall wo 
die Abfrage zutrifft.

Moderne Debugger haben auch Möglichkeiten bedingte Breakpoints zu 
setzen. Z.B. der Breakpoint wird scharf, wenn die Stelle x-mal 
überlaufen wurde oder eine Variable einen betimmten Wert hat...

von Karl H. (kbuchegg)


Lesenswert?

Oder man setzt den Breakpoint ganz einfach an die
Stelle an der der interssierende Aufruf erfolgt.
Debugger bleibt dort stehen, einmal ein Single Step
in die Funktion hinein und man ist dort.

von ich&er (Gast)


Lesenswert?

hallo,

erstmal vielen dank für die antworten. das problem in meinem fall ist 
einfach, dass mir von vornherein kein "software"-debugger zur verfügung 
steht (also nichts mit single-step, register "mal eben nebenbei" 
auslesen etc pp), weil ich den code für den powerpc quasi nur im 
"text"-editor schreibe (ok, so schlimm ist es nicht, der editor hat 
schon syntax-highlighting etc), ich den code dann über eine 
putty-verbindung auf einer anderen maschine kompiliere und auf einer 
dritten für den mpc zur verfügung stelle. breakpoints zu implementieren 
habe ich schon versucht, aber das problem ist dann manchmal einfach 
folgendes: ich habe eine funktion die dafür verantwortlich ist, dass ein 
bestimmtes paket (konkret für USB) vorbereitet und gesendet wird...nun 
kann man sich leicht vorstellen, dass diese funktion hunderte male 
(teilweise durch schleifen etc) aufgerufen wird, bevor ich an die für 
mich interessante stelle komme.wann das sein wird, kann ich mir zwar 
vorher ausrechnen, aber es ist ja nie gesagt, dass zwischendurch nicht 
mal ein paket abgewiesen wird (durch das angeschlossene usb-device) und 
dieses erneut gesendet werden muss...und schon kommt die rechnung nicht 
mehr hin. vor die funktionsaufru (zb in der main) den breakpoint (in 
meinem fall eher ein triggersignal, weil ich das programm nat nicht 
anhalten kann) zu setzen macht off wegen der zeitlichen auflösung keinen 
sinn, weil selbst bei 2GS/s-oszis nichts mehr zu machen ist, wenn der 
trigger zu weit vom eigentlich zu untersuchenden ereignis in der 
untersuchten funktion ist. also vielleicht trägt das ein wenig zum 
verständnis meines anliegens bei, die anderen sachen habe ich natürlich 
als erstes probiert, und ein usb-hardware-analyzer mit dem ich bestimmte 
pakete einfach später nochmal betrachten kann steht mir nicht zur 
verfügung...

von Stefan (Gast)


Lesenswert?

Aha. Das Debuggen wird interessant ;-) Der Trigger soll also nahe an den 
problematischen Befehlen platziert werden. Hier verschiedene 
Varianten...

int main(void)
{
   // deine Variante #1. Funktion entrollen
   // Nachteil: schlechte Wartung und ggf. problematisch zu machen
   // Vorteil: Trigger unmittelbar an den Befehlen
   triggerbefehl;
   befehl1;
   befehl2;

   // andere Variante #2.
   // Nachteil: ggf. Zeitproblem durch den Funktionsaufruf
   triggerbefehl;
   funktion1();

   // andere Variante #3
   // Nachteil: ggf. noch grösseres Zeitproblem durch 2 Funktionsaufrufe
   trigger();
   funktion1();

   // andere Variante #4 zu #2
   // Verlagerung der Triggerung näher den Funktionsbefehlen
   // Nachteil: Abfrage in der Funktion beeinflusst auch
   // Zeitverhalten ungetriggerter Aufrufe
   triggerflag = TRUE;
   funktion2();
   triggerflag = FALSE;
}

Mit folgenden Funktionen

void funktion1(void)
{
   befehl1;
   befehl2;
}

void funktion2()
{
   if (trigger)
       triggerbefehl;

   befehl1;
   befehl2;
}

void trigger(void)
{
   triggerbefehl; // ... ggf. mehrere Befehle
}

Variante 1 könnte für die kritische Funktion durch ein Makro 
implementiert werden:

#ifdef MYDEBUG
#define trigger() triggerbefehl
#define funktion1()     \
{                       \
   int lokale_variable; \
   befehl1;             \
   befehl2;             \
}
#else
#define trigger()
void funktion1(void)
{
   befehl1;
   befehl2;
}
#endif /* MYDEBUG */

Innerhalb von main() ist der Code unverändert, bis auf den zusätzlichen 
Triggerbefehl

int main(void)
{
   ...
   trigger();
   funktion1();
   ...
}

dies wird bei gesetztem Makro MYDEBUG vom Präprozessor ersetzt durch:

int main(void)
{
   ...
   triggerbefehl;
   {
      int lokale_variable;
      befehl1;
      befehl2;
   };
   ...
}

Die Klammerung ist wichtig wg. lokalen Variablen.
Und bei nicht gesetztem Makro MYDEBUG durch

int main(void)
{
   ...
   ;
   funktion1();
   ...
}

Ich könnte mich für Variante #4 erwärmen, wenn der minimale Overhead 
durch die Flagabfrage nicht stört.

von ich&er (Gast)


Lesenswert?

danke für die ausführliche antwort stefan...
bei den varianten 2-4 hast du recht, die sind alle samt ungünstig was 
den zeitfaktor angeht.
deine makro-variante gefällt mir soweit ganz gut, hat aber für mich 
leider immer noch ein problem, das vll leider durch meine erklärung noch 
nicht ganz deutlich geworden ist. die triggerfunktion macht im grunde 
nichts anderes, als eine flanke zu erzeugen, die ich als trigger für den 
oszi nutze. ab dem punkt lese ich ich dann auf dem USB (auf der 
datenleitung) den verkehr mit und sehe mir an was so hin&her läuft. 
zeitlich ist das aber so kritisch, das man schon relativ genau "treffen" 
muss, damit die pakete noch im speicher des oszis sind. das problem ist 
einfach, dass ich den trigger-funktionsaufruf trigger() an verschiedene 
stellen in der untersuchten funktion packen können muss...du kannst dir 
die zu untersuchende funktion etwa so vorstellen

void send_stuff()
{
    send_paket1(); // <- mal trigger ich hier...
    //wait for response
    send_paket2();
    //wait for response
    send_paket3();
    //wait for response
    send_paket2();
    //wait for response
    send_paket2(); // <- ...mal dort
    //wait for response
    send_paket1();
    //wait for response
}

mir bringt es also leider nichts, wenn der trigger vor der funktion 
greift, ich muss ihn auch IN der funktion "verschieben" können.
jetzt könnte ich das nat. mit deiner makro-variante machen, und im 
"MYDEBUG" teil bequem den trigger()-aufruf verschieben wie ich es will, 
ABER: dann habe ich das ja wieder für jeden aufruf der send_stuff - 
funktion, und im grunde nichts gewonnen !?
deshalb schien mir das aufrollen, wenn auch extrem unübersichtlich weil 
der code so lang wird, am leichtesten, weil ich den trigger() einfach 
einmalig einfüge und der garantiert nie wieder aufgerufen wird, und ich 
nichts nebenher mitzählen muss (was sich vll gar nicht zählen lässt)

puhh...viel zu lesen, ich hoffe ich konnte das problem halbwegs 
schildern ;-)

beste grüße

von Stefan (Gast)


Lesenswert?

Das ist dann doch nur eine Sache, wo du das Triggerflag positionierst 
und wo und wie es abgeprüft wird. Ich sehe das Problem mit dem Makro 
nicht. Du würdest ja deine send_stuff() Funktion so schreiben 
(angenommen du willst "mal dort" triggern):

void send_stuff()
{
    send_paket1(); // <- mal trigger ich hier...
    //wait for response
    send_paket2();
    //wait for response
    send_paket3();
    //wait for response
    send_paket2();
    //wait for response
    trigger();
    send_paket2(); // <- ...mal dort
    //wait for response
    send_paket1();
    //wait for response
}

In obigem Fall würde ich also das Flag in send_stuff() an der 
entsprechenden Position vor der interessierenden send_paket*() Funktion 
setzen und dann in den send_paket*() Funktionen abprüfen und den Trigger 
erzeugen.

Das Triggerflag muss auch nicht TRUE/FALSE sein. Du kannst mit 
verschiedenen Werten jeweils andere Codestellen triggern.

Vielleicht sogar indem du den Wert für das Triggerflag über freien 
Input-Ports oder Schnittstellen manipulierst. Denkbar ist z.B. eine 
Triggerung auf die Rücksprungadresse auf dem Stack oder ganz bequem auf 
die aufrufende Sourcecodezeile (Stichwort _LINE_). Dazu würde die 
Anweisung triggerflag = ... durch eine triggersetz-Funktion z.B. über 
Ports oder einen seriellen Monitor ersetzt. Dann ist fürs Debuggen auch 
nicht jedesmal ein neuer Übersetzungslauf + das Einspielen auf den µC 
notwendig.

von ich&er (Gast)


Lesenswert?

hmm, die kombination aus makro und flag klingt jetzt doch am 
sinnvollsten, da hast du absolut recht...
ich setze mir im makro die trigger-funktion nebst flag-check einfach an 
die passende stelle und dann in der main() (oder wo auch immer) ab dem 
interessanten zeitpunkt das flag auf TRUE, ggf nutze ich einfach mehrere 
flags wenn es zu verschachtelt wird - das sollte dann eigentlich seinen 
zweck erfüllen und das gewünschte ereignis genau genug herausfiltern. 
ich werd' das einfach mal ausprobieren, vielen dank auf jeden fall für 
den denkanstoß und deine zeit...

beste grüße

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.