Forum: Mikrocontroller und Digitale Elektronik STM32 C++ Memberfunktion in Interrupt


von Oliver F. (ollif)


Lesenswert?

Hallo,

mache gerade meine ersten Experimente mit C++ auf einem STM32f429 
Discoveryboard.

Jetzt stehe ich vor folgendem Problem.
Ich habe eine Klasse in der es eine Funktion
1
float doEnvelope();
gibt.
Diese funktion möchte ich in einet Timer ISR aufrufen.
Diese soll den Rückgabewert über den DAC ausgeben.

Jetzt zu meiner Frage wie rufe ich die Funktion auf?

Habe es wie folgt versucht
1
volatile CEnvelopeGenerator EG1;  //global in main.cpp
2
3
4
void TIM2_IRQHandler(void){
5
6
  TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
7
  if (ISR_COUNTER == 0)
8
  {
9
10
    TOOGLE_LED_GREEN;
11
12
    float Env = EG1.doEnvelope();
13
                //Ausgabe env auf Dac
14
                .....
15
16
}

beim kompilieren bekomme ich folgende Fehlermeldung:

Invalid arguments '
Candidates are:
float doEnvelope()

Was mache ich hier falsch?
Vielen Dank im voraus.

Greetz

von Jim M. (turboj)


Lesenswert?

Kann es sein das da einfach der Header vergessen wurde?

Außerdem ist das ja jetzt zingend C++, damit muss man folgendes machen:
1
volatile CEnvelopeGenerator EG1;  //global in main.cpp
2
3
extern "C" {
4
void TIM2_IRQHandler(void);
5
}
6
;
7
8
void TIM2_IRQHandler(void)
9
{
10
 //...
11
}

Ansonsten wunderst Du Dich warum der Handler nicht aufgerufen wird.

von Oliver F. (ollif)


Lesenswert?

Hallo,

Dies habe ich schon so umgesetzt. Der Handler wird auch aufgerufen.
Habe dies schon mit statischen werten in der ISR überprüft.

Greetz

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Jim M. schrieb:
> Kann es sein das da einfach der Header vergessen wurde?

Dann müsste der Compiler schon die definition von EG1 anmeckern.

>
> Außerdem ist das ja jetzt zingend C++, damit muss man folgendes machen:

Komm auf den Startup code an, der die Vectortable definiert. 
Wahrscheinluch hast Du aber Recht und er müsste der ISR C-Binding geben. 
Das geht aber viel einfacher:
1
extern "C" void TIM2_IRQHandler()
2
{
3
...
4
}

Das Problem ist aber, dass EG1 `voltaile` definiert. `volatile` verhält 
sich analog zu `const` und dem entsprechend muss doEnvelope() `volatile` 
qualifiziert werden:
1
float doEnvelope() volatile;

mfg Torsten

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Wobei es keine wirklich sinnvolle Definition gibt, was eine volatile 
qualified member function macht. Ich würde einfach dokumentieren, dass 
die Klasse so implementiert ist, dass member function von einer ISR aus 
aufgerufen werden können und dann ggf. die notwendigen member Variablen 
volatile qualifizieren.

mfg Torsten

von Wilhelm M. (wimalopaan)


Lesenswert?

Torsten R. schrieb:
> Wobei es keine wirklich sinnvolle Definition gibt, was eine volatile
> qualified member function macht.

Man kann ja gemäß der CV-Qualifizierung überladen. Damit kann man die 
Anwendung einer Operation auf volatile / non-volatile Objekte 
unterscheiden. Und wenn man dann ein Exemplar dieser Klasse als 
non-volatile hat, kann der Compiler ggf. besser optimieren, was er im 
Fall der volatile-qualif. Elementfunktion ja nicht darf.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Wilhelm M. schrieb:
> Man kann ja gemäß der CV-Qualifizierung überladen. Damit kann man die
> Anwendung einer Operation auf volatile / non-volatile Objekte
> unterscheiden.

Da würde mir jetzt kein Beispiel einfallen, wo das sinnvoll wäre.

> Und wenn man dann ein Exemplar dieser Klasse als
> non-volatile hat, kann der Compiler ggf. besser optimieren, was er im
> Fall der volatile-qualif. Elementfunktion ja nicht darf.

Dazu müsste die Klasse die selbe Operation dann aber immer zwei mal 
implementieren. Und wenn ich die Operation zwei mal habe, kann ich durch 
das Weglassen des volatile qualifiers aus Versehen das falsche Set an 
Operationen erwischen. -> Würde ich nicht machen.

Kommt aber auch darauf an, wie wahrscheinlich es ist, das eine Klasse, 
die in der Kommunkation mit einer ISR eingesetzt wird auch in einem 
anderen Kontext sinnvoll ist.

Der volatile qualifier an der Variablen sorgt dafür dass bei der Klasse 
alle member functions auch volatile qualifiziert werden müssten und dass 
dadurch dann implizit auch alle member variables volatile qualifiziert 
sind.

Wenn ich jetzt z.B. einen Ring-Puffer habe, um mit einer ISR zu 
kommunizieren, dann müssen die Zeiger in den Puffer volatile sein, der 
Puffer selber aber nicht.

mfg Torsten

von Oliver F. (ollif)


Lesenswert?

Hallo,

vielen Dank für die Antworten. Ich denke mein Problem liegt in der 
Instanzinierung der globalen Klasse. HIer stellt sich für mich die Frage 
wo und wie ich dies erledige. Mache ich das mit "new" in der main? 
Benötige hier später ein Array aus CEnvelopeGenerator.


Fragen über Fragen.
Kann mir hier bitte jemand witerhelfen?

Vielen Dank

Greetz

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Warum um alles in der Welt wolltest Du new verwenden? Was ist den Deine 
"Problem"?

Wenn Du Hilfe brauchst, ist es ganz ratsam, so viele Hinweise auf Dein 
Problem, wie möglich zu geben. Mit der von mir vorgeschlagenen Änderung, 
sollte Dein Ursprüngliches Problem behoben sein.

> Fragen über Fragen.

Was hindert Dich den daran, konkrete Fragen zu stellen?

von Oliver F. (ollif)


Lesenswert?

Hallo,

So hier mal etwas mehr code...

1
#include "CEnvelopeGenerator.h"
2
3
CEnvelopeGenerator EG1;  // Diese Klasse soll global verfügbar sein damit sie in im Timer und USART ISR aufgerufen werden kann.
4
5
6
7
#ifdef __cplusplus
8
  extern "C" {
9
 #endif
10
 void TIM2_IRQHandler();
11
 #ifdef __cplusplus
12
  }
13
 #endif
14
15
//Interrupt handler declaration in C/C++
16
#ifdef __cplusplus
17
 extern "C" {
18
#endif
19
void USART1_IRQHandler();
20
#ifdef __cplusplus
21
 }
22
#endif
23
}
24
25
void initTimerIRQ(void){
26
27
  // FRQ = 84MHz / (Prescaler+1) / (Periode+1)
28
  TIM_TimeBaseInitTypeDef TIM_TimeBase_InitStructure;
29
  NVIC_InitTypeDef NVIC_InitStructure;
30
31
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
32
33
  TIM_TimeBase_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
34
  TIM_TimeBase_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
35
  TIM_TimeBase_InitStructure.TIM_Period = 9;
36
  TIM_TimeBase_InitStructure.TIM_Prescaler = 4199;
37
38
  TIM_TimeBaseInit(TIM2, &TIM_TimeBase_InitStructure);
39
40
  TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
41
42
  NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
43
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
44
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
45
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
46
  NVIC_Init(&NVIC_InitStructure);
47
48
  TIM_Cmd(TIM2, ENABLE);
49
}
50
51
52
void USART1_IRQHandler(void) {
53
54
    //hier werden die MIDI in Signale ausgewertet un bei bedarf NoteOn oder NoteOff aufgerufen
55
}
56
57
void noteOff(unsigned char channel, unsigned char pitch, unsigned char velocity){
58
    LED_RED_OFF;
59
    EG1.stopEG();    //ERROR request for member 'stopEG' in 'EG1', which is of non-class type 'CEnvelopeGenerator()'
60
    return;
61
}
62
63
void noteOn(unsigned char channel, unsigned char pitch, unsigned char velocity){
64
    LED_RED_ON;
65
    EG1.startEG();   //ERROR request for member 'startEG' in 'EG1', which is of non-class type 'CEnvelopeGenerator()'
66
    return;
67
}
68
69
70
void TIM2_IRQHandler(void){
71
  TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
72
  if (ISR_COUNTER == 0)
73
  {
74
    TOOGLE_LED_GREEN;
75
    UB_DAC_SetDAC2((uint16_t)(EG1.doEnvelope() *4095.0));  //ERROR request for member 'doEnvelope' in 'EG1', which is of non-class type 'CEnvelopeGenerator()'
76
  }
77
}
78
79
80
int main(void)
81
{
82
    SystemInit(); // Quarz Einstellungen aktivieren
83
    init();
84
85
    UB_DAC_Init(SINGLE_DAC2);
86
    initTimerIRQ();
87
88
    LED_GREEN_ON;
89
90
    while (1) {
91
     ;
92
   }
93
}
94
95
96
97
98
99
///"CEnvelopeGenerator.h"
100
101
102
#define EG_DEFAULT_STATE_TIME  1000.0f
103
104
enum modeStrings {
105
  analog, digital
106
};
107
108
enum envState {
109
    off ,
110
    attack,
111
    decay,
112
    sustain,
113
    release,
114
    shutdown
115
};
116
117
class  CEnvelopeGenerator{
118
public:
119
  bool m_bResetToZero;      //RTZ mode flag
120
  bool m_bLegatoMode;        //legato mode flag
121
  bool m_bOutputEG;        //true if this EG is connected to the output amplifier of the patch
122
  int m_uEGMode;          //mode flag
123
124
  CEnvelopeGenerator();
125
126
  void startEG();
127
  void stopEG();
128
  float doEnvelope();
129
130
131
protected:
132
  float m_fSampleRate;      //the samplerate
133
  float m_fEnvelopeOutput;      //the current Envelope Output
134
  float m_fAttackCoeff;      //coeff b for attack
135
  float m_fAttackOffset;      //attack offset x0
136
  float m_fAttackTCO;        //Attack Time constant overshoot
137
  float m_fDecayCoeff;      //coeff b for decay
138
  float m_fDecayOffset;      //decay offset x0
139
  float m_fDecayTCO;        //decay Time constant overshoot
140
  float m_fReleaseCoeff;      //coeff b for release
141
  float m_fReleaseOffset;      //release offset x0
142
  float m_fReleaseTCO;      //release Time constant overshoot
143
144
  float m_fAttackTime_mSec;      //attack time in milliseconds
145
  float m_fDecayTime_mSec;      //decay time in milliseconds
146
  float m_fReleaseTime_mSec;      //release time in milliseconds
147
  float m_fShutdownTime_mSec;      //shutdown time in milliseconds
148
  float m_fSustainLevel;      //sustain level [0...+1]
149
  float m_fIncShutdown;      //increment value for shutdown mode
150
  int m_uState;        //state variable
151
};

Die Interrupt Routinen sowie die Auwertung der Midi In Signale, die 
Ausgabe auf dem DAC funktionieren.


Was ich erreichen möchte.

Erstellen eines, später mehreren globalen CEnvelopeGenerator Objekten.

In den beiden ISRs müssen die Memberfunktion NoteOn(), NoteOff() 
doProcess() aufgerufen werden können.

Ist diesed Vorgehen falsch? Wenn ja was wäre hier die Alternative?

Fehlt hier vielleicht nur der Konstruktoraufruf? Das Objekt muss ja 
irgendwo erzeugt werden.

New oder ander dynamischen Sachen sind hier wohl falsch!!

Vielen Dank für eure Hilfe.

Greetz

von Vincent H. (vinci)


Lesenswert?

Oliver F. schrieb:
> Was ich erreichen möchte.
>
> Erstellen eines, später mehreren globalen CEnvelopeGenerator Objekten.
>
> In den beiden ISRs müssen die Memberfunktion NoteOn(), NoteOff()
> doProcess() aufgerufen werden können.
>
> Ist diesed Vorgehen falsch? Wenn ja was wäre hier die Alternative?
>
> Fehlt hier vielleicht nur der Konstruktoraufruf? Das Objekt muss ja
> irgendwo erzeugt werden.
>
> New oder ander dynamischen Sachen sind hier wohl falsch!!
>
> Vielen Dank für eure Hilfe.
>
> Greetz


Prinzipiell ist das Aufrufen von Member Funktionen aus einer ISR 
überhaupt kein Problem? Folgendes compiliert mit GCC 6.3.1 problemlos:

1
struct SomeClass
2
{
3
  void foo()
4
  {
5
    trace_printf("foo\n");
6
  }
7
};
8
9
SomeClass c1;
10
SomeClass c2;
11
12
void SysTick_Handler()
13
{
14
  HAL_IncTick();
15
  HAL_SYSTICK_IRQHandler();
16
  c1.foo();
17
  c2.foo();
18
}



"Falsch" ist dieses Vorgehen perse nicht. Ich persönlich bin kein großer 
Freund dieser Variante und lasse meine Interrupts lieber von einer 
eigenen Klasse managen. Das kann man aber, sobald man ein tieferes 
Verständnis von C++ hat immer noch implementieren...

Der Konstruktur wird übrigens sowohl in deinem, als auch in meinem 
Beispiel aufgerufen. Das lässt sich sehr einfach überprüfen, indem du 
innerhalb des Konstrukturs einen Breakpoint setzt. Beim Debuggen wirst 
du dann (vermutlich) merken, dass der Aufruf statt findet, noch bevor du 
in main() landest.

1
class  CEnvelopeGenerator
2
{
3
4
CEnvelopeGenerator()
5
{
6
  asm volatile("nop"); // hier Break
7
}
8
9
}


/edit
Hungarian Notation ist übrigens furchtbar. ;)

: Bearbeitet durch User
von Oliver F. (ollif)


Lesenswert?

Hallo,




jetz bekomme ich:

arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -mfloat-abi=hard 
-mfpu=fpv4-sp-d16 
-T"C:\Users\oliverf\workspace\HelloDSP\LinkerScript.ld" 
-Wl,-Map=output.map -Wl,--gc-sections -fno-exceptions -fno-rtti -lm -o 
"HelloDSP.elf" @"objects.list"
src/main.o: In function `noteOff(unsigned char, unsigned char, unsigned 
char)':
C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:160: undefined 
reference to `CEnvelopeGenerator::stopEG()'
src/main.o: In function `noteOn(unsigned char, unsigned char, unsigned 
char)':
C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:168: undefined 
reference to `CEnvelopeGenerator::startEG()'
src/main.o: In function `TIM2_IRQHandler':
C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:307: undefined 
reference to `CEnvelopeGenerator::doEnvelope()'
collect2.exe: error: ld returned 1 exit status

Greetz

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Vincent H. schrieb:
>
> Der Konstruktur wird übrigens sowohl in deinem, als auch in meinem
> Beispiel aufgerufen. Das lässt sich sehr einfach überprüfen, indem du
> innerhalb des Konstrukturs einen Breakpoint setzt. Beim Debuggen wirst
> du dann (vermutlich) merken, *dass der Aufruf statt findet, noch bevor du
> in main()* landest.
>

Richtig, deswegen rate ich bei der Arbeit mit statisch angelegten 
Objekten immer sehr zur Vorsicht, denn zu dem Zeitpunkt ist noch nichts 
richtig initialisiert... wenn der Konstruktor Dinge macht, die ein 
initialisiertes System voraussetzen, sind Probleme vorprogrammiert. Das 
können sehr subtle Dinge sein, die schwierig herauszufinden sind (z.B. 
Hardwareinitialisierungen, die später durch Aufruf von SystemInit() 
übergebügelt werden, oder Aufrufe zu malloc() bei noch nicht 
funktionierendem sbrk() oder Umbiegen der Speicherverwaltung zum 
späteren Zeitpunkt); ausserdem hängt es auch bom Laufzeitsystem ab, wo 
genau die Objekte hingelegt werden (was manchmal mit dem gewollten 
Speicherlayout clashen kann).

Ich mache es lieber so:

SomeClass *c1;
SomeClass *c2;

void SysTick_Handler()
{
  HAL_IncTick();
  HAL_SYSTICK_IRQHandler();
  c1->foo();
  c2->foo();
}

und irgendwo zu einem angemessenen Zeitpunkt (also bevor der 
SysTick_Handler zum ersten Mal aufgerufen wird aber nachdem das System 
hinreichend initialisiert wurde):

c1 = new SomeClass();
c2 = new SomeClass();

von Vincent H. (vinci)


Lesenswert?

Oliver F. schrieb:
> arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -mfloat-abi=hard
> -mfpu=fpv4-sp-d16
> -T"C:\Users\oliverf\workspace\HelloDSP\LinkerScript.ld"
> -Wl,-Map=output.map -Wl,--gc-sections -fno-exceptions -fno-rtti -lm -o
> "HelloDSP.elf" @"objects.list"
> src/main.o: In function `noteOff(unsigned char, unsigned char, unsigned
> char)':
> C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:160: undefined
> reference to `CEnvelopeGenerator::stopEG()'
> src/main.o: In function `noteOn(unsigned char, unsigned char, unsigned
> char)':
> C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:168: undefined
> reference to `CEnvelopeGenerator::startEG()'
> src/main.o: In function `TIM2_IRQHandler':
> C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:307: undefined
> reference to `CEnvelopeGenerator::doEnvelope()'
> collect2.exe: error: ld returned 1 exit status


Sieht immer noch nach "Header Problem" aus. Schieb die Deklaration der 
Interrupts mal in den Header, wo sie auch hingehört.

1
// HPP
2
class  CEnvelopeGenerator
3
{
4
 // impl
5
};
6
7
#ifdef __cplusplus
8
extern "C"
9
{
10
#endif
11
12
void TIM2_IRQHandler();
13
void USART1_IRQHandler();
14
15
#ifdef __cplusplus
16
}
17
#endif


/edit
Ein Header Guard wär auch nicht schlecht. Nachdem du uns die 
Implementierungen der Klasse nicht gezeigt hast geh ich davon aus, dass 
"CEnvelopeGenerator.h" öfter als 1x inkludiert wird...

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Nee: undefined refence -> Implementierungsdatei (*.cpp) fehlt bzw. die 
Objectdatei wird nicht hinzugebunden

von Oliver F. (ollif)


Lesenswert?

Hatte die Klasse jetzt mal direkt in die Main gepackt, ging dann 
fehlerfrei zu kompilieren und der Aufruf der startEG() Methode hat 
geklappt.

Die Timer ISR läuft auch (mit Oszi geprüft).

Wie kann ich denn prüfen ob die Objectdatei hinzugebunden wird bzw. wo 
stelle ich das ein?

Greetz

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Je nachdem, welchen Compiler/welche IDE Du verwendest, gibt es da eine 
Projektverwaltung oder ein Makefile. Damit musst Du Dich beschäftigen.

von Oliver F. (ollif)


Lesenswert?

Hallo,

erstmal Vielen Dank an alle das Problem ist gelöst!

In der Cpp Datei der Klasse CEnvelopeGenerator

waren einige Funktione mit inline gekennzeichnet.
nachdem ich diese Entfernt hatte lies sich das Programm fehlerfrei 
kompilieren.

Greetz

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Oliver F. schrieb:
> In der Cpp Datei der Klasse CEnvelopeGenerator
>
> waren einige Funktione mit inline gekennzeichnet.

Die hätten in die .h-Datei gehört.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Ruediger A. schrieb:
> Ich mache es lieber so:
>
> SomeClass *c1;
> SomeClass *c2;
> ...
> c1 = new SomeClass();
> c2 = new SomeClass();

Juhu, mit Speicherleck und unnötiger Verwendung eines Heaps. Die 
Interrupts einfach erst in main() einschalten würde das von Dir 
konstruierte Problem nachhaltig und effektiv lösen.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Torsten R. schrieb:
> Ruediger A. schrieb:
>> Ich mache es lieber so:
>>
>> SomeClass *c1;
>> SomeClass *c2;
>> ...
>> c1 = new SomeClass();
>> c2 = new SomeClass();
>
> Juhu, mit Speicherleck und unnötiger Verwendung eines Heaps. Die
> Interrupts einfach erst in main() einschalten würde das von Dir
> konstruierte Problem nachhaltig und effektiv lösen.

Hä? Wieso "unnötiger Verwendung eines Heaps"? Meinst Du, dass ein 
statisch alloziiertes Objekt keinen Speicher braucht? Für den Chip ist 
es relativ egal; der Speicher ist eh weg. Der Unterschied ist, dass in 
meiner Implementation die Kontrolle darüber, WO das Objekt liegt, beim 
Entwickler liegt, bei der statischen Alloziierung beim Laufzeitsystem 
(über das man mglw. keine Kontrolle hat).

Wieso "Speicherleck?" Ein statisches Objekt wird niemals dealloziiert, 
das hält seinen Speicher bis zum Reset, also ist es für diesen Zweck 
auch egal, ob das new() durch ein delete() gematcht wird.

Ausserdem: "Mein" Problem ist nicht "konstruiert," sondern durch 20 
Jahre Erfahrung untermauert. Und wenn Du mein Posting vollständig 
gelesen hättest, wüsstest Du auch, dass IRQs nur eine mögliche 
Fehlerquelle sind. Wie würdest Du z.B. das Thema handlen, dass der 
Konstruktor eines Objektes meint, einen UART mit 19200 Baud 
initialisieren zu können? Wenn der Konstruktor VOR der 
Systeminitialisierung aufgerufen wird, wird sehr wahrscheinlich die 
spätere Initialiserung den UART auf Defaultwerte umsetzen. Den Fehler 
darfste lange suchen, viel Spass.

Es geht hier einfach darum, dass der statische Konstruktor unter Anderen 
Bedingungen aufgerufen wird als der dynamische, d.h. das System wird 
sich hier Anders verhalten, und das muss man halt wissen.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Ruediger,

Ruediger A. schrieb:

> Hä? Wieso "unnötiger Verwendung eines Heaps"?

"Unnötig" im Sinn von: Braucht es nicht zur Lösung des Problems und hat 
im Zweifelsfall mehr Nebenwirkungen als erwünscht.

> Meinst Du, dass ein
> statisch alloziiertes Objekt keinen Speicher braucht? Für den Chip ist
> es relativ egal; der Speicher ist eh weg.

Der Speicherbedarf für statische Objekte wird zur Linkzeit ermittelt. 
Wenn der nicht ausreicht, bekommen wir eine Fehlermeldung, bevor der 
Code jemals die Hardware gesehen hat. Bei Deiner Variante, fehlt noch 
irgend eine Form der Prüfung zur Laufzeit (das ist Code, der 
geschrieben, gelesen und gewartet werden muss). Und sein es nur, um mal 
zu gucken, ob der ggf. auftretende Hardfault ausreicht, die Ursache 
recht einfach auf "out of memory" zurück zu führen.

Ein Heap ist ein nicht trivialer Satz an Funktionen, die einen gegebenen 
Speicher dynamisch verwalten. Dieser Satz an Funktionen kostet 
Program-Speicher und für die Verwaltung des heaps braucht es auch 
zusätzlches RAM.

Im Beispiel würde man die Größe des heaps auch nicht einfach nur auf 2 * 
sizeof(SomeClass) dimensionieren, sondern noch etwas Reserve drauf 
geben, um nicht bei jeder Änderung an den Linker-Einstellungen herum 
drehen zu müssen. (und je nach Implementierung des heaps müsste der 
Speicherbereich sogar deutlich größer sein).

Also, ja ich meine ein statisch alloziiertes Objekt braucht weniger 
Arbeits-Speicher. Und wenn die Anwendung vorher keinen heap verwendet 
hat, würde ich für das von Dir skizzierte Problem auch keinen heap 
einsetzen.

Wenn die Anwendung eh einen heap verwendet, dann können solche 
statischen Objekte auf dem heap auch noch zu einer zusätzlichen 
Fragmentierung des heaps führen.

> Der Unterschied ist, dass in
> meiner Implementation die Kontrolle darüber, WO das Objekt liegt, beim
> Entwickler liegt, bei der statischen Alloziierung beim Laufzeitsystem
> (über das man mglw. keine Kontrolle hat).

Es ist eigentlich genau anders herum. Mit new überläßt Du es dem heap, 
zur Laufzeit zu bestimmen, wo genau das Objekt liegt (und damit dem 
Laufzeitsystem). Die Adresse von statischen Objekten legt der Linker 
bereits zur Link-Zeit fest. Letzteres hat auch noch den enormen Vorteil, 
dass Compiler / Linker darauf optimieren können.

Was Du bestimmst, ist der Zeitpunkt der Initialisierung (das kann man 
aber auch anders machen).

> Wieso "Speicherleck?" Ein statisches Objekt wird niemals dealloziiert,
> das hält seinen Speicher bis zum Reset, also ist es für diesen Zweck
> auch egal, ob das new() durch ein delete() gematcht wird.

Ja, da hast Du Recht. Aber: wenn ich den Code lese, dann ist da formal 
ein Speicherleck. Das dies kein Fehler ist, muss ich mir erst erarbeiten 
(std::unique_ptr, könnte helfen).

> Ausserdem: "Mein" Problem ist nicht "konstruiert," sondern durch 20
> Jahre Erfahrung untermauert. Und wenn Du mein Posting vollständig
> gelesen hättest, wüsstest Du auch, dass IRQs nur eine mögliche
> Fehlerquelle sind.

Ich habe deinen Post vollständig gelesen und ja, ich gebe Dir Recht, 
dass Bugs in der Initialisierungsreihenfolge alles andere als trivial 
sind. Ich finde die Nachteile Deiner Lösung für das gegebene Problem 
überwiegen aber bei weitem.

> Wie würdest Du z.B. das Thema handlen, dass der
> Konstruktor eines Objektes meint, einen UART mit 19200 Baud
> initialisieren zu können? Wenn der Konstruktor VOR der
> Systeminitialisierung aufgerufen wird, wird sehr wahrscheinlich die
> spätere Initialiserung den UART auf Defaultwerte umsetzen. Den Fehler
> darfste lange suchen, viel Spass.

Dann lass die UART ein member des Objekts sein. Dann ist die 
Initalisierungsreihenfolge definiert und zusätzlich wird verhindert, 
dass die UART zuerst default initialisiert wird um sie anschließend 
wieder um zu konfigurieren (das zweite Problem ignoriert Deine Lösung).

Generell würde ich es immer vermeiden, Probleme, die bereits zur 
Compile/Link-Zeit erkennbar sind, auf die Laufzeit zu verschieben. Die 
Prüfungen zur Laufzeit müssen zumindest in einem Debug-Build 
implementiert werden und kosten unnötig Code und Zeit und eliminieren 
Optimierungspotential.

mfg Torsten

: Bearbeitet durch User
von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Torsten R. schrieb:
>
>> Wie würdest Du z.B. das Thema handlen, dass der
>> Konstruktor eines Objektes meint, einen UART mit 19200 Baud
>> initialisieren zu können? Wenn der Konstruktor VOR der
>> Systeminitialisierung aufgerufen wird, wird sehr wahrscheinlich die
>> spätere Initialiserung den UART auf Defaultwerte umsetzen. Den Fehler
>> darfste lange suchen, viel Spass.
>
> Dann lass die UART ein member des Objekts sein. Dann ist die
> Initalisierungsreihenfolge definiert und zusätzlich wird verhindert,
> dass die UART zuerst default initialisiert wird um sie anschließend
> wieder um zu konfigurieren (das zweite Problem ignoriert Deine Lösung).
>

Versteh ich ehrlich gesagt nicht wie Du das meinst. Ist aber auch nur 
ein Nebenkriegsschauplatz.

> Generell würde ich es immer vermeiden, Probleme, die bereits zur
> Compile/Link-Zeit erkennbar sind, auf die Laufzeit zu verschieben. Die
> Prüfungen zur Laufzeit müssen zumindest in einem Debug-Build
> implementiert werden und kosten unnötig Code und Zeit und eliminieren
> Optimierungspotential.
>

Gut, dann haben wir relativ schnell (also bevor der Ton unangemessen 
geworden ist ;-)) unsere verschiedenen Positionen als 
Philosophieunterschiede identfiziert. Schön!

Ich kann deine Argumentation nachvollziehen, das ist so ein bisschen wie 
die MISRA Idee: Was immer sich statisch bestimmen lässt, kann dynamisch 
keine Fehler mehr erzeugen. Da ist was dran, und dagegen kann man 
erstmal auch wenig sagen.

Allerdings argumentiere ich in diesem Fall, dass das Verhalten nicht 
intuitiv ist, weil ein Entwickler davon ausgeht, dass sich sein 
Konstruktor immer gleich verhält. Wir sind aber sicherlich einer 
Meinung, dass sich ein Konstruktor notwendigerweise verschieden 
verhalten muss, wenn er in zwei verschiedenen Systemkontexten (System 
initialisiert gegenüber nicht initalisiert) aufgerufen wird. Oder noch 
interessanter: Abhängig davon, was er genau macht, kann sein 
Verhalten in beiden Kontexten identisch sein oder nicht.

Das muss ein Entwickler wissen, um nicht in eine dieser Fallen zu 
tappen, die wochenlange Fehlersuche nach sich ziehen können.

Also es ist ein bisschen wie Pest vs. Cholera. Jede/r Mitlesende darf 
sich daraus sein(e) eigene Art zu sterben auswählen... ;-)

> mfg Torsten

zurück!

von KarlHeinz (Gast)


Lesenswert?

Hallo,

Ich mache so etwas mit dem Singleton Design Pattern [1].

Kurzes Beispiel:
1
class MyInterruptClass
2
{
3
private:
4
5
  int interruptCount_;
6
7
public:
8
  
9
  
10
  MyInterruptClass():interruptCount_(0)
11
  {}
12
  
13
  MyInterruptClass& instance ()
14
  {    
15
    static MyInterruptClass me;
16
    return me;
17
  }
18
19
  void toBeCalledInInterrupt ()
20
  {
21
    interruptCount_++;
22
  }
23
  
24
  int getInterruptCount () const
25
  {
26
    return interruptCount_;
27
  }  
28
}
29
30
31
32
33
extern "C" void TimerISR ()
34
{
35
  MyInterruptClass::instance().toBeCalledInInterrupt();
36
}
37
38
39
40
41
int main (void)
42
{
43
  
44
  // initialize Singleton
45
  // first call to function instance will create a static MyInterruptClass object (call to the Constructor).
46
  // to this before the interrupt is enabled, otherwise the first ISR execution time 
47
  // may be long depending on the complexity of the object and there might be a 
48
  // race conditions if the interrupt and the application call instance the first time
49
  // at the same time
50
  MyInterruptClass::instance();
51
  
52
  initTimerISR();
53
  
54
  while(1)
55
  {
56
    printf("Interrupt count = %d\n",MyInterruptClass::instance().getInterruptCount());
57
  }
58
  
59
}


[1] https://en.wikipedia.org/wiki/Singleton_pattern

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.