Guten Morgen Forum :)
Allgemeiner Teil:
Soweit ich weiss (korrigiert mich wenn ich falsch liege!) garantiert mir
der C-Standard, dass globale Variablen immer mit 0 initialisiert werden,
es sei denn, der Programmierer gibt einen Wert vor. Dann wird die
Variable natuerlich mit diesem Wert initialisiert.
Ist das in C++ auch so, oder sagt der C++-Standard etwas anderes dazu?
Spezieller Teil:
Wo findet die Initialisierung statt? Muss da (in Abhaengigkeit der
Platform?) Code vom Compiler fuer generiert werden? Oder findet das
ganze im Startup-Code statt (welcher im Embeddedbereich ja oftmals von
den Herstellern geliefert wird)?
Ich hoffe Ihr koennt mir helfen :)
Gruesse
Globale Variablen sind generell zu vermeiden. - Gründe leifert dir
google. Falls du Sie unbedingt brauchst, müssen die in der Deklaration
initialisiert werden. C / CPP garantieren KEINE initialisierung. In
einer nicht-initialisierten Variable findet sich dass, was als letztes
an dieser Stelle im RAM stand.
Bei
int variable;
printf(variable)
kommt also etwas mehr oder weniger zufälliges raus. Da ist global oder
nicht völlig Wurst. Wenn in deinem Framework allerdings globale
vorhanden sind, sagt dir die Dokumentation ob die initialisiert sind
ober nicht.
Du kannst das meiste davon nachlesen unter:
http://en.cppreference.com/w/cpp/language/initialization
Auszug daraus:
>>...All non-local variables with static storage duration are initialized as >>part of program startup ...
und:
>>...For all other non-local static and thread-local variables, Zero >>initialization takes place. ...
waflija schrieb:> Globale Variablen sind generell zu vermeiden.
Pauschale Aussagen sind generell zu vermeiden! Sie sind je nach
konkretem Fall beliebig unsinnig/falsch.
Zum Thema: Im Zweifel schadet es nicht Variablen einfach zu
initialisieren. Das erleichtert auch die Lesbarkeit des Codes.
Kaj G. schrieb:> Soweit ich weiss (korrigiert mich wenn ich falsch liege!) garantiert mir> der C-Standard, dass globale Variablen immer mit 0 initialisiert werden,
Korrekt.
C89, 6.5.7 Initialization:
If an object that has automatic storage duration is not initialized
explicitely, its value is indeterminate. If an object that has static
storage duration is not initialized explicitely, it is initialized
implicitely as if every member that has arithmetic type were assigned 0
and every member that has pointer type were assigned a null pointer
constant.
C99, 6.7.8 Initialization:
If an object that has automatic storage duration is not initialized
explicitly, its value is indeterminate. If an object that has static
storage duration is not initialized explicitly, then:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned)
zero;
— if it is an aggregate, every member is initialized (recursively)
according to these rules;
— if it is a union, the first named member is initialized (recursively)
according to these rules.
> Oder findet das> ganze im Startup-Code statt (welcher im Embeddedbereich ja oftmals von> den Herstellern geliefert wird)?
Embedded ist das der Teil, der zwischen dem Resetvektor und main()
liegt, was typischerweise Assembler-Startupcode ist. Dort wird
üblicherweise das RAM genullt. Der Grund hierfür ist, daß explizit mit 0
initialisierte Variablen in der BSS-Sektion landen und nicht in der
DATA-Sektion.
Mit anderen Worten, die Initialisierungswerte von mit 0 explizit
initialisierten globalen Variablen belegen keinen Platz im ROM, wenn man
das so macht.
Außerdem werden im Startupcode auch die Initialwerte von globalen
Variablen aus dem ROM ins RAM geladen, die nicht null sind.
Diesen Startupcode schreibt man sich üblicherweise selber, oft durch
Anpassung einer bestehenden Vorlage.
waflija schrieb:> int variable;> printf(variable)>> kommt also etwas mehr oder weniger zufälliges raus. Da ist global oder> nicht völlig Wurst.
Das ist überhaupt nicht wurst - bei lokalen Variablen stimmt das, aber
bei globalen sieht der C-Standard das anders als Du.
waflija schrieb:> Falls du Sie unbedingt brauchst, müssen die in der Deklaration> initialisiert werden. C / CPP garantieren KEINE initialisierung. In> einer nicht-initialisierten Variable findet sich dass, was als letztes> an dieser Stelle im RAM stand.> Bei>> int variable;> printf(variable)>> kommt also etwas mehr oder weniger zufälliges raus. Da ist global oder> nicht völlig Wurst. Wenn in deinem Framework allerdings globale> vorhanden sind, sagt dir die Dokumentation ob die initialisiert sind> ober nicht.
Sorry, stimmt nur bedingt. Globale Variablen werden entweder in das .bss
Segment abgelegt (uninitialisierte Variablen) oder .data (initialisierte
Variablen). Die ersteren werden im Startupcode in einer for Schleife
explizit ausgenullt, die Anderen in einer anderen for Schleife von einem
gleichgroßen Bereich des Programmspeichers initialisiert. Zumindestens
ist das bei Allen Compilern/Startupcodesequenzen im von gcc etc
abgeleiteten Architekturen so. Will man nichtflüchtigen Speicher haben,
muss man die dort liegenden Variablen explizit in ein Anderes Segment
ablegen, das vom Startupcode nicht berührt wird.
Über den "Wert" von globalen Variablen braucht man an dieser Stelle
nicht zu diskutieren, das sind Stil- und Philosophiefragen.
Edit: oops, nop war schneller...
Der Andere schrieb:> Zum Thema: Im Zweifel schadet es nicht Variablen einfach zu> initialisieren. Das erleichtert auch die Lesbarkeit des Codes.
Richtig. Zumal, wenn man statische Variablen explizit mit 0
initialisiert, erkennt der Compiler das und erzeugt keinen zusätzlichen
Code. Aber man sagt als Programmierer, daß man die Initialisierung nicht
vergessen hat, sondern daß da bewußt 0 hinsoll.
Nochwas, das gilt nicht nur für globale Variablen, sondern auch für
static-Variablen mit Datei- oder Funktionsscope.
waflija schrieb:> C / CPP garantieren KEINE initialisierung. In> einer nicht-initialisierten Variable findet sich dass, was als letztes> an dieser Stelle im RAM stand.
Völliger Blödsinn.
Natürlich garantiert C eine Initialisierung von globalen Variablen.
> int variable;> printf(variable)>> kommt also etwas mehr oder weniger zufälliges raus. Da ist global oder> nicht völlig Wurst. Wenn in deinem Framework allerdings globale> vorhanden sind, sagt dir die Dokumentation ob die initialisiert sind> ober nicht.
Nein, das ist nicht zufällig. Wenn "variable" tatsächlich global ist
wird diese mit 0 vom Startup Code bzw der Segment Initialisierung
initialisiert. Problematisch ist das nur wenn eine lokale Variabe in
einer Funktion benutzt wird weil sie dann auf dem Stack liegt:
int main(void) {
int hugo;
// hugo hat jetzt einen zufälligen Wert
}
Zum Thema Embedded sollte noch ergänzend hinzugefügt werden, daß globale
C++ Objekte (nicht Pointer, sondern Objekte) mit großer Sorgfalt zu
benutzen sind, weil der Startupcode ziemlich sofort nach Reset versucht,
die Konstruktoren aufzufufen, und zu dem Zeitpunkt ist das System noch
nicht komplett initialisiert, dh Konstruktoren, die ein initialisiertes
System voraussetzen, können ordentliches Chaos anrichten.
Ruediger A. schrieb:> Zum Thema Embedded sollte noch ergänzend hinzugefügt werden, daß globale> C++ Objekte (nicht Pointer, sondern Objekte) mit großer Sorgfalt zu> benutzen sind, weil der Startupcode ziemlich sofort nach Reset versucht,> die Konstruktoren aufzufufen, und zu dem Zeitpunkt ist das System noch> nicht komplett initialisiert, dh Konstruktoren die ein initialisiertes> System voraussetzen können ordentliches Chaos anrichten.
Ich finde, Du übertreibst ein wenig! Ja, da kann man etwas falsch
machen, aber das ist jetzt auch nicht komplett unüberblickbar.
Ich habe bis jetzt leider noch keinen vom Hersteller mitgelieferten
Startup Code gesehen, der das für globale Objekte macht. Musste ich bis
jetzt Immer selbst implementieren.
Torsten R. schrieb:> Ich habe bis jetzt leider noch keinen vom Hersteller mitgelieferten> Startup Code gesehen, der das für globale Objekte macht. Musste ich bis> jetzt Immer selbst implementieren.
meinst du das der Konstruktor von globalen Objekte nicht aufgerufen
wird? Das kann ich mir gar nicht vorstellen.
Peter II schrieb:> meinst du das der Konstruktor von globalen Objekte nicht aufgerufen> wird? Das kann ich mir gar nicht vorstellen.
Ja, zumindest der gcc Startup code von Nordic und ST tut es ganz sicher
nicht.
Guest schrieb:> Problematisch ist das nur wenn eine lokale Variabe in> einer Funktion benutzt wird weil sie dann auf dem Stack liegt:
Der C-Standard sagt nicht, daß lokale Variablen auf dem Stack liegen,
denn der kennt gar keinen Stack. Sie können ja praktisch gesehen
durchaus auch in Registern liegen.
Torsten R. schrieb:> Peter II schrieb:>>> meinst du das der Konstruktor von globalen Objekte nicht aufgerufen>> wird? Das kann ich mir gar nicht vorstellen.>> Ja, zumindest der gcc Startup code von Nordic und ST tut es ganz sicher> nicht.
kann man dann überhaupt C++ sinnvoll verwenden? Ich hatte bis jetzt nur
kleine Atmel und da ist mir das nicht aufgefallen, dort haben ich
globale Objekte und im konstruktor steht auch code drin. Irgendwo muss
man sich ja auf ein verhalten verlassen können.
gibt es dann wenigsten eine Fehler oder eine Warnung?
Peter II schrieb:> Torsten R. schrieb:>> Ja, zumindest der gcc Startup code von Nordic und ST tut es ganz sicher>> nicht.>> kann man dann überhaupt C++ sinnvoll verwenden? Ich hatte bis jetzt nur> kleine Atmel und da ist mir das nicht aufgefallen, dort haben ich> globale Objekte und im konstruktor steht auch code drin. Irgendwo muss> man sich ja auf ein verhalten verlassen können.
Man kann das benötigte Verhalten ja relativ einfach nachrüsten
(https://github.com/TorstenRobitzki/bluetoe/blob/master/examples/nrf51/runtime.cpp)
> gibt es dann wenigsten eine Fehler oder eine Warnung?
Die "Der Hersteller Deines Chips kümmert sich einen Sch.. Dre.. um C++,
sei froh, wenn er schon C99 verwendet!"-Warnung? ;-)
Peter II schrieb:> Torsten R. schrieb:>> Peter II schrieb:>>>>> meinst du das der Konstruktor von globalen Objekte nicht aufgerufen>>> wird? Das kann ich mir gar nicht vorstellen.>>>> Ja, zumindest der gcc Startup code von Nordic und ST tut es ganz sicher>> nicht.>> kann man dann überhaupt C++ sinnvoll verwenden? Ich hatte bis jetzt nur> kleine Atmel und da ist mir das nicht aufgefallen, dort haben ich> globale Objekte und im konstruktor steht auch code drin. Irgendwo muss> man sich ja auf ein verhalten verlassen können.
Im Embedded-Bereich gibt's leider recht viele, die in ihrem Startup-Code
nur C bedenken und C++ nicht, weshalb dann solche Sachen fehlen.
> gibt es dann wenigsten eine Fehler oder eine Warnung?
Wo sollte die herkommen?
Peter II schrieb:> meinst du das der Konstruktor von globalen Objekte nicht aufgerufen> wird? Das kann ich mir gar nicht vorstellen.
GCC erstellt je nach bedarf statische Konstruktoren.
avr-g++ erstellt also einen statischen Konstruktor _GLOBAL__sub_I_v
welcher die Initialisierung von i übernimmt und trägt die Adresse des
Konstruktors in die Liste der Funktionen (.ctors) ein, die beim Start-up
auszuführen sind (ähnlich wie attribute((constructor)) in C). Ähnlichen
aber komplizierteren Code erhält man wenn Klassen entsprechendes
benötigen.
Mit einem Host-g++ sieht der Code ganz ähnlich aus
:
Der Eintrag für den Startup-Code (.init_array) sieht natürlich anders
aus, da er zum diesem passen muss, aber im Eneffekt passiert das
gleiche.
Wenn g++ das also nicht macht, dann passt der Startup-Code nicht zum
Compiler bzw. das Backend ist Murx, oder es wurde dem Compiler explizit
abgewöhnt, was doch reichlich fragwürdig erscheint.
...und das gleiche dann nochmal für die Destruktoren (.fini_array,
.dtors, etc)
Nop schrieb:> Guest schrieb:>> Problematisch ist das nur wenn eine lokale Variabe in>> einer Funktion benutzt wird weil sie dann auf dem Stack liegt:>> Der C-Standard sagt nicht, daß lokale Variablen auf dem Stack liegen,> denn der kennt gar keinen Stack. Sie können ja praktisch gesehen> durchaus auch in Registern liegen.
Ja natürlich, aber das ändert nichts an der Tatsache das der
Variablenwert ohne explizite Initialisierung rein zufällig ist.
Torsten R. schrieb:> Ruediger A. schrieb:>> Zum Thema Embedded sollte noch ergänzend hinzugefügt werden, daß globale>> C++ Objekte (nicht Pointer, sondern Objekte) mit großer Sorgfalt zu>> benutzen sind, weil der Startupcode ziemlich sofort nach Reset versucht,>> die Konstruktoren aufzufufen, und zu dem Zeitpunkt ist das System noch>> nicht komplett initialisiert, dh Konstruktoren die ein initialisiertes>> System voraussetzen können ordentliches Chaos anrichten.>> Ich finde, Du übertreibst ein wenig! Ja, da kann man etwas falsch> machen, aber das ist jetzt auch nicht komplett unüberblickbar.>> Ich habe bis jetzt leider noch keinen vom Hersteller mitgelieferten> Startup Code gesehen, der das für globale Objekte macht. Musste ich bis> jetzt Immer selbst implementieren.
naja, vielleicht arbeitest Du mit drastisch anderen Plattformen als ich,
aber bei meinen Kunden taucht das Thema regelmäßig auf. Zwei Beispiele:
1. Controller, bei der die .bss Section im SDRAM liegt. Da der SDRAM
Controller erst initialisiert werden muss, bevor auf den Speicher
zugegriffen werden kann, muss hier der Resetvektor so umgebogen werden,
dass erst eine Minimalinitialisierung durchlaufen wird, bevor der
Startupcode laufen kann (dieser Teil hat natürlich erstmal nichts mit
C++ zu tun, geht aber in eine ähnliche Richtung).
2. Statische C++ UART Objekte. Wenn der Konstruktor hier versucht, auf
Peripherieregister zuzugreifen, bevor der UART initialisiert ist (also
z.B. die GPIOs entsprechend auf primäre Funktionen konfiguriert wurden),
wird vermutlich der folgende Prozessorinitialisierungscode erstmal alles
wieder defaultmäßig überbügeln.
Das hängt natürlich stark davon ab, in welcher Form die
Runtimeinitialisierung genutzt wird. In vielen Plattformen wird der
Resetvektor direkt auf die __entry Funktion des C Runtimecodes gesetzt,
die dann ihre opake Magie macht und letztendlich auf das main()
verzweigt, ab dem die Firmware anfängt zu arbeiten (also z.B. die
Prozessorinitialisierung vornimmt). Bei diesen Plattformen ist der Code
zwischen __startup und main() meistens irgendeine adaptierte Form vom
Standard C/C++ runtime Code, und der MUSS statische Konstruktoren
aufrufen, er hat keine Wahl.
Bei Anderen Ökosystemen sind die .bss und .data Initialisierungen
bereits im vom Hersteller beritgestellten Resetvektorcode enthalten, da
hat man dann mehr Einflussmöglichkeiten (muss aber dann natürlich die
Konstruktoren selber an geeigneter Stelle aufrufen).
Wird ein ctor nicht aufgerufen, an einer Stelle, wo er aufgerufen werden
sollte, so ist das ein nicht-konformer Compiler. Und für class-type
Objekte der Speicherklasse static (e.g.
program-/übersetzungseinheit-globale variable) muss er aufgerufen
werden.
Ruediger A. schrieb:> Torsten R. schrieb:>> 2. Statische C++ UART Objekte. Wenn der Konstruktor hier versucht, auf> Peripherieregister zuzugreifen, bevor der UART initialisiert ist (also> z.B. die GPIOs entsprechend auf primäre Funktionen konfiguriert wurden),> wird vermutlich der folgende Prozessorinitialisierungscode erstmal alles> wieder defaultmäßig überbügeln.
Was modelliert denn Deine UART-Klasse? Eine HW-Ressource? In diesem Fall
sollte man dann wohl auch nicht beliebig viele Exemplare erzeugen
können. Also wäre das ggf. ein Singleton (wohl eher nicht) oder ein
Monostate? Ein Monostate wiederum wird aber wohl die HW nicht
initialisieren...
Ich würde also eine all-static Klasse / Template mit deleted-ctor als
natürlicher empfinden, als ein Monostate o.ä. Die Initialsierung würde
man dann explizit durchführen. Bei vielen HW-Ressourcen geht das ggf.
elegant über fold-expressions.
Unabhängig davon: bei globals kommt aber noch das "static initialization
fiasco" ggf. hinzu...
Wilhelm M. schrieb:> Ich würde also eine all-static Klasse / Template mit deleted-ctor als> natürlicher empfinden, als ein Monostate o.ä. Die Initialsierung würde> man dann explizit durchführen. Bei vielen HW-Ressourcen geht das ggf.> elegant über fold-expressions.>
meine Lösung ist in der Regel, auf statische Konstruktoren komplett zu
verzichten und statt
CSomeObject g_ObjInstance;
so etwas machen wie
CSomeObject *g_ObjInstance;
...
void main(void)
{
<Prozessorinitialisierung>
g_ObjInstance = new CSomeObject(...);
}
Bei Embedded ist halt Kontrolle ein großes Thema. Abstraktionen
übernehmen (fast per Definition) eine Menge Kontrolle (was in Anderen
Umgebungen gut ist), von der man aber bei Embedded eine Teilmenge nicht
abgeben kann.
C++ ist (sinnvoll angewandt) eine enorme Hilfe bei der Entwicklung auch
und gerade für Embedded, aber man muss es halt so anwenden, dass man
nicht zu viel von der Kontrolle ins Transparentland weggibt, die man
braucht, um die spezielle Plattform richtig bearbeiten zu können.
All-static Klassen machen für mich grundsätzlich wenig Sinn, weil sie im
Prinzip die Einkapselung und Separierung von Instanzen aushebeln. Also
nur um damit ein (umschiffbares) Konstrukt wie statische C++ Objekte
irgendwie doch noch hinzukriegen würde ich sie erst recht nicht
anwenden.
Das Beispiel mit dem UART war nur zur Veranschaulichung. Bei Prozessoren
mit bis zu 6 physikalischen UARTs (durchaus nicht unüblich) läßt sich je
nach Konfigurationsvariante pro Firmware eine Anzahl Instanzen > 0 und
<= 6 festnageln. Wenn sich die Zahl während der Laufzeit nicht ändert,
ist auf den ersten Blick ein statisches Anlegen jeder jeweils benötigten
Instanz ein möglich gangbarer Weg (wobei dann aber natürlich die
static-only Implementation wegfällt, weil dann der this pointer
notwendigerweise gebraucht wird). Ich wollte für solche Fälle nur
veranschaulichen, womit man rechnen muss, wenn man tatsächlich auf
statisch initialisierte Objekte nucht verzichten will).
Ruediger A. schrieb:> meine Lösung ist in der Regel, auf statische Konstruktoren komplett zu> verzichten und statt>> CSomeObject g_ObjInstance;>> so etwas machen wie>> CSomeObject *g_ObjInstance;> g_ObjInstance = new CSomeObject(...);
man will keine dynamische Speicherwaltung auf kleinen Systemen haben.
Ich würde es genauso so NICHT machen.
Ruediger A. schrieb:> Wilhelm M. schrieb:>> Ich würde also eine all-static Klasse / Template mit deleted-ctor als>> natürlicher empfinden, als ein Monostate o.ä. Die Initialsierung würde>> man dann explizit durchführen. Bei vielen HW-Ressourcen geht das ggf.>> elegant über fold-expressions.>>>> meine Lösung ist in der Regel, auf statische Konstruktoren komplett zu> verzichten und statt
so war das aber nicht gemeint!
all-static bedeutet, dass ich gar keine Instanz erzeuge!
>> CSomeObject g_ObjInstance;>> so etwas machen wie>> CSomeObject *g_ObjInstance;>> ...> void main(void)> {> <Prozessorinitialisierung>> g_ObjInstance = new CSomeObject(...);> }
Oh, Du hast dyn. Spiecherverwaltung!?! DAnn reden wir wohl nicht mehr
über eine "uC" sondern über einen "C" ohne "u" ;-)
Nein, auf uC dyn. Speicherallokation ist das verschiedenen Gründen einb
nicht so guter Weg.
> Das Beispiel mit dem UART war nur zur Veranschaulichung. Bei Prozessoren> mit bis zu 6 physikalischen UARTs (durchaus nicht unüblich) läßt sich je> nach Konfigurationsvariante pro Firmware eine Anzahl Instanzen > 0 und> <= 6 festnageln.
Am besten m.E. mit einem Template:
Uart<0> ... Uart<5>
> Wenn sich die Zahl während der Laufzeit nicht ändert,> ist auf den ersten Blick ein statisches Anlegen jeder jeweils benötigten> Instanz ein möglich gangbarer Weg (wobei dann aber natürlich die> static-only Implementation wegfällt, weil dann der this pointer> notwendigerweise gebraucht wird).
Den braucht man ja gerade nicht in dem Fall.
Ruediger A. schrieb:> meine Lösung ist in der Regel, auf statische Konstruktoren komplett zu> verzichten...
Da es in Konstruktoren grundsätzlich zu Problemen kommen kann (wenn sie
vor main() aufgerufen werden), ist es eine Alternative sie in Funktionen
zu kapseln (imho auch eine Empfehlung von Scott Meyers). Bspw.:
1
staticObjectmake(){/* code to create object */}
2
3
staticObject&get()
4
{
5
staticautoobject=make();
6
returnobject;
7
}
Ggf. Smart Pointer statt Referenzen.
Vorteil: Gegenseitige Abhängigkeiten (sofern es diese gibt) werden
korrekt aufgelöst. Exceptions des Konstruktors können abgefangen werden.
Neutral: Das Laufzeitverhalten ändert sich da es ein Delay beim ersten
Aufruf gibt.
Nachteil: Der Code wird umfangreicher und nicht gerade besser lesbar.
Wilhelm M. schrieb:>> Oh, Du hast dyn. Spiecherverwaltung!?! DAnn reden wir wohl nicht mehr> über eine "uC" sondern über einen "C" ohne "u" ;-)>> Nein, auf uC dyn. Speicherallokation ist das verschiedenen Gründen einb> nicht so guter Weg.>
uhm, das wird jetzt aber eine komplett Andere Diskussion... verstehe ich
es richtig, daß Du Dich grundsätzlich gegen dynamische
Speicherverwaltung in Embedded aussprichst, oder findest Du nur meine
Codefragment mit dem new() unter einem dynamischen
Speicherverwaltungssystem keine gute Idee? Ist leider etwas unklar...
Mikro 7. schrieb:> Ruediger A. schrieb:>> meine Lösung ist in der Regel, auf statische Konstruktoren komplett zu>> verzichten...>> Da es in Konstruktoren grundsätzlich zu Problemen kommen kann (wenn sie> vor main() aufgerufen werden), ist es eine Alternative sie in Funktionen> zu kapseln (imho auch eine Empfehlung von Scott Meyers).
Ja, das ist absolut üblich und nennt sich lazy / deferred-instantiation.
Es ist so normal, dass es einem manchmal gar nicht mehr einfällt. Vor
allem, wenn man templates einsetzt, erspart es zudem Schreibarbeit ;-)
> Bspw.:>>
1
>staticObjectmake(){/* code to create object */}
2
>
3
>staticObject&get()
4
>{
5
>staticautoobject=make();
6
>returnobject;
7
>}
8
>
> Ggf. Smart Pointer statt Referenzen.
das ergibt aber wieder dyn. alloc.
>> Vorteil: Gegenseitige Abhängigkeiten (sofern es diese gibt) werden> korrekt aufgelöst. Exceptions des Konstruktors können abgefangen werden.
Noch Vorteil: wird die Instanz nicht gebraucht, wird die auch nicht
erzeugt.
>> Neutral: Das Laufzeitverhalten ändert sich da es ein Delay beim ersten> Aufruf gibt.>> Nachteil: Der Code wird umfangreicher und nicht gerade besser lesbar.
Ruediger A. schrieb:> Wilhelm M. schrieb:>>>> Oh, Du hast dyn. Spiecherverwaltung!?! DAnn reden wir wohl nicht mehr>> über eine "uC" sondern über einen "C" ohne "u" ;-)>>>> Nein, auf uC dyn. Speicherallokation ist das verschiedenen Gründen einb>> nicht so guter Weg.>>>> uhm, das wird jetzt aber eine komplett Andere Diskussion...
ja, das hat mit urspr. Frage nach der Initialsierung von storage-class
static Objekten nichts zu tun. Dies ist ja wahl beantwortet.
> verstehe ich> es richtig, daß Du Dich grundsätzlich gegen dynamische> Speicherverwaltung in Embedded aussprichst, oder findest Du nur meine> Codefragment mit dem new() unter einem dynamischen> Speicherverwaltungssystem keine gute Idee? Ist leider etwas unklar...
Grundsätzlich. Es sei denn, man hat eben MBs von RAM. Dann wird man wohl
auch eine Plattform haben, für die die libstdc++ vollständig realisiert
ist.
Meine Vermutung ging in Richtung kleine uCs ohne dyn. alloc.
Zudem ist ja gerade bei der Modellierung reiner HW-Ressourcen gar nicht
dynamisch (s.a. Beispiel USART).
Wilhelm M. schrieb:> das ergibt aber wieder dyn. alloc.
Nein, das Objekt muss nur kopierbar sein (für eine HW Abstraktion meist
nicht denkbar) oder Move-bar sein.
Torsten R. schrieb:> Wilhelm M. schrieb:>>> das ergibt aber wieder dyn. alloc.>> Nein, das Objekt muss nur kopierbar sein (für eine HW Abstraktion meist> nicht denkbar) oder Move-bar sein.
Ja, da hatte ich Dich falsch verstanden: ich dachte an
make_unique<>()/make_shared<>().
Aber wozu in dem o.g. Bsp. SmartPtr?
Wilhelm M. schrieb:>>>> verstehe ich>> es richtig, daß Du Dich grundsätzlich gegen dynamische>> Speicherverwaltung in Embedded aussprichst, oder findest Du nur meine>> Codefragment mit dem new() unter einem dynamischen>> Speicherverwaltungssystem keine gute Idee? Ist leider etwas unklar...>> Grundsätzlich. Es sei denn, man hat eben MBs von RAM. Dann wird man wohl> auch eine Plattform haben, für die die libstdc++ vollständig realisiert> ist.>> Meine Vermutung ging in Richtung kleine uCs ohne dyn. alloc.>
Definiere "klein." Es ist mit der entsprechenden Middleware probemlos
möglich, FW für Controller der Größenordnung 256k RAM und 512k Flash mit
RTOS und dynamischer Speicherverwaltung zu fahren. Ich habe sogar mal
die Combo 128k Flash/32K RAM so zum Laufen bekommen, allerdings mit der
"Sparvariante" FreeRTOS::heap_1.c (also nur malloc(), kein free()).
Klar gibt es viele Anwendungen, in denen sämtlicher Speicherbereich
vollständig deterministisch ist, und dann braucht man keine dyn. SV.
Aber schon wenn so Dinge drin sind wie Kommunikationen mit variabler
Telegrammgröße, hilft es enorm, nicht worst case voralloziieren zu
müssen.
> Zudem ist ja gerade bei der Modellierung reiner HW-Ressourcen gar nicht> dynamisch (s.a. Beispiel USART).
Richtig, aber wenn z.B. konfigurationsabhängig mal 1, mal 2, mal... n
USARTs zu bedienen sind, macht es gerade in kleinen Architekturen einen
großen Unterschied, ob ich z.B. USART Empfangsringbuffer statisch für
alle theoretisch zu bedienenden USARTs präventiv anlege oder nur für die
USARTs, die ich jeweils brauche.
Aber solche Sachen führen nun wirklich recht weit weg und sollten
vermutlich Offline weiterdiskutiert werden...
Wilhelm M. schrieb:>> Ggf. Smart Pointer statt Referenzen.>> Es ging um diese Bemerkung!
Falls man mehr braucht als nur den Konstruktor. Bspw. ein std::fstream
den man erfolgreich mit einem Default geöffnet haben möchte und wo sich
das Kapseln in eine separate Klasse nicht "lohnt".
Ruediger A. schrieb:> Wilhelm M. schrieb:>>>>>>> verstehe ich>>> es richtig, daß Du Dich grundsätzlich gegen dynamische>>> Speicherverwaltung in Embedded aussprichst, oder findest Du nur meine>>> Codefragment mit dem new() unter einem dynamischen>>> Speicherverwaltungssystem keine gute Idee? Ist leider etwas unklar...>>>> Grundsätzlich. Es sei denn, man hat eben MBs von RAM. Dann wird man wohl>> auch eine Plattform haben, für die die libstdc++ vollständig realisiert>> ist.>>>> Meine Vermutung ging in Richtung kleine uCs ohne dyn. alloc.>>>> Definiere "klein."
Steht da: ohne dyn. alloc.
>> Zudem ist ja gerade bei der Modellierung reiner HW-Ressourcen gar nicht>> dynamisch (s.a. Beispiel USART).>> Richtig, aber wenn z.B. konfigurationsabhängig mal 1, mal 2, mal... n> USARTs zu bedienen sind,
Laufzeit? Oder Compilezeit?
> macht es gerade in kleinen Architekturen einen> großen Unterschied, ob ich z.B. USART Empfangsringbuffer statisch für> alle theoretisch zu bedienenden USARTs präventiv anlege oder nur für die> USARTs, die ich jeweils brauche.
Brauche ich doch auch gar nicht. Nur für die, die ich habe und benutze.
Mikro 7. schrieb:> Wilhelm M. schrieb:>>> Ggf. Smart Pointer statt Referenzen.>>>> Es ging um diese Bemerkung!>> Falls man mehr braucht als nur den Konstruktor. Bspw. ein std::fstream> den man erfolgreich mit einem Default geöffnet haben möchte und wo sich> das Kapseln in eine separate Klasse nicht "lohnt".
Wo ist da jetzt der Zusammenhang?
Es ging doch um die Vermeidung dyn. alloc. In dem Zusammenhang habe ich
die Verwendung von SmartPtr nicht verstanden.
Wilhelm M. schrieb:>>>> Richtig, aber wenn z.B. konfigurationsabhängig mal 1, mal 2, mal... n>> USARTs zu bedienen sind,>> Laufzeit? Oder Compilezeit?
Bei dem Kunden zur Laufzeit (in einem Konfigurationsblock im Flash sind
die aktiven Interfaces hinterlegt - in Installation x vielleicht 2, in y
3). Zum Startup wird der Konfigurationsblock ausgelesen und die Anzahl
benötigter Interfaces initialisiert.
>>> macht es gerade in kleinen Architekturen einen>> großen Unterschied, ob ich z.B. USART Empfangsringbuffer statisch für>> alle theoretisch zu bedienenden USARTs präventiv anlege oder nur für die>> USARTs, die ich jeweils brauche.>> Brauche ich doch auch gar nicht. Nur für die, die ich habe und benutze.
Eben. Wenn das erst dynamisch zur Laufzeit bestimmen kann (siehe
Beispiel oben), habe ich das Wissen, wieviel Speicher ich brauche, erst
zur Laufzeit, und dann tue ich mich ohne dynamische SV schwer ;-)
Ruediger A. schrieb:> Eben. Wenn das erst dynamisch zur Laufzeit bestimmen kann (siehe> Beispiel oben), habe ich das Wissen, wieviel Speicher ich brauche, erst> zur Laufzeit, und dann tue ich mich ohne dynamische SV schwer ;-)
Ok, wenn Du Deine Konfiguration dynamisch anpassen musst. Vielleicht
hast Du am Systembus ja auch hot-swappable devices.
Dann sollte man vielleicht den Focus auf spezielle Allokatoren richten.
Johann L. schrieb:> Wenn g++ das also nicht macht, dann passt der Startup-Code nicht zum> Compiler bzw. das Backend ist Murx, oder es wurde dem Compiler explizit> abgewöhnt, was doch reichlich fragwürdig erscheint.
Leider wurschtelt in der ARM-Welt (anders als bei AVR) so ziemlich
jeder mit eigenem Startup-Code und eigenen Linkerscripts herum, die
oft genug beliebig falsch sein können. Du glaubst gar nicht, was
einem da alles übern Weg laufen kann. ;-) Das fängt an bei
„vergessenen“ Sternen in den linker input sections (sodass mit
-ffunction-sections compilierte Objekte bspw. aus der Bibliothek
dann teilweise nicht erfasst werden) und geht bis dahin, dass der
tolle Schreiberling des Linkerscripts den initialen Stackpointer nicht
auf direkt hinter das Ende des RAMs gesetzt hat (ARM macht immer
pre-decrement, und das ist so dokumentiert), sondern „vorsichtshalber“
vier Byte darunter. Das merkt man erst dann, wenn man das erste Mal
printf() mit einem double-Argument benutzt, dann rennt man nämlich
plötzlich in einen alignment fault, weil „double“ auf 64 Bit
ausgerichtet
sein muss, dass aber mit diesem verkorksten Stack nicht mehr garantiert
ist.
ARM hat zwar wohl viel beim Compiler selbst getan, aber so eine
„Rundum-Sorglos-Option“ wie -mmcu= beim AVR haben sie einfach versäumt.
Das hätte aber auch nur ARM selbst in die Hand nehmen können, die
einzelnen Lizenznehmer interessieren sich nicht für die Konsistenz der
Toolchain, die rasseln einfach irgendwas in ihre bevorzugte IDE
hinein, und der Kunde muss dann mit dem Salat leben.
Torsten R. schrieb:> Peter II schrieb:>>> meinst du das der Konstruktor von globalen Objekte nicht aufgerufen>> wird? Das kann ich mir gar nicht vorstellen.>> Ja, zumindest der gcc Startup code von Nordic und ST tut es ganz sicher> nicht.
Der Assembler Startup von ST ruft __libc_init_array auf. Innerhalb jener
finden Konstruktur-Aufrufe statt.
Jeder der behauptet das C/C++ auch nur irgendeine Initialisierung
garantiert, hat noch nie ein bare-metal Projekt auf die Beine gestellt.
Wenn ich lustig bin boot ich einen Cortex M in 2x Zeilen Assembler und
spring dann in main, ohne auch nur einen Finger zu rühren...
Und um nach 50 teils sinnlosen, teils komplett irrelevanten Antworten
auch noch die ursprüngliche Fragestellung zu beantworten:
Kaj G. schrieb:> Spezieller Teil:> Wo findet die Initialisierung statt? Muss da (in Abhaengigkeit der> Platform?) Code vom Compiler fuer generiert werden? Oder findet das> ganze im Startup-Code statt (welcher im Embeddedbereich ja oftmals von> den Herstellern geliefert wird)?
Zur Compilezeit bekannte Initialisierungswerte landen (zusammen mit
Programm-Code und sonstigen Konstanten) in einem als ".text" benannten
Bereich. Damit der Startup-Code diese verschiedenen Bereiche im Flash
wieder findet exportiert das Linkerscript einen Haufen Symbole.
Diese Symbole werden für die jeweiligen Anfangs- und Endadressen der
benötiten Daten erzeugt. Die "Standardbezeichnungen" sind meist
1
_sdata(__data_start__)
2
_edata(__data_end__)
3
4
__preinit_array_start
5
__preinit_array_end
6
7
__init_array_start
8
__init_array_end
9
10
__fini_array_start
11
__fini_array_end
Der Bereich _sdata bis _edata ist bei ein simpler "copy/paste" Block,
sprich dieser Bereich wird am Anfang schlichtweg 1:1 ins RAM kopiert.
__preinit und __init sind Arrays vom Typ void(*)(). Die enthaltenen
Function Pointer rufen Initialisierungs-Code auf, der vom Compiler
erzeugt wird und für Konstrukturen gedacht ist. Über genau jenes Array
zu iterieren ist übrigens die Aufgabe des GCC builtin
"__libc_init_array".
Der Bereich __fini ist das Destruktur Pendant zu __preinit und __init
und wird für µC eher selten gebraucht.
Vincent H. schrieb:> Torsten R. schrieb:>> Jeder der behauptet das C/C++ auch nur irgendeine Initialisierung> garantiert, hat noch nie ein bare-metal Projekt auf die Beine gestellt.
Natürlich garantiert C++ die Initialisierung. Das steht so im Standard!
Punkt!
Wenn Du natürlich an dem Compiler/Linker irgendetwas verbiegst, dann
eben nicht mehr.
Wilhelm M. schrieb:> Vincent H. schrieb:>> Torsten R. schrieb:>>>> Jeder der behauptet das C/C++ auch nur irgendeine Initialisierung>> garantiert, hat noch nie ein bare-metal Projekt auf die Beine gestellt.
Bitte das mit dem Zitieren noch mal üben!!!
Torsten R. schrieb:> Wilhelm M. schrieb:>> Vincent H. schrieb:>>> Torsten R. schrieb:>>>>>> Jeder der behauptet das C/C++ auch nur irgendeine Initialisierung>>> garantiert, hat noch nie ein bare-metal Projekt auf die Beine gestellt.>> Bitte das mit dem Zitieren noch mal üben!!!
Arrg, bitte um Entschuldigung - noch nicht genügend Kaffee ;-)
Jörg W. schrieb:>> ARM hat zwar wohl viel beim Compiler selbst getan, aber so eine> „Rundum-Sorglos-Option“ wie -mmcu= beim AVR haben sie einfach versäumt.> Das hätte aber auch nur ARM selbst in die Hand nehmen können, die> einzelnen Lizenznehmer interessieren sich nicht für die Konsistenz der> Toolchain, die rasseln einfach irgendwas in ihre bevorzugte IDE> hinein, und der Kunde muss dann mit dem Salat leben.
...das kann man leider so nicht stehen lassen - eine "Rundum Sorglos
Option" würde notwendigerweise mit manchen Architekturen nicht
kompatibel sein und deswegen nur eine Teilmenge der technisch möglichen
Hardwaredesigns unterstützen (Beispiel .bss im SDRAM, siehe oben).
Ist das uralte Dilemma zwischen Kontrolle und Abstraktion, das sich wie
ein roter Faden durch die Embedded Welt zieht. In der Vorstellungswelt
Mancher würde auf jedem Embedded System der Welt von der Chipkarte bis
zur LkW Steuerung Android oder kompatibel laufen, und man könnte/dürfte
(von einer Handvoll Gurus abgesehen) nur noch Java programmieren,
bräuchte sich aber dafür um Architekturen/Systeme/Startup/OS etc. keine
Gedanken mehr zu machen (Für mich eine Horrorvision). Auf der Anderen
Seite diejenigen, die am Liebsten Ihre Controller noch selbst designed
würden und selbst dynamischer Speicherverwaltung (von RTOS ganz zu
schweigen) mißtrauisch gegenüberstehen (Für mich eher retro und nicht
zukunfsträchtig, auch wenn ich die Idee charmant finde). Viel von der
Zukunft der Embedded Welt wird davon abhängen, wo auf dem Spektrum sich
ein "Standard" ansiedeln wird.
Kaj G. schrieb:> Soweit ich weiss ... garantiert mir> der C-Standard, dass globale Variablen immer mit 0 initialisiert werden,> es sei denn, der Programmierer gibt einen Wert vor.Wilhelm M. schrieb:> Natürlich garantiert C++ die Initialisierung. Das steht so im Standard!> Punkt!
Soso.
Ihr beiden meint, daß euch ein Standard irgend etwas garantiert, ja?
Mal im Klartext: Ein Standard ist allenfalls ein Haufen Druckerschwärze
auf einem Stück Papier - mehr nicht. Ein Standard kann GARNICHTS
garantieren. Allenfalls eine Empfehlung formulieren.
Es ist auch völig schnurz, ob und was in .bss oder .data oder sonstwo in
einem .elf File hinkommt. Das, was in einen µC gebrutzelt wird, ist rein
binär und kommt für gewöhnlich aus einem Hexfile oder einem schlichten
Binärfile. So. Punkt.
Es gibt ne Menge dummer Eierköpfe, die da meinen, sich auf obengenannte
Zusagen verlassen zu können - weil es ja in einem Standard steht. Chan's
FAT-Filesystem gehört z.B. dazu. Und wenn man sowas unreflektiert
benutzt, dann passiert es einem gelegentlich, daß man damit auf die Nase
fällt. Ich kann eigentlich jedem nur dazu raten, auf einem µC-System
grundsätzlich davon auszugehen, daß jede Variable solange
uninitialisiert ist, bis man sie tatsächlich selbst initialisiert hat.
Sich drauf zu verlassen, daß da ab urbe condita eine Null drin steht,
ist grob fahrlässig.
W.S.
W.S. schrieb:> Ich kann eigentlich jedem nur dazu raten, auf einem µC-System> grundsätzlich davon auszugehen, daß jede Variable solange> uninitialisiert ist, bis man sie tatsächlich selbst initialisiert hat.> Sich drauf zu verlassen, daß da ab urbe condita eine Null drin steht,> ist grob fahrlässig.
du geht aber auch von Standards selbstverständlich aus. Prüfst du ob 1+1
= 2 ist?
du verlässt dich bestimmt auf memset - ist auch nur ein Standard, kann
ja eventuell überall etwas anderes machen.
Wenn man C(++) Programmiert, muss man sich auf den Standard verlassen
können, sonst macht das überhaupt keine sinn damit zu arbeiten.
Peter II schrieb:> Wenn man C(++) Programmiert
Nicht nur wenn man programmiert:
Das ist in jedem technischen Bereich so.
Wenn ich eine M8-Mutter kaufe, dann kann ich mich darauf verlassen, dass
die Mutter auf eine M8-Schraube passt und auch darauf, das der
Schraubenschluessel passt. Da muss ich keine Angst haben, dass die
M8-Mutter ploetzlich nur auf eine M6- oder eine M10-Schraube passt oder
ich mir womoeglich erst noch selber eine passende Schrauben basteln
muss, weil es keine passende gibt.
Genau fuer sowas sind Standards da!
Wenn du dich nicht daran haeltst, ist das dein Problem!
Ist dein System nicht standardkonform, hast du ganz andere Probleme als
eine nicht initialisierte Variable.
Wenn ich mich nicht auf den Standard verlassen kann, wie kann ich mir
dann sicher sein, das i = 0 auch wirklich i auf 0 setzt und nicht auf
42?
W.S. schrieb:> Ich kann eigentlich jedem nur dazu raten, auf einem µC-System> grundsätzlich davon auszugehen, daß jede Variable solange> uninitialisiert ist, bis man sie tatsächlich selbst initialisiert hat.
Das hilft dir überhaupt nichts.
Diese deine „Initialisierung“ ist genauso viel oder wenig wert wie
das Ausnullen der anderen Variablen: wenn der Startup-Code das
Umkopieren aus dem Flash vermasselt oder der Linkerscript es nicht
mit in die zu kopierenden Daten übernommen hat, dann hilft auch ein
(überflüssigen) Hinschreiben von „= 0“ rein gar nichts: die Variable
ist danach nicht mehr und nicht weniger „initialisiert“ als vorher.
Wenn du dich aber drauf verlassen kannst, dass das „= 0“ (oder eben
auch ein „= 42“) tatsächlich eine Wirkung hat, dann kannst du dich
bei C oder C++ (und darum ging's im Thread-Titel) auch drauf verlassen,
dass globale und statische Variable mit 0 vorbelegt sind. Die Eierköpfe
sind aber natürlich immer die anderen …
Ruediger A. schrieb:> eine "Rundum Sorglos Option" würde notwendigerweise mit manchen> Architekturen nicht kompatibel sein und deswegen nur eine Teilmenge der> technisch möglichen Hardwaredesigns unterstützen (Beispiel .bss im> SDRAM, siehe oben).
Sie funktioniert für alle nicht Spezialfälle auf dem AVR
erwiesenermaßen verdammt gut. Für Spezialfälle kann man sich ja
immer noch was eigenes zimmern, aber es ist einfach K*cke, dass man
beim ARM sich immer seinen eigenen Mist zusammenstoppeln muss, und
dabei natürlich beliebig viele Fehler reinbringt (die sich oft genug
auch durch die im Netz verfügbaren Musterexemplare ziehen).
Jörg W. schrieb:> aber es ist einfach K*cke, dass man> beim ARM sich immer seinen eigenen Mist zusammenstoppeln muss, und> dabei natürlich beliebig viele Fehler reinbringt (die sich oft genug> auch durch die im Netz verfügbaren Musterexemplare ziehen).
Das stimmt, hat aber auch mit der weit verbreiteten "ich krame mir das
Nächstbeste von irgendeinem wohlmeinenden Amateur ins Netz gestellte
Codefragment heraus und bin zufrieden, wenn es zu funktionieren scheint"
Mentalität zusammen (die logischerweise sehr eng mit der Java
Baukastenphilosophie zusammenhängt). Leider wird die auch oft von
Ökosystemherstellern zur Kostereduzierung verfolgt, also was sich in
Beispielcode von Herstellern findet, ist nicht notwendigerweise besser
als open source contributions...
Ruediger A. schrieb:> Leider wird die auch oft von Ökosystemherstellern zur Kostereduzierung> verfolgt, also was sich in Beispielcode von Herstellern findet, ist> nicht notwendigerweise besser als open source contributions.
Ja, das ist das, was ich meinte: eine sauber gepflegte Kombination
aus Startup-Code und Linkerscript, die ARM zusammen mit der Toolchain
herausgibt, wäre hier sehr viel sinnvoller gelaufen. avr-libc zeigt,
dass man trotzdem sehr viele Freiheiten einbauen kann für
Modifikationen (Lage von Stack oder Heap, zusätzliche Init-Sections,
Variable, die bei Reset in Ruhe gelassen werden). AVR-GCC / avr-libc
sind nun zwar nicht von Atmel gepflegt worden, aber eben immer so
einigermaßen Hand in Hand parallel entwickelt. Bei ARM hat man
natürlich keinerlei Interesse der Lizenznehmer (= Halbleiterhersteller),
sowas zu tun, aber es hätte im Interesse der ARM Ltd. selbst liegen
können, das zu machen.
Meiner Ansicht nach gehört - im Gegensatz zu einer auf einem OS
aufsetzenden Applikation - der Startupcode untrennbar mit zu einem
embedded Programm.
Deshalb schreibt man den selbst, dann weiß man was er macht und auch,
wem man in den Hintern treten muß, wenn er nicht das macht, was er soll.
Markus F. schrieb:> Meiner Ansicht nach gehört - im Gegensatz zu einer auf einem OS> aufsetzenden Applikation - der Startupcode untrennbar mit zu einem> embedded Programm.>> Deshalb schreibt man den selbst, dann weiß man was er macht und auch,> wem man in den Hintern treten muß, wenn er nicht das macht, was er soll.
für globale Variabel spielt es keine Rolle ob man ein BS hat oder eine
Anwendung.
W.S. schrieb:> Soso.> Ihr beiden meint, daß euch ein Standard irgend etwas garantiert, ja?>> Mal im Klartext: Ein Standard ist allenfalls ein Haufen Druckerschwärze> auf einem Stück Papier - mehr nicht. Ein Standard kann GARNICHTS> garantieren. Allenfalls eine Empfehlung formulieren.
Das bedeutet, dass Du dem gcc, clang, you-name-it Compiler, der das
Label Standard-konform trägt, grundsätzlich nicht traust, und jedesmal
den Assembler-Code kontrollierst. Nein, wahrscheinlich den Binärcode,
weil Du ja nicht sicher sein kannst, dass der Assembler richtig
funktioniert. Nein, wahrscheinlich die Bitmaske auf dem Chip, weil Du ja
nicht wissen kannst, ob der Hersteller das richtig umgesetzt hat. Denn
auch die Hersteller-Doku ist ja nur ein Stück Papier ...
Leute, wo sind wir denn?
Jörg W. schrieb:> Das hilft dir überhaupt nichts.>> Diese deine „Initialisierung“ ist genauso viel oder wenig wert wie> das Ausnullen der anderen Variablen
Schreibe nicht so einen albernen Unfug!
Ich werde jetzt wirklich BÖSE.
Und du solltest dich ob solcher Worte schämen.
Meine Art, mich eben nicht auf Obengenanntes zu verlassen ist eben
mehr wert, denn sie verläßt sich NICHT auf verordnete Vorbelegungen,
weil das von Grund auf unsicher ist. Stichwort: Wiederaufsetzen des
Systems nach Absturz, Exceptions usw.
Jeder normalverständige Programmierer kennt diese Regel, sich nicht auf
Vorbelegungen durch irgendwen zu verlassen. Genau das wird hier
regelmäßig gröblichst mißachtet - weil ja alle so unsäglich schlau sind
- oder sich selbst dafür halten, da ja wie gesagt der Standard sagt...
Palmström, etwas schon an Jahren,
wird an einer Straßenbeuge
und von einem Kraftfahrzeuge
überfahren.
....
Und er kommt zu dem Ergebnis:
»Nur ein Traum war das Erlebnis.
Weil«, so schließt er messerscharf,
»nicht sein kann, was nicht sein darf!«
Eben. Soviel zu Standards.
Peter II schrieb:> du geht aber auch von Standards selbstverständlich aus. Prüfst du ob 1+1> = 2 ist?
Ja, hab ich - mit zwei Fingern. Und - OH WUNDER - es stimmt tatsächlich.
Erwiesenermaßen!
Kaj G. schrieb:> Wenn ich eine M8-Mutter kaufe, dann kann ich mich darauf verlassen, dass> die Mutter auf eine M8-Schraube passt und auch darauf, das der> Schraubenschluessel passt
Nö. erstens ist das ein alberner und unpassender Vergleich und zweitens
hab ich grad neulich bei diversen Aufspannschrauben für Fräsarbeiten
Muttern in SW17, SW18 und SW19 vorgefunden.
Soviel zu passenden Schraubenschlüsseln.
Jetzt könnte ich hingegen so argumentieren, daß man sich - zumindest
hier in Berlin - auch nicht mehr drauf verlassen kann, daß LKW-Fahrer
sich an die Straßenverkehrsregeln halten... Ein Glück für mich
persönlich, daß unsereiner in Spandau war.
Also: Werdet mal nicht albern - so kurz vor Weihnachten. Zum 1.April ist
das ja ok, aber grad jetzt nicht.
W.S.
Von ARM Ltd. gepflegte Beispiel-Linkerscripts und Beispiel Startupcode
finden sich in /usr/share/gcc-arm-embedded/samples und die sehen
verdächtig so aus als ob sie die Grundlage für das waren was man in den
diversen Hersteller-Codebasen findet.
> Sternchen vergessen
Fehler passieren. Hast Du den Bug gemeldet? Wurde er nicht behoben?
> Sorglos-Paket
Die jeweiligen Sorglos-Pakete sind die komplett IDEs der jeweiligen
Hersteller die man sich hier gerne den ganzen Tag lang gegenseitig
wärmstens empfiehlt bzw. nachfragt. Da klickst Du dreimal mit der Maus
und fertig ist das Sorglos-Projekt.
Natürlich muss man sich im klaren sein was das Sorglos-Paket denn alles
enthalten soll: Mit dem Startup-Code allein hast Du noch keinen
Sorglos-Zustand erreicht, bei weitem nicht. Du musst mindestens auch
noch die Clock-Initialisierung mitliefern damit das gleiche Feeling
aufkommt wie damals bei AVR. Und dann musst Du das auch noch so flexibel
implementieren daß man aus allen denkbaren 42 Dutzend Optionen pro
Prozessor(-Variante!) wählen kann um alles vollständig abzudecken.
Es ist schon verständlich daß man hier bei ARM irgendwo eine Grenze
zieht und das Herstellerspezifische an die einzelnen Hersteller
delegiert (oder optional auch an die Anwender). Wenn die Hersteller
nicht zeitnah auf Bug-Reports reagieren (würde eine zentrale Instanz bei
ARM das tun?) dann liegt das erstmal nicht an der Arbeitsteilung an sich
sondern am jeweiligen Verhalten der für die einzelnen Teile
Verantwortlichen.
Es hat sich auch gezeigt daß verteilte Softwareentwicklung wesentlich
weniger schwerfällig ist und zu mehr Wahlmöglichkeiten des Anwenders
führt
Ich sehe also hier nicht wirklich ein Problem. Der Anwender kann (nach
aufsteigendem Skill-Level sortiert):
* Das vorhandene "Sorglos-Paket" (IDE oder SDK) des Herstellers
verwenden
* Fehler im "Sorglos-Paket" an den Hersteller melden
* Eine alternative Projektvorlage verwenden die er bei github findet
* Wenn er einen Fehler findet kann er den beim jeweiligen Autor melden
* Er kann Fehler auch selber beheben und einen Pull-Request schicken
* Er kann selber eine Vorlage schreiben und für sich behalten
* Er kann selber eine schreiben und für andere veröffentlichen
W.S. schrieb:> Soso.> Ihr beiden meint, daß euch ein Standard irgend etwas garantiert, ja?
Er garantiert natürlich nicht, dass kaputte und verkorkste Toolchains
richtig funktionieren. Das Problem ist dann aber die Toolchain. Mit
irgendwelchen Annahmen dazu, was der Compiler/Linker aus meinem Code
macht, muss ich ja starten.
W.S. schrieb:> Jörg W. schrieb:>> Das hilft dir überhaupt nichts.>>>> Diese deine „Initialisierung“ ist genauso viel oder wenig wert wie>> das Ausnullen der anderen Variablen>> Schreibe nicht so einen albernen Unfug!> Ich werde jetzt wirklich BÖSE.> Und du solltest dich ob solcher Worte schämen.
Warum? Er hat recht. Wenn du die Variable explizit mit 0 initialisierst,
ist genau der selbe Teil des Startup-Code dafür verantwortlich, die 0 da
reinzubringen, wie wenn du sie nicht explizit initialisierst. Wenn genau
der Teil des Startup-Code kaputt ist (wie bei zu vielen µC-Toolchains
leider der Fall), so dass die implizite Initialisierung nicht
funktioniert, wird die explizite ganz genauso nicht funktionieren.
> Peter II schrieb:>> du geht aber auch von Standards selbstverständlich aus. Prüfst du ob 1+1>> = 2 ist?>> Ja, hab ich - mit zwei Fingern. Und - OH WUNDER - es stimmt tatsächlich.> Erwiesenermaßen!
Er meinte vermutlich nicht, ob du das mit deinen Fingern nachgezählt
hast, sondern ob du explizit getestet hast, dass dein Compiler das auch
hinbekommt. Ist ja schließlich der selbe Standard, nach dem auch die
Initialisierung geregelt ist. Wenn du dich auf den nicht verlässt,
gibt's quasi gar nichts, das du in C noch sicher hinschreiben kannst.
W.S. schrieb:> Jeder normalverständige Programmierer kennt diese Regel, sich nicht> auf Vorbelegungen durch irgendwen zu verlassen.
Was für ein Unsinn.
Jeder normalverständige Programmierer kennt diese Regel, sich nicht
auf ZUSAMMENGEFRICKELTE TOOLS durch irgendwen zu verlassen.
Johann L. schrieb:> W.S. schrieb:>> Jeder normalverständige Programmierer kennt diese Regel, sich nicht>> auf Vorbelegungen durch irgendwen zu verlassen.>> Was für ein Unsinn.>> Jeder normalverständige Programmierer kennt diese Regel, sich nicht> auf ZUSAMMENGEFRICKELTE TOOLS durch irgendwen zu verlassen.
Jeder normalverständige Programmierer kennt zumindest die Handbücher
seiner Tools.
Meistens sind dort auch Abweichungen vom Standard dokumentiert.
W.S. schrieb:> Meine Art, mich eben nicht auf Obengenanntes zu verlassen ist eben> mehr wert, denn sie verläßt sich NICHT auf verordnete Vorbelegungen
Doch, das tut sie. Weil jedenfalls der GCC nämlich intelligent genug
ist, daß er es bemerkt, wenn Du eine statische Variable mit 0
initialisierst. Die legt er dann nämlich nicht etwa in die Data-Sektion,
sondern in die Bss-Sektion. Das kann man mit dem Mapfile auch leicht
nachweisen. Das spart Platz im ROM, deswegen macht GCC das so.
Mit anderen Worten, GCC verläßt sich darauf, daß die Bss-Sektion so
genullt wird, wie es der C-Standard auch vorgibt.
Abgesehen davon bist Du doch ansonsten auch ein Verfechter der
Lowlevel-Eigenimplementation. Also ist es doch Dein eigener
Assemblercode im Startupfile, der das besorgt, wo liegt denn das Problem
überhaupt?
Nop schrieb:> Weil jedenfalls der GCC nämlich intelligent genug ist, daß er es> bemerkt, wenn Du eine statische Variable mit 0 initialisierst.
Selbst, wenn er es nicht wäre: wenn man sich nicht auf das Füllen
mit 0 verlassen kann, gibt es keinen Grund zu der Annahme, dass man
sich auf das Füllen mit anderen expliziten Initialwerten verlassen
könnte. Beide Code-Teile (im Startupcode) stehen gleich nebeneinander.
Wenn einer davon kaputt ist, dann ist es nicht weniger wahrscheinlich,
dass es der andere auch ist.
Da W. S. aber ohnehin selbst ernannter C-Nichtkenner ist, sei ihm die
Ignoranz nachgesehen. Er sollte dann nur aufhören, über Dinge zu
schreiben, die er gar nicht kennen will.
Ich hatte hier auch schon mal eine Diskussion ob die Kenntnis des
Startupcodes notwediger Teil des Entwickelns ist oder nicht. Nachdem mir
die ganzen IDE-Junkies Blödheit unterstellt haben, hab ich es
aufgegeben.
Seht euch doch mal an was die großen IDE Hersteller aus dem Startupcode
machen oder gemacht haben. Da gibt es einen oder besser ein Template für
den Startup-Code und das Linkerscript das für alle von der IDE
unterstützten Controller irgendwie passen muss. Das eigentliche
Linkerscript erzeugen Generatoren, Besonderheiten muss der Präprozessor
abhandeln. Standards gibt es da keine. Man kann sich jetzt entscheiden,
ob man von den Zusammenhängen nichts verstehen will weil es ja so
kompliziert oder unnötig ist oder ob man sein Projekt doch lieber selbst
im Griff haben will. Bei zweitem würde o.g. Frage gar nicht auftreten.
Oder anders, wenn ich weiß dass in meinem Startup-Code die Variablen
nach C/C++ Standard initialisiert werden, kann ich mir eine
Initialisierung mit 0 sparen. Und ob Konstruktoren vernünftig gerufen
werden weiß ich dann auch. Auch habe ich die Möglichkeit erst die
prozessorinternen Bereiche zu initalisieren, danach externes SDRAM in
Betrieb zu nehmen und dann die Variablen und Objekte in den Segmenten
die den SDRAM zugeordnet sind zu behandeln.
Hin und wieder werden bei cortexen auch mal Interrupt-Handler in den RAM
gelegt um Flashwaitstates zu sparen. Dann ist die Kenntnis des
Linkerscripts unerlässlich. Damit ist die Portierbarkeit des Projektes
von einer IDE zur nächsten meistens Geschichte.
Mann kann sich aber auch gleich eigene Linkerscripte und Startupcode
schreiben der dann nicht mehr von irgendwelchen IDE Einstellungen
abhängig ist. Bei größeren Projekten wo das im Gesamtzeitaufwand für das
Projekt ehr zweitrangig ist, ist das sowieso die bessere Wahl. Und die
die z.B. Software für weisse Ware schreiben und RAM-, FLASH- und andere
Tests zwangsweise in ihren Code einbauen müssen. können sich ohnehin
nicht auf den "Segnungen" moderner IDE's verlassen.
Und wenn man den Code schon nicht selber schreibt, kann man ja mal einen
Breakpoint auf den "ResetHandler" oder "reset_handler" oder
reset-handler" oder auf das was die IDE meint ein guter Name dafür zu
sein, setzen und mal Schrittweise zu verfolgen was da passiert. Schon
allein damit gehört man nicht mehr zu den Unwissenden und sich
Draufverlassenden.
temp schrieb:> Schon> allein damit gehört man nicht mehr zu den Unwissenden und sich> Draufverlassenden.
Dagegen hilft aber - ich schrieb es bereits - lesen.
Ich weiß ja nicht, mit welchen Gammel-IDEs und Gammel-Compilern manche
Leute hier arbeiten (müssen), aber zumindest in meinen bescheidenen
Anwendungsfällen hat ein Blick ins ABI-Manual noch immer geholfen.
Das bleibt ansonsten ja auch nicht aus. Man kann ein Linker-Skript auch
nicht einfach am Compiler vorbeischreiben. Danke einfach mal an
Trampolines.
Jörg W. schrieb:> Selbst, wenn er es nicht wäre: wenn man sich nicht auf das Füllen> mit 0 verlassen kann, gibt es keinen Grund zu der Annahme, dass man> sich auf das Füllen mit anderen expliziten Initialwerten verlassen> könnte. Beide Code-Teile (im Startupcode) stehen gleich nebeneinander.> Wenn einer davon kaputt ist, dann ist es nicht weniger wahrscheinlich,> dass es der andere auch ist.>> Da W. S. aber ohnehin selbst ernannter C-Nichtkenner ist,
Oh, danke für die Blumen, aber du scheinst in aller Bescheidenheit mal
so ausgedrückt ein Brett vor dem Kopfe zu haben.
Deshalb erkläre ich dir das mal im Detail anhand eines UART-Treibers:
Da hat man einen Ringpuffer und zwei zugehörige Indexvariablen, einer
ist der Schreib-Index, der andere der Lese-Index. Soweit klar? gut.
Beim Initialisieren des UART's fällt natürlich auch an, daß man den
Ringpuffer ebenfalls initialisieren sollte. Konkret heißt das, daß
Schreib- und Lese-Index sinnvoll zurückgesetzt werden müssen. Ich
schreibe dort beim Initialisieren des seriellen Kanals dediziert in
beide Indizes Null hinein, so daß beide auf Puffer[0] weisen. Und das
tue ich nicht im Startupcode, sondern im seriellen Treiber. Immer noch
klar? ok.
Jetzt kommst du und faselst von Codeteilen im Startupcode. Nonsense
sowas. Wer sich auf die "Standards" verläßt, würde hier sagen, daß per
Standard ja ohnehin in beiden Indizes bereits eine Null drinstehen müßte
(Palmström!) - aber ICH verlasse mich auf so etwas nicht und jeder
andere verständige C- (odern Nicht-C-) Programmierer tut sowas ebenfalls
nicht.
Hast du das jetzt ENDLICH verstanden?
Nochwas zu den üblichen Startupcodes:
Die sehen zumindest bei ARM, Cortex sich alle verdammt ähnlich: Zumeist
wird dort ziemlich direkt in main gesprungen, manchmal sogar per BX und
nicht per BLX. Dazu kommt, daß die Default-Handler eigentlich immer nur
aus einem B. bestehen. Sehr viel seltener findet man Startups, die
tatsächlich Variablen vorinitialisieren. Kurzum, auf wirklich umfassend
gute Startupcodes darf man sich eben NICHT verlassen, egal wie laut
jetzt alle Anderen "ABER DIE STANDARDS!" schreien.
So, nochwas: Im allgemeinen erwartet man von Moderatoren, daß sie sich
etwas weniger daneben benehmen als die übrigen User, aber darauf ist -
wie man sehen kann - etwa so viel Verlaß wie auf das Ablöschen und
Vorbelegen von Variablen.
In dem Sinne: Frohe Weihnachten! Es ist alles nur halb so schlimm.
W.S.
W.S. schrieb:> So, nochwas: Im allgemeinen erwartet man von Moderatoren, daß sie sich> etwas weniger daneben benehmen als die übrigen User, aber darauf ist -> wie man sehen kann - etwa so viel Verlaß wie auf das Ablöschen und> Vorbelegen von Variablen.
Bedeutet das, daß Jörg, weil er Mitautor einer sich an Standards
haltenden AVRlibc ist, nicht moderieren darf?
Oder muß er, weil er moderiert, das Entwickeln von OSS einstellen?
Und wenn's ums Verständnis geht: wer dem Startupcode nicht zutraut,
.bss-Daten auf 0 zu setzen, der darf dem gleichen Code auch nicht
zutrauen, die im Flash hinterlegten Initialisierungsdaten ins RAM zu
kopieren.
Muß man auch nicht, denn man kann das leicht überprüfen, so man dann die
Sprache der Ziel-HW versteht. Wenn man dann noch seiner Festplatte
zutraut, daß der Startup-Code morgen noch der Selbe ist...
W.S. schrieb:> Sehr viel seltener findet man Startups, die> tatsächlich Variablen vorinitialisieren.
Ich hab noch keinen gesehen der das nicht tut. Zeig doch mal einen!
W.S. schrieb:> So, nochwas: Im allgemeinen erwartet man von Moderatoren, daß sie sich> etwas weniger daneben benehmen als die übrigen User
Dafür sorgst du als einer der "übrigen User" ja schon, indem du mal
wieder (wie üblich) pauschal alle zu Idioten erklärst, die nicht deiner
Meinung sind - garniert mit den leider ebenfalls üblichen Beleidigungen:
W.S. schrieb:> Es gibt ne Menge dummer Eierköpfe, die da meinen, sich auf obengenannte> Zusagen verlassen zu könnenW.S. schrieb:> Schreibe nicht so einen albernen Unfug!W.S. schrieb:> du scheinst in aller Bescheidenheit mal so ausgedrückt ein Brett vor dem> Kopfe zu haben.W.S. schrieb:> Nonsense sowas.
Mit so einem Diskussionsstil bist du der letzte hier, der das Recht
hätte, anderen was von wegen "daneben benehmen" vorzuhalten.
Wir könnten ja bloss so zum Spass und zur Wahrung des Friedens der
kommenden Feiertage mal versuchen, die Sprache und den Implementierung
auseinander zu halten. Denn die Sprachen C und C++ garantieren es, aber
in den jeweiligen Implementierungen davon kann es Löcher geben.
Eine dem Sprachstandard nicht folgende Implementierung darf man als
fehlerhaft bezeichnen. In diesem Fall ist es aber nicht C, das den
Fehler hat, sondern die Implementierung. Wenn man das rückwirkend auf
die Sprache bezieht, dann landet man irgendwann bei der Frage, ob man
sich drauf verlassen kann, dass in if(0) wirklich nichts ausgeführt
wird.
Ergo: Eine Liste, welche Entwicklungsumgebungen in dieser Frage Probleme
haben ist sinnvoller als verklausulierte Fundamentalkritik an der
Sprache. Und ggf. auch welche Chip-Anpassungen darin, denn die
Entwicklungsumgebungen im Embedded-Bereich haben ja spezielle
Anpassungen an die diversen Zielsysteme und die müssen nicht alle gleich
sein, wenn also Cortex M7 ok ist, STR9 aber nicht.
Oder brauchen wir wirklich den dreiunddröllstigen Thread über C/C++ vs
ASM, mit den üblichen Verdächtigen auf beiden Seiten?
Johann L. schrieb:> Jeder normalverständige Programmierer kennt diese Regel, sich nicht> auf ZUSAMMENGEFRICKELTE TOOLS durch irgendwen zu verlassen.
Ausser natürlich, man hat die Tools selber zusammengefrickelt. ;-)
W.S. schrieb:>> Diese deine „Initialisierung“ ist genauso viel oder wenig wert wie>> das Ausnullen der anderen Variablen>> Schreibe nicht so einen albernen Unfug!> Ich werde jetzt wirklich BÖSE.
Im Unterschied zu dir weiss er wovon er schreibt.
A. K. schrieb:> Johann L. schrieb:>> Jeder normalverständige Programmierer kennt diese Regel, sich nicht>> auf ZUSAMMENGEFRICKELTE TOOLS durch irgendwen zu verlassen.>> Ausser natürlich, man hat die Tools selber zusammengefrickelt. ;-)
So siehts aus.
Denn dann hat man sich ja höchstpersönlich selbst vom ordnungsgemäßen
Zustand der 49 Kugeln überzeugt. Und wenns dann immer noch nicht geht
weiß man zumindest wer schuld ist und kann den kurzfristig auf dem
kurzen Dienstweg selbst abwatschen.
W.S. schrieb:> Kurzum, auf wirklich umfassend> gute Startupcodes darf man sich eben NICHT verlassen
Ich wiederhole meine Frage: ansonsten verläßt Du Dich auch nicht auf
irgendwelche HALs, SPLs, IDEs und sonstwas, sondern befürwortest
Lowlevel-Eigenimplementation, weil Du dann weißt, was abläuft.
Wieso nicht beim Startup-Assembler?
Wir können ja auch gerne mal konkret werden:
Der Startupcode, den ich gerade verwende [1], macht es richtig:
1
...
2
3
CopyDataInit:
4
ldr r3, =_sidata
5
ldr r3, [r3, r1]
6
...
7
8
FillZerobss:
9
movs r3, #0
10
str r3, [r2], #4
11
...
Wenn W.S. eine kaputte Implementierung kennt, könnte er die hier gern
mal benennen....
[1]
~/.ac6/SW4STM32/firmwares/STM32Cube_FW_F4_V1.13.1/Drivers/CMSIS/Device/S
T/STM32F4xx/Source/Templates/gcc/startup_stm32f446xx.s
here schrieb:> STM32Cube
Und die springt hinterher auch nach __main in der standard library wo
die statischen Konstruktoren aufgerufen werden und mitnichten direkt in
die main. Das widerlegt auch den anderen der gestern behauptet hat bei
ST wäre dies nicht der Fall.
here schrieb:> Wir können ja auch gerne mal konkret werden:> Der Startupcode, den ich gerade verwende [1], macht es richtig:>
1
> ...
2
>
3
> CopyDataInit:
4
> ldr r3, =_sidata
5
> ldr r3, [r3, r1]
6
> ...
7
>
8
> FillZerobss:
9
> movs r3, #0
10
> str r3, [r2], #4
11
> ...
12
>
>> Wenn W.S. eine kaputte Implementierung kennt, könnte er die hier gern> mal benennen....>> [1]> ~/.ac6/SW4STM32/firmwares/STM32Cube_FW_F4_V1.13.1/Drivers/CMSIS/Device/S
T/STM32F4xx/Source/Templates/gcc/startup_stm32f446xx.s
Ich hab noch kein ST Linkerscript gesehen, dass es erlaubt Variablen im
CCRAM / SRAM2 zu initialisieren. Somit ist dieser Startup-Code auch
nicht "Standard" koform.
Vincent H. schrieb:> Ich hab noch kein ST Linkerscript gesehen, dass es erlaubt Variablen im> CCRAM / SRAM2 zu initialisieren.
Wie kriegst du diese Variablen da rein, dabei im Standard bleibend?
A. K. schrieb:> Vincent H. schrieb:>> Ich hab noch kein ST Linkerscript gesehen, dass es erlaubt Variablen im>> CCRAM / SRAM2 zu initialisieren.>> Wie kriegst du diese Variablen da rein, dabei im Standard bleibend?
Ganz einfach, Linkerscript und Startup-Code müssen angepasst werden.
Vincent H. schrieb:>> Wie kriegst du diese Variablen da rein, dabei im Standard bleibend?>> Ganz einfach, Linkerscript und Startup-Code müssen angepasst werden.
Eben. Wie geliefert verhalten sich beide konform zum Standard. Und wer
eines davon an eigene Wünsche anpasst ist auch dafür verantwortlich, das
andere passend zu modifizieren.
Es ging mir konkret um das hier ->
> Wir können ja auch gerne mal konkret werden:> Der Startupcode, den ich gerade verwende [1], macht es richtig:
"Richtig" heißt in diesem Zusammenhang wohl, dass der Startup-Code mit
dem Standard übereinstimmt. Wenn ich aber eine globale Variable anlegen
kann, die nicht initialisiert wird, dann macht der Code es auch nicht
"richtig".
Vincent H. schrieb:> "Richtig" heißt in diesem Zusammenhang wohl, dass der Startup-Code mit> dem Standard übereinstimmt. Wenn ich aber eine globale Variable anlegen> kann, die nicht initialisiert wird, dann macht der Code es auch nicht> "richtig".
Wir drehen uns im Kreis.
Ich wollte darauf hinaus, dass du vermutlich nicht in der Lage bist,
eine Variable in einem solchen RAM zu platzieren, ohne im Code
Sprachelemente ausserhalb des Standards zu verwenden, oder im
Entwicklungssystem Teile wie ein Linkerscript gegenüber der Vorgabe zu
verändern.
Wenn du aber das Linkerscript veränderst, oder z.B. "__attribute__" in
GCC verwendest, dann bist du für die Folgen verantwortlich.
Linkerschript und Startup-Code gehören unmittelbar zusammen.
Unfachmännischer Umgang damit kann zu unerwünschten Resultaten führen.
Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.
Wichtige Regeln - erst lesen, dann posten!
Groß- und Kleinschreibung verwenden
Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang