Forum: Compiler & IDEs Eigenes new klappt nicht bei Konstruktor mit Speicherallokation


von U. E. (Gast)


Angehängte Dateien:

Lesenswert?

Hi,
auf einem kleinen C++ - Projekt (ich verwende CodeSourcery g++) für den 
STM32F103RE habe ich den Versuch unternommen, einen eigenen globalen 
new-Handler zu schreiben (um nicht die Standard Libs einbinden zu 
müssen).

Bislang klappt das Allokieren ganz gut. Allerdings klappt das Allokieren 
von Speicher im Konstruktor schlicht nicht.
Ich habe das Problem in einem Beispielprogramm rekonstruiert (main.cpp).
Das RAM liegt beim STM32F103RE im Bereich $20000000 bis $2000ffff. Die 
Allokation für myA klappt, für myB liefert er mir einen Zeiger vom Stack 
:-(
-> Screen Shot: main_debug.jpg

Wenn man den Konstruktor von B durchgeht, sieht man, daß das Objekt 
offenbar richtig allokiert wurde (this) und auch für myVector die 
Allokation geklappt hat. Nur beim Rücksprung geht die Info über this 
anscheinend wieder "verloren".
-> Screen Shot: main_debug2.jpg

Die Speicherallokation selbst wird in mini_cpp.cpp vorgenommen.

Aber warum? Kann jemand helfen?
Grüße
schnack

von U. E. (Gast)


Angehängte Dateien:

Lesenswert?

Die Allokation klappt genau dann, wenn sie separat z.B. in einer init() 
Methode erfolgt.
Letztlich ist mir unklar, wie in einem new der Zeiger auf den 
allokierten Speicher zurückgegeben wird, wenn nach der Allokation noch 
der Konstruktor durchlaufen wird, bevor der Rücksprung erfolgt.
Grüße
schnack

von Ralf (Gast)


Lesenswert?

Ich könnte mir vorstellen, dass alles ordentlich funktioniert. 
Vielleicht ist es nur eine Fehlinterprtation?

C. D. schrieb:
> daß das Objekt
> offenbar richtig allokiert wurde (this) und auch für myVector die
> Allokation geklappt hat. Nur beim Rücksprung geht die Info über this
> anscheinend wieder "verloren".
Nach dem Rücksprung aus dem Konstruktor ist es ja nicht mehr 'this', 
dann ist es 'B'. 'this' ist dann was anderes.
???!

von U. E. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Ralf,
ich fürchte, daß es keine Fehlinterpretation ist. Das Programm main.cpp 
ist auch nur die kondensierte Fassung des Problems. Tatsächlich haben 
sich die Probleme im eigentlichen Projekt beim Aufruf virtueller 
Funktionen gezeigt, die überhaupt nicht funktionierten (führten auf dem 
STM32 direkt zu einem 'Hard Fault').
Nach langer Sucherei habe ich dann gemerkt, daß der Aufruf für 
diejenigen Objekte fehlschlägt, bei denen das new() nicht funktioniert 
hat (weil sie im Konstruktor eigenen Speicher vom Heap allokieren) und 
dann dieses Kurzbeispiel zusammengesetzt.

Ich habe inzwischen herausbekommen, wie der Wert für den Zeiger myB zu 
interpretieren ist: es ist die untere, aktuelle Stack-Grenze.
Der Screen-Shot zeigt den Durchlauf durch p_malloc(), aufgerufen in
1
myVector = new char[10];
. Der Wert für stack ist $2000ff94. Und genau diesen Wert hat nach dem 
Rücksprung auch myB.

Wobei: warum das so ist, ist mir ein völliges Rätsel..

Grüße
schnack

von Joe R. (joer)


Lesenswert?

Ist eventuell schon eine Lösung gefunden worden?

static char*  heap_end = 0;
lieber als volatile nutzen?

von U. E. (Gast)


Lesenswert?

Hi,
ich denke 'volatile' kann da nicht helfen (habs aber trotzdem 
ausprobiert- ohne Verbesserung), denn hier gibt es ja keine ISR's oder 
andere Funktionen, die "von der Seite reingrätschen".
Meine "Lösung" besteht in dem workaround von oben, d.h. ich allokiere 
Speicher separat.

Das ist keine wirklich erfreuliche Sache, denn ich kann auf diese Weise 
im eigentlichen Projekt die Allokation durch Vererbung NICHT automatisch 
erledigen lassen -> Ideal wäre es, wenn der Konstruktor der Oberklasse 
automatisch durchläuft und dabei den Speicher allokiert.
So muß ich die Allokation durch die Oberklasse-Methode selbst aufrufen 
(was ich tue) oder in der Unterklasse das Ganze noch einmal 
implementieren.
Grüße
schnack

von Rolf Magnus (Gast)


Lesenswert?

Du könntest es ja mal mit placement-new versuchen.

von U. E. (Gast)


Lesenswert?

Hm, das verstehe ich nicht. Ist das ein anderer workaround?
Mit placement-new übergebe ich die bereits die Speicheradresse - aber 
woher soll die kommen?
1
// woher kommt myBAddress?
2
myB = new (myBAddress) new B;

Zum anderen: die Allokation klappt ja "weitgehend" - nur beim Rücksprung 
aus new() wird die Adresse verbaselt. Ich fände es sauberer, die Ursache 
für das Problem zu verstehen und dann nach einer geeignete Lösung zu 
suchen.

Weiß jemand, was ein C++ Compiler mit der Adresse für den allokierten 
Speicher macht, wenn er vor dem Rücksprung die Konstuktoren durchläuft?
"Eigentlich" sollte er diese Adresse via Stack zurückgeben - wie das bei 
Funktionen üblich ist.

Meine Vermutung ist deshalb, daß irgendetwas mit dem Stack nicht mehr 
stimmt. Denn in p_malloc() wird die Variable stack:
1
  char* stack = (char*) __get_MSP();
angelegt - mit dem Wert des aktuellen Stackzeigers - geliefert von 
__get_MSP(). Diese Variable liegt ja ihrerseits auch auf dem Stack 
(sorry, ist schon verwirrend).
Offenbar wird dieser Variableninhalt als Rückgabewert für new() 
verwendet.

von U. E. (Gast)


Angehängte Dateien:

Lesenswert?

Hm.
Ich habe jetzt die Allokation etwas verändert und die 
Sicherheitsabfrage, ob heap und stack zusammengelaufen sind, 
ausgelassen.
Und prompt klappt es...

Das ist natürlich auch noch keine Lösung, aber immerhin..
Grüße
schnack

von Rolf Magnus (Gast)


Lesenswert?

C. D. schrieb:
> Hm, das verstehe ich nicht. Ist das ein anderer workaround?

Ja. Es ist die Art, die C++ vorsieht, um die Allokation von der 
Initialisierung zu trennen. Du allokierst erst deinen Speicher und 
übergibst den an placement-new. Das ruft dann nur noch die Konstruktoren 
auf.

> Mit placement-new übergebe ich die bereits die Speicheradresse - aber
> woher soll die kommen?

Von deinem p_malloc.

> Zum anderen: die Allokation klappt ja "weitgehend" - nur beim Rücksprung
> aus new() wird die Adresse verbaselt. Ich fände es sauberer, die Ursache
> für das Problem zu verstehen und dann nach einer geeignete Lösung zu
> suchen.

Natürlich, aber das scheint ja nicht so gut zu klappen, da du oben 
deinen Workaround mit der init()-Funktion beschrieben hast. 
placement-new wäre auch ein Workaround, aber einer, der vielleicht nicht 
ganz so unkomfortabel ist.

> Weiß jemand, was ein C++ Compiler mit der Adresse für den allokierten
> Speicher macht, wenn er vor dem Rücksprung die Konstuktoren durchläuft?

Was soll er damit schon machen? Er übergibt ihn als this-Zeiger an den 
Konstruktor.

> "Eigentlich" sollte er diese Adresse via Stack zurückgeben - wie das bei
> Funktionen üblich ist.

Meistens wird das eher über Register gemacht.

> Meine Vermutung ist deshalb, daß irgendetwas mit dem Stack nicht mehr
> stimmt. Denn in p_malloc() wird die Variable stack:
>   char* stack = (char*) __get_MSP();
> angelegt - mit dem Wert des aktuellen Stackzeigers - geliefert von
> __get_MSP().

Bist du eigentlich sicher, daß __get_MSP() korrekt funktioniert? Ich 
kenne mich mit dem Prozessor nicht aus, aber es sieht für mich aus, als 
ob der asm-Teil das Register r0 verändert, ohne das dem Compiler 
mitzuteilen.

> Diese Variable liegt ja ihrerseits auch auf dem Stack (sorry, ist schon 
verwirrend).

Ich würde auch hier erwarten, daß sie in einem Register liegt.

von U. E. (Gast)


Lesenswert?

Rolf Magnus schrieb:
>> Meine Vermutung ist deshalb, daß irgendetwas mit dem Stack nicht mehr
>> stimmt. Denn in p_malloc() wird die Variable stack:
>>   char* stack = (char*) __get_MSP();
>> angelegt - mit dem Wert des aktuellen Stackzeigers - geliefert von
>> __get_MSP().
>
> Bist du eigentlich sicher, daß __get_MSP() korrekt funktioniert? Ich
> kenne mich mit dem Prozessor nicht aus, aber es sieht für mich aus, als
> ob der asm-Teil das Register r0 verändert, ohne das dem Compiler
> mitzuteilen.

Nein, bin ich mir nicht. Ich habe die Funktion  __get_MSP() aus 
core_cm3.c quasi blind übernommen. Wahrscheinlich liegt hier der Hund 
begraben..
Wenn r0 natürlich für den Rückgabewert benutzt wird, ist das nicht so 
pfiffig.
Grüße
schnack

von U. E. (Gast)


Lesenswert?

Rolf Magnus schrieb:
> als
> ob der asm-Teil das Register r0 verändert, ohne das dem Compiler
> mitzuteilen.

Wie kann ich das dem Compiler mitteilen?
Grüße
schnack

von U. E. (Gast)


Lesenswert?

Offenbar durch die Clobber List:
http://www.rn-wissen.de/index.php/Inline-Assembler_in_avr-gcc#Clobbers
Mal schauen..

von U. E. (Gast)


Lesenswert?

Also, nun ist es vollbracht.
@Rolf: danke - Dein Tip hat den richtigen Weg aufgezeigt!

Ich die __get_MSP(void) Funktion ersetzt durch:
1
uint32_t __get_MSP(void)
2
{
3
 register uint32_t result __asm ("r0") = 0;
4
5
__asm volatile ("MRS %0, msp\n"
6
                  "BX  lr     \n"  : "=r" (result) );
7
 return(result);
8
}

aus 
http://cdn.energymicro.com/dl/documentation/doxygen/core__cm3_8c-source.html.
Damit hat es funktioniert.
Zum Vergleich die alte Funktion, zusammen mit der Header-Info der 
core_cm3.c:
1
/**************************************************************************//**
2
 * @file     core_cm3.c
3
 * @brief    CMSIS Cortex-M3 Core Peripheral Access Layer Source File
4
 * @version  V1.30
5
 * @date     30. October 2009
6
7
...
8
9
 * Return the current value of the MSP (main stack pointer)
10
 * Cortex processor register
11
 */
12
uint32_t __get_MSP(void) __attribute__( ( naked ) );
13
uint32_t __get_MSP(void)
14
{
15
  uint32_t result=0;
16
17
  __ASM volatile ("MRS %0, msp\n\t" 
18
                  "MOV r0, %0 \n\t"
19
                  "BX  lr     \n\t"  : "=r" (result) );
20
  return(result);
21
}

Grüße
schnack

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.