Forum: Compiler & IDEs Inline Member Funktionen


von Hermann E. (hermann_e)


Lesenswert?

Guten Tag,

im Moment schlage ich mich folgendem Problem 'rum:
Ich möchte eine Klasse schaffen in der die Member-Funktion(-en) in jedem 
Objekt neu und unabhängig voneinander kreiert werden.
(D.h. der entsprechende Functionpointer ist für jedes(!) Objekt der 
Klasse ein anderer, die entsprechenden Member-Funktionen und ihre 
(statischen) Variablen sollen also im Programmspeicher koexistieren).

Normalerweise würde ich denken dass ich dies mit "inline" erzwingen 
kann.
Dazu habe ich folgenden Code zum Testen geschrieben um es dann mit dem 
Simulator durchzuspielen (neuste Version von AVR studio):
1
#define F_CPU 1000000UL
2
#include <avr/io.h>
3
4
class test
5
{
6
  public:
7
  int  function(int t);
8
};
9
//--------------------------------------------------
10
inline int  test::function(int t)
11
{
12
  static int g = 1;
13
  int tt = g*t;
14
  g = tt;
15
16
  return  g;
17
}
18
//-----------------------------------------------------
19
20
int main(void)
21
{
22
  test T1;
23
  test T2;
24
  volatile int tv1, tv2,tv3;
25
    
26
  tv1 = T1.function(2);      
27
  tv2 = T2.function(2);
28
  
29
  tv3 = tv1+tv2;
30
    
31
  while(1)
32
    {    
33
    }
34
}  
35
//------------------------------------------------------------
(tv3 dient nur zum Einfügen eines "Breakpoints)
Falls T1.Function und T2.Function separat existieren würden, wäre 
natürlich  das Ergebniss in tv1 = tv2 = 2. Dem ist aber nicht so, 
sondern es wird: tv1=2 und  tv2=5 !!
D.h. die statische Variable "g" ist für beide Objekte T1 und T2 die 
Gleiche, "inline" hat also nicht wie erhoft funktioniert.

Wüsste jemand eine Möglichkeit dies zu erzingen ?

Grüsse und Danke:  Hermann

: Bearbeitet durch User
von Bastler (Gast)


Lesenswert?

Member-Variable in die Klasse einbauen. Die existiert dann für jede 
Instanz getrennt.
Static in (nicht-static) Methode oder generell in einer Klasse wirkt auf 
mich komisch. Dafür gäbe es in C++ die statische Member Variable.
BTW, inline bedeutet nur, daß man sich das Call spart und den 
hoffentlich kurzen Code statt dessen direkt einfügt. Es bedeutet nicht, 
daß man eine "Instanz der Methode" erhält. Der Compiler darf inline 
sogar ignorieren. Er könnte ja nach Größe optimieren sollen und 
feststellen, daß inline das vereitelt.

von Hermann E. (hermann_e)


Lesenswert?

Danke für deine schnelle Antwort,

in der Tat, die Variablen alle auf der Klassenebene zu definieren löst 
das Problem der statischen Variablen. Der Hintergrund dass ich mehrere 
Funktionsinstanzen haben will ist folgender: Die Klasse soll eine Klasse 
für Schrittmotoren werden. Jedes Objekt entspricht dann einer 
Achse/Motor.
Für meine Anwendung ist es manchmal notwendig zwei Achen gleichzeitig zu 
fahren, dies geschieht mit timer0 (ISR T0) und timer2(ISR T2) 
(Atmega644).
Die Funktion um die es geht berechnet den nächsten Schritt bzw. die 
Schrittdauer.
Was ich letztlich verhindern will ist dass (durch die Interruptabfolge 
der beiden Timer) der erste Interrupt (sagen wir mal T0) in die Funktion 
einsteigt und kurz danach T2 nachkommt -bevor T0 fertig ist- und alles 
durcheinander wirbelt.
Natürlich könnte ich mit cli() die Funktion atomar ausführen, hab' aber 
ein sehr ungutes Gefühl dabei, da ich dann alles außen rum blockiere.
Grüsse:  Hermann

: Bearbeitet durch User
von Bastler (Gast)


Lesenswert?

Durcheinander wirbeln ergibt sich aber daraus, daß Dinge vermischt sind.
Du brauchst g, das zwischen den Interrupts erhalten bleiben soll, 
richtig?
Und es gibt zwei Instanzen T1und T2, für 2 unabhängige Achsen, richtig?
Also Member-Variable g in die Klasse test. So wie das jetzt da steht, 
gibt es g nur einmal und daran kann (und darf) inline nichts ändern.

von Hermann E. (hermann_e)


Lesenswert?

hm,

es geht dabei nicht (nur) um 'g'. Die eigentliche Funktion ist natürlich 
weit komplexer wie diejenige die ich eingestellt habe. Zur 
Schrittberechnung sind halt noch jede Menge zusätzlicher (auf das Scope 
der Funktion) beschränkte kurzlebige Variablen von Nöten. Ein zweiter 
Eingriff in die(selbe) Funktion durch die ISR T2 könnte diese 
kurzlebigen Parameter ja verändern und damit die Schrittberechnung von 
ISR T0 (zer-)stören.
Oder versteh' ich da was falsch ?

Grüße:  Hermann

von Dr. Sommer (Gast)


Lesenswert?

Hermann E. schrieb:
> (D.h. der entsprechende Functionpointer ist für jedes(!) Objekt der
> Klasse ein anderer, die entsprechenden Member-Funktionen und ihre
> (statischen) Variablen sollen also im Programmspeicher koexistieren).
Funktionspointer haben nichts mit Member-Funktionen zu tun. Es gibt in 
deinem Beispiel genau eine test::function, und genau die wird immer 
direkt (ohne Pointer) aufgerufen.
> Normalerweise würde ich denken dass ich dies mit "inline" erzwingen
> kann.
inline ist bloß eine Hinweis an den Compiler, dass er den Code der 
Funktion an die Stelle des aufrufs kopieren "sollte", um den 
Aufruf-Aufwand zu sparen. Es verändert nicht das Verhalten des 
Programms, sondern hat höchstens Einfluss auf die 
Geschwindigkeit/Programmgröße.

Hermann E. schrieb:
> Natürlich könnte ich mit cli() die Funktion atomar ausführen, hab' aber
> ein sehr ungutes Gefühl dabei, da ich dann alles außen rum blockiere.
> Grüsse:  Hermann
Und deswegen gibt es Controller mit Interrupt(Gruppen)Prioritäten, wie 
zB. ARMv7M (zB. STM32, LPC...) - hier kannst du vollautomatisch den 
Aufruf von z.B. der Timer0-ISR abschalten solange die Timer1-ISR läuft 
(gleiche Priorität), aber z.B. Aufrufe der USART0-ISR immer zulassen 
(höhere Priorität).

Hermann E. schrieb:
> Zur
> Schrittberechnung sind halt noch jede Menge zusätzlicher (auf das Scope
> der Funktion) beschränkte kurzlebige Variablen von Nöten. Ein zweiter
> Eingriff in die(selbe) Funktion durch die ISR T2 könnte diese
> kurzlebigen Parameter ja verändern und damit die Schrittberechnung von
> ISR T0 (zer-)stören.
Wie das denn? Die temporären Variablen liegen auf dem aktuellen 
Stack-Frame, und wenn ein Interrupt kommt und dieselbe oder eine andere 
Funktion aufruft, wird eine neues Stack-Frame aufgemacht und dort die 
Variablen abgelegt. Probleme gibts erst wenn der Stack zu klein ist oder 
du auf Variablen zugreifst, die zwischen den ISR's und der main geteilt 
werden, wie zB globale Variablen, Member-Variablen von globalen 
Objekten, Pointer jeweils auf Objekte anderer ISR's... Funktionslokale 
Variablen sind jedenfalls immer lokal, solange du nicht explizit per 
Pointer/Referenz Zugriff an andere Programmteile gibst.

Hermann E. schrieb:
> Natürlich könnte ich mit cli() die Funktion atomar ausführen
Standardmäßig sind auf AVR's während der Abarbeitung einer ISR onehin 
die Interrupts abgeschaltet solange du sie nicht explizit mit sei() 
wieder einschaltest, und alles innerhalb der ISR somit "atomar". Beim 
Zurückkehren der ISR (Instruktion "sei") werden sie wieder 
eingeschaltet.

von Dr. Sommer (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Beim
> Zurückkehren der ISR (Instruktion "sei") werden sie wieder
> eingeschaltet.
Gnah, "reti" natürlich.

von Oliver S. (oliverso)


Lesenswert?

Falscher Lösungsansatz für eventuell richtig erkanntes Problem.

Wenn du es wirklich hinbekommst, auf deinem AVR Multithreading zu 
implementieren, dann müssen die Funktionen reentrant sein. Dann kommt da 
auch nichts durcheinder, wenn mehrere Threads "gleichzeitig" die selbe 
Funktion nutzen. Google hilft zum Thema weiter.

Aber mal ganz dumm gefragt: Wie genau willst du das machen, mit zwei 
Timern zwei Motoren gleichzeitig laufen zu lassen? Denn ich fürchte mal, 
dein ganzes Programmkomzept wird so überhaupt nicht funktionieren.

Oliver

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Hermann E. schrieb:
> Natürlich könnte ich mit cli() die Funktion atomar ausführen, hab' aber
> ein sehr ungutes Gefühl dabei, da ich dann alles außen rum blockiere.

Nun, auf einem AtMega deaktiviert die Hardware von sich aus beim 
Eintritt in eine ISR das globale Interruptflag. Und solange man nicht 
ganz genau weiß, was man da tut, sollte man das auch so belassen.

Eine ISR von einer anderen, die etwas ähnliches macht, unterbrechen zu 
lassen, bringt gar nichts. Lass die ISRs sauber hintereinander ablaufen, 
das erspart dir sehr viele unschöne Dinge, und "gleichzeitiger" gehts 
sowieso nicht.

Oliver

von Thomas M. (langhaarrocker)


Lesenswert?

Hermann E. schrieb:
> Was ich letztlich verhindern will ist dass (durch die Interruptabfolge
> der beiden Timer) der erste Interrupt (sagen wir mal T0) in die Funktion
> einsteigt und kurz danach T2 nachkommt -bevor T0 fertig ist- und alles
> durcheinander wirbelt.

Also werden diese Methoden der Motorenklassen aus den ISRs aufgerufen? 
Und Du möchtest verhindern, dass zwei Objekte (also zwei Instanzen 
dieser Motorenklassen) eine geteilte Ressource - nämlich die Timer - 
koordiniert benutzen? Dann ist das Problem auch nicht bei den 
Motorenklassen, sondern bei der geteilten Ressource zu lösen.

Wie andere schon sagten: mit inline hat das gar nichts zu tun. Auch zu 
versuchen Funktionen zu verdoppeln ist sinnfrei. Mach sowas wie 'g' in 
Deinem Beispiel zu einem normalen Member dann können sich die Instanzen 
erst mal nicht gegenseitig beeinflussen.

Um das Problem mit der geteilten Ressource (Timer) zu lösen könntest Du 
den in eine eingene Klasse, ein Singleton verpacken. Davon gäbe es dann 
genau eine einzige Instanz, die sich alle Motorenobjekte teilen. Dieses 
Timerobjekt kümmert sich dann um die Koordination der Zugroffe. Das geht 
z.B. mit einer Queue, in die jedes Motorenobjekt Events wie "Motor mit 
id 2 will zum Zeitpunkt X benachrichtigt werden" rein legen kann. 
Jedesmal wenn ein Timerinterrupt auftritt, wird die Queue abgearbeitet, 
und der entsprechende Motor gefragt, wann er wieder geweckt werden 
möchte.
Es gibt auch andere Wege, z.B. Mutex Objekte, die ein beschäftigtes 
Timerobjekt vor wilden Zugriffen schützen. Dann muss ein Motor, der eine 
neue Weckzeit eintragen möchte warten bis der Mutex wieder freigegeben 
wird.

von Hermann E. (hermann_e)


Lesenswert?

Wow,
da kam ja 'ne ganze Lawine an Antworten !
Super, freut mich dass die Community so hilfreich ist.
Ich war gerade mit meiner Freundin draußen um vom schönen Wetter zu 
profitieren und muss jetzt erst mal essen und vor allem all das was 
geschrieben wurde (+Essen) verdauen.

Mehr heute Abend:  H

von Hermann E. (hermann_e)


Lesenswert?

Aaaalso,

ich antworte mal in chronologischer Folge, nicht nach Wichtigkeit 
geordnet.
Offensichtlich sind da einige Dinge die ich nicht richtig verstanden 
hatte (wie die Auswirkung von "inline"), doch mehr dazu im Text:

Dr. Sommer schrieb:
> Funktionspointer haben nichts mit Member-Funktionen zu tun. Es gibt in
> deinem Beispiel genau eine test::function, und genau die wird immer
> direkt (ohne Pointer) aufgerufen.

Ich wollte mit dem Hinweis auf den Funktionspointer nur 
unmissverständlich klar machen dass ich 2 komplett getrennte Instanzen 
der selben (Member-) Funktionen realisieren wollte.

Dr. Sommer schrieb:
> inline ist bloß eine Hinweis an den Compiler, dass er den Code der
> Funktion an die Stelle des aufrufs kopieren "sollte", um den
> Aufruf-Aufwand zu sparen. Es verändert nicht das Verhalten des
> Programms, sondern hat höchstens Einfluss auf die
> Geschwindigkeit/Programmgröße.

Da lag wohl einer meiner Denkfehler: Beim Googlen hatte ich vorher schon 
die "Optionalität" (=sollte)  von "inline" gesehen. Allerdings lässt es 
mich mit ein wenig Verwirrung zurück: Mein Gedanke war dass ein "inline" 
Programmteil sich so verhält als ob er direkt in den aufrufenden Code 
eingefügt wurde. (Das würde heißen: an zwei verschiedenen Stellen im 
Programm aufgerufen => als Funktion zweimal realisiert) In der Tat kann 
man sich bei Memberfunktionen aber fragen was das dann in der Praxis 
bedeutet (muss also nochmals selbst nachforschen)

Dr. Sommer schrieb:
> Und deswegen gibt es Controller mit Interrupt(Gruppen)Prioritäten

Richtig, Das ist ja auch beim Atmega644 der Fall. Bei mir muss vor allem 
der Uart und timer1 (meine Zeitbasis) noch höhere Priorität haben. 
Richtig ist (hatte diesen Punkt aber vegessen) dass "nested Interrupts" 
nicht der Default sind sondern erst durch "ISR_NOBLOCK" erzwungen werden 
müssen.

Dr. Sommer schrieb:
> Wie das denn? Die temporären Variablen liegen auf dem aktuellen
> Stack-Frame, und wenn ein Interrupt kommt und dieselbe oder eine andere
> Funktion aufruft, wird eine neues Stack-Frame aufgemacht und dort die
> Variablen abgelegt.
Das wusste ich schlicht nicht: Danke für die Info, wieder was gelernt !

Oliver S. schrieb:
> Aber mal ganz dumm gefragt: Wie genau willst du das machen, mit zwei
> Timern zwei Motoren gleichzeitig laufen zu lassen? Denn ich fürchte mal,
> dein ganzes Programmkomzept wird so überhaupt nicht funktionieren.

Nee, das läuft nämlich schon. Ich kann zwei Schrittmotoren einen auf T0 
und den andern T2 mit einem 14Mhz Baudquartz problemlos und ohne Zuckeln 
mit 2000 Schritten/s (oder mehr) parallel laufen lassen. Das 
funktioniert auch wenn beide Motoren gleichzeitig beschleunigen/bremsen 
(also während dem rechenintensivsten Teil der Bewegung) auch dann wenn 
noch Zusatzjobs anfallen. Mir gefällt aber meine eigene Programmstruktur 
nicht. Bislang habe ich nämlich eine T0 und eine getrennte T2 Klasse 
(und vermeide so das ganze Interferenzproblem). Das nervt aber da ich 
alle Änderungen  für beide Klassen (damit also doppelt) ausführen muss.
Die ISR selbst tut nur wenig: sie setzt im wesentlichen nur den nächsten 
OCR-Wert und eine Flag dass mit der Berechnung des darauffolgenden OCR 
Werts in der main "while(1=1)"-Schleife  begonnen werden kann. 
Darüberhinaus habe ich alle OCR Werte "doppelt gebuffert", d.h. in 
Wirklichkeit wird also sicherheitshalber nicht auf einen sondern auf 
zwei OCR-Werte im Voraus gerechnet (falls mal ein zeitintensiver Job 
dazwischen kommt).
Der springende Punkt sind aber genau die zwei Funktionen die mir die 
nächsten OCR's für timer0 bzw. 2 in der main-Schleife berechnen: Diese 
sind nämlich jeweils Member-funktionen der T0 bzw. T2 Klasse und die 
Befürchtung war/ist ein möglicher Konflikt in der OCR-Berechnung wenn 
ich die beide zu einer einzigen Klasse zusammenfüge. (Hoffe meine 
Ausführung ist so verständlich).

Nachmal ein dickes Thanx und natürlich sind weitere Vorschläge/Kritiken 
willkommen:

Hermann

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Hermann E. schrieb:
> 2 komplett getrennte Instanzen
> der selben (Member-) Funktionen realisieren wollte.
Von Funktionen gibt es keine Instanzen... Jede Funktion ist genau 1x im 
Programmspeicher, und wird nur mit verschiedenen Instanzen der Objekte 
aufgerufen (der "this"-Pointer wird implizit übergeben). Nur eben die 
lokalen Variablen werden für jeden Aufruf extra auf dem Stack angelegt, 
aber da ist es völlig unerheblich ob das eine globale oder 
Member-Funktion ist.

Hermann E. schrieb:
> Mein Gedanke war dass ein "inline"
> Programmteil sich so verhält als ob er direkt in den aufrufenden Code
> eingefügt wurde.
Nicht so als ob der C/C++ Code einfach kopiert würde. Er verhält sich 
von der Logik her wie eine normale Funktion, und lokale Variablen 
werden dann eben im Stackframe des Aufrufers angelegt.
> (Das würde heißen: an zwei verschiedenen Stellen im
> Programm aufgerufen => als Funktion zweimal realisiert)
Der Code taucht dann 2x auf, ja.

Hermann E. schrieb:
> Richtig, Das ist ja auch beim Atmega644 der Fall
Ja dann ist das ja optimal, dann können sich die beiden Timer-ISR's gar 
nicht in die Quere kommen.

PS: Beim AVR werden die meisten lokalen Variablen tatsächlich in 
Registern sein, da er so viele davon hat, aber Stack-Zugriffe 
ineffizient sind. Beim Aufruf einer Funktion oder ISR werden aber die 
Register auf den Stack gesichert.

von Hermann E. (hermann_e)


Lesenswert?

Dr. Sommer schrieb:
> Von Funktionen gibt es keine Instanzen... Jede Funktion ist genau 1x im
> Programmspeicher, und wird nur mit verschiedenen Instanzen der Objekte
> aufgerufen (der "this"-Pointer wird implizit übergeben)

Jop, da war wohl mein Hauptdenkfehler. Mir war schon klar dass eine 
Memberfunktion grundsätzlich nur einmal im Programmspeicher abgelegt 
wird, ging aber fälschlicherweise davon aus dass ich mit "inline" 
weitere "Instanzen" erzwingen könnte.

Gut wenn ich das hier geschriebene so für mich zusammenfasse ziehe ich 
folgende Lehre:

A) Alle permanenten (um das Wort "statisch" zu vermeiden) Variablen pro 
Motor auf Klassenebene definieren
und
B) Keine statischen Variablen die eine Erinnerung an vorhergehende 
Prozesse lassen könnten in die Memberfunktionen packen.
C) Solange keine "Nested Interrupts" definiert sind vermeidet die 
sequenzielle Ausführung der (selben) Funktionen Interferenzen.

Mal sehen wie weit ich damit komme:  Hermann

: Bearbeitet durch User
von Thomas M. (langhaarrocker)


Lesenswert?

Hermann E. schrieb:
> A) Alle permanenten (um das Wort "statisch" zu vermeiden) Variablen pro
> Motor auf Klassenebene definieren

Genau. Das sind die Variablen des Objekts um dessen Eigenschaften zu 
halten. Da diese Variablen an die Lebensdauer des Objekts gekoppelt 
sind, braucht man keine weiteren Maßnahmen zu ergreifen, damit sie 
erhalten bleiben wenn z.B. eine Funktion verlassen wird. Diese Variablen 
existieren weiter bis das Objekt gelöscht wird.

> B) Keine statischen Variablen die eine Erinnerung an vorhergehende
> Prozesse lassen könnten in die Memberfunktionen packen.

Richtig. Sonst wären sie objektübergreifend. Falls man doch mal 
Informationen halten möchte, die für alle Instzanzen einer Klasse 
gelten, dann gehören die als static Variablen in die Deklaration der 
Klasse, aber nicht in die Memberfunktionen.

von Hermann E. (hermann_e)


Lesenswert?

Supa,

hab' mir gerade nochmals meinen Code angeschaut und denke dass ich das 
so hinkriege. Gleichzeitig erhoffe ich mir dass er durch diese 
Veränderungen auch lesbarer wird.

Danke für eure Hilfe:  Hermann

von Oliver (Gast)


Lesenswert?

Hermann E. schrieb:
> A) Alle permanenten (um das Wort "statisch" zu vermeiden) Variablen pro
> Motor auf Klassenebene definieren

Das ist genau das grundlegende Prinzip aller objektorienterten Ansätzen: 
Jede Instanz eines Objektes hat ihren eigenen Datensatz.

Oliver

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.