Ich hab ein paar Fragen zur Bedeutung von static in C
Mein bisheriges Verständnis von static bezieht sich vorwiegend auf die
Verwendung in lokalen Variablen von Funktionen. Dort ist es einfach so
dass die Variable nur lokal in der Funktion existiert, tatsächlich aber
global angelegt ist ohne Sichtbarkeit in anderen Funktionen oder
Modulen. Die Variable behält also Ihren Wert zwischen den
Funktionsaufrufen.
Soweit ist noch alles klar.
Nun sehe ich immer wieder folgende Deklaration in Funktionen die ich
nicht genau zuordnen kann
1
voidfoo(void)
2
{
3
staticuint8_ttest=0;
4
....
5
}
was bewirkt das genau? test wird doch bei jedem Aufruf auf 0 gestzt
oder? static hätte also keinerlei Wirkung?
Bonusfrage: Was macht static bei einer Funktionsdeklaration.
Ich hab natürlch schon etwas Literatur gewälzt, aber bis jetzt noch
keine zufriedenstellende Erklärung gefunden.
Kann mir jemand auf die Sprünge helfen?
Thomas Z. schrieb:> Nun sehe ich immer wieder folgende Deklaration in Funktionen die ich> nicht genau zuordnen kannvoid foo(void)> {> static uint8_t test = 0;> ....> }> was bewirkt das genau? test wird doch bei jedem Aufruf auf 0 gestzt> oder? static hätte also keinerlei Wirkung?
Die Variable test existiert nur einmal pro Programm (bereits bei Start
von main(), nicht erst ab dem Aufruf von kannvoid()) und wird dabei
einmalig mit 0 initialisiert - nicht bei jedem Aufruf.
In C++ wäre es übrigens praktisch gleich, auch wenn du nicht danach
fragst, sorry.
Da gibt es nur eine kleine Nuance, die man nur bei Objekten einer Klasse
bemerken wird in der Regel:
Die Initialisierung findet beim ersten Aufruf der Funktion statt.
Der Konstruktor eines solchen static-Objekts in einer Funktion wird also
überhaupt nur dann ausgeführt, wenn die Funktion erstmalig aufgerufen
wird - nicht bei Programmstart.
Thomas Z. schrieb:> test wird doch bei jedem Aufruf auf 0 gestzt oder
Nein. Initialisiert wird bei static nur einmal.
Static begrenzt immer nur die Sichtbarkeit (wo man zugreifen kann) auf
die umschließende Klammer { } bzw wenn direkt im C-File, auf das C-File.
Die Variablen (oder Funktionen) sind aber immer dauerhaft, also nicht
auf dem Stack.
Thomas Z. schrieb:> Bonusfrage: Was macht static bei einer Funktionsdeklaration.
Dasselbe wie jedes andere static außerhalb von Funktionen:
Der Name (hier der Name der Funktion) ist nur in diesem Quelltext
sichtbar und nicht über den Linker aus anderen Modulen erreichbar.
Also eine dateilokale Funktion.
ok vielen Dank erst mal. Ich muss aber nochmal nachhaken...
Dann ist es doch egal ob in der Funktion schreibe
static unt8_t test; oder eben static unt8_t test=0; in beiden Fällen
wird das Laufzeitsystem die Variable vor dem SystemStart auf 0 setzen
oder?
Zu static bei Funktionen:
die sind doch sowieso erst mal nur im lokalen File sichtbar und können
in andern Files nicht afgerufen werden. Das ist ja dann nur etwas
syntaktischer Zucker oder?
Thomas Z. schrieb:> Dann ist es doch egal ob in der Funktion schreibe> static unt8_t test; oder eben static unt8_t test=0; in beiden Fällen> wird das Laufzeitsystem die Variable vor dem SystemStart auf 0 setzen> oder?
Ich bin mir gerade nicht sicher. Aber ich glaube, wenn du nicht =0
initialisierst, ist vielleicht der Inhalt nicht garantiert.
Auf jeden Fall ist es guter Stil, hinzuschreiben womit man initialisiert
haben möchte - das drückt aus, was man will.
Thomas Z. schrieb:> Zu static bei Funktionen:> die sind doch sowieso erst mal nur im lokalen File sichtbar und können> in andern Files nicht afgerufen werden. Das ist ja dann nur etwas> syntaktischer Zucker oder?
Nein, ist es nicht.
Wenn du nicht static schreibst, kann in jedem anderen Quelltext des
Programms jemand deine Funktion als extern deklarieren und aufrufen:
1
externintf();
2
...
3
i=f();
Der Linker ruft dann die
1
intf()
2
{
3
...
4
}
auf.
Ohne static heißt also: Symbol exportieren.
Mit static:
1
staticintf()
2
{
3
...
4
}
kannst du in anderen Quelltexten machen was, du willst - du kommst an
die Funktion nicht ran.
Klaus W. schrieb:> kannst du in anderen Quelltexten machen was, du willst - du kommst an> die Funktion nicht ran.
Danke das wars was mir gefehlt hat. So ergibt das auch einen Sinn.
Es gibt einen bei Mikrocontrollern effektiven Unterschied zwischen
expliziter und impliziter Initalisierung, egal ob static oder external
scope. Explizite Initialisierung kostet Platz im Flash von
Microcontrollern, wobei das beim Wert 0 vom Entwicklungssystem abhängen
kann. GCC behandelt explizite Initialisierung mit 0 genau wie implizite,
aber das muss nicht überall so sein.
(prx) A. K. schrieb:> Klaus W. schrieb:>> Aber ich glaube, wenn du nicht =0>> initialisierst, ist vielleicht der Inhalt nicht garantiert.>> Doch, ist er.
Ja, du hast recht - laut K&R wird eine lokale static zu 0 initialisiert,
wenn man nichts anderes schreibt.
(Trotzdem empfehle ich es, explizit zu schreiben)
(prx) A. K. schrieb:> Es gibt einen bei Mikrocontrollern effektiven Unterschied zwischen> expliziter und impliziter Initalisierung, egal ob static oder external> scope. Explizite Initialisierung kostet Platz im Flash von> Microcontrollern, wobei das beim Wert 0 vom Entwicklungssystem abhängen> kann. GCC behandelt explizite Initialisierung mit 0 genau wie implizite,> aber das muss nicht überall so sein.
Genau deshalb hab ich hier noch mal nachgefragt. Ich debugge im Moment
ein Binary wo ich mir einige Konstrukte nicht erklären kann (konnte).
Mit der static Geschichte und einem unwissenden C only Programmierer
ergeben die InitTables die ich im Binary sehe auf einmal Sinn.
Falls es interessiert, es geht um ein Keil c51 binary.
Thomas Z. schrieb:> Danke das wars was mir gefehlt hat. So ergibt das auch einen Sinn.
Mit static kommt nicht nur niemand dran: es erlaubt in mehreren Files
Funktionen oder Variablen gleichen Namens zu haben:
Du kannst in jedem C-File ein static int initialized; haben oder ein
static void f(void);
Das ist hilfreich wenn Module gleichartig aufgebaut sind.
A. S. schrieb:> Mit static kommt nicht nur niemand dran: es erlaubt in mehreren Files> Funktionen oder Variablen gleichen Namens zu haben:>> Du kannst in jedem C-File ein static int initialized; haben oder ein> static void f(void);
danke das würde ich persönlich aber nicht machen...
Das ha ja schon was von c++ :-)
Eine **static** deklarierte Variable wird nicht am Stack abgelegt.
Der Wert bleibt nach dem Verlassen der Funktion, im Gegensatz zur
lokalen Variablen ohne **static** , erhalten.
Eine Variable gleicher Deklaration mit gleichem Namen in einer anderen
Funktion ist eine neue Variable
Dieses Verhalten zeigt das beiliegende Programm:
Verhalten der lokalen static Variable **a** im Vergleich zur lokalen
Variablen **b** beim rekursiven Aufruf der Funktion mit der static
Variablendeklaration:
1
pi@test:~/test $ ./a.out 56789
2
*&a,*&b in abc *135220= 56789, *2127717716= 56789
3
*&a,*&b in abc *135220= 5678, *2127717668= 5678
4
*&a,*&b in abc *135220= 567, *2127717620= 567
5
*&a,*&b in abc *135220= 56, *2127717572= 56
6
*&a,*&b in abc *135220= 5, *2127717524= 5
7
8
a=5 b=56789
9
pi@test:~/test $
**Unterschied** :
- die lokale static Variable a ist während der Rekursion immer die
gleiche Variable, am Ende gilt der Wert der tiefsten Rekusion
- die normale locale Variable b wird bei jedem erneuten rekursiven
Aufruf neu am Stack angelegt, am Ende gilt der Wert des ersten Aufrufes
Im Anhang das Programm.
Thomas Z. schrieb:> danke das würde ich persönlich aber nicht machen...> Das ha ja schon was von c++ :-)
?? Kapselung ist bei C genauso wichtig wie bei C++. Lokale Funktionen,
die immer das gleiche zu tun, unterschiedlich zu benennen, wäre so, als
würde man den Schleifenzähler i in verschiedenen Blöcken unterschiedlich
benennen.
(prx) A. K. schrieb:> Explizite Initialisierung kostet Platz im Flash von Microcontrollern,> wobei das beim Wert 0 vom Entwicklungssystem abhängen kann.
Ich würde sagen: kann Platz kosten. Bei GCC (wie du schon festgestellt
hast) war das letztmalig in Version 2.95 so, seither behandelt er das
gleich. Übrigens unabhängig von Mikrocontroller oder nicht, auch "ganz
normale" PC-Programme waren früher davon betroffen und haben
entsprechend Platz in .data (und damit in der ELF-Datei) reserviert.
Gerald K. schrieb:> Eine Variable gleicher Deklaration mit gleichem Namen in einer anderen> Funktion ist eine neue Variable
oder anderer Block. Egal ob parallel, nacheinander (üblich bzw. normal)
oder verschachtelt (legal, aber fehlerträchtig).
Jörg W. schrieb:> C-Programme waren früher davon betroffen und haben> entsprechend Platz in .data (und damit in der ELF-Datei) reserviert.
Natürlich. Nur spielt das auf Disk eine wesentlich geringere Rolle als
im mitunter knappen Flash.
Thomas Z. schrieb:> Bonusfrage: Was macht static bei einer Funktionsdeklaration.
Die Funktion ist im nur in c-Modul sichtbar in dem sie definiert wurde.
Selbst eine Prototypdeklaration in einen anderen c-Modul veranlasst den
Linker nicht die static Funktion zu verwenden.
Beitrag "Re: Frage zu static bei C nicht C++"Gerald K. schrieb:> Unterschied :>> die lokale static Variable a ist während der Rekursion immer die gleiche> Variable, am Ende gilt der Wert der tiefsten Rekusion> die normale locale Variable b wird bei jedem erneuten rekursiven Aufruf> neu am Stack angelegt, am Ende gilt der Wert des ersten Aufrufes>> Im Anhang das Programm.
Die gleichzeitige Verwendung einer Funktion im Backgrund und in einer
Interruptroutine bzw. zwei Threads mit einer static Variable führt zu
einer Wechselwirkung dieser Variablen. Bei reinen lokalen Variablen
besteht diese Problem nicht, da jede Variable in einem eigenen
Stackbereich sitzt.
Klaus W. schrieb:> (prx) A. K. schrieb:>> Klaus W. schrieb:>>> Aber ich glaube, wenn du nicht =0>>> initialisierst, ist vielleicht der Inhalt nicht garantiert.>> Doch, ist er.> Ja, du hast recht - laut K&R wird eine lokale static zu 0 initialisiert,> wenn man nichts anderes schreibt.> (Trotzdem empfehle ich es, explizit zu schreiben)
Im VS wird platterdings gemeckert, wenn man nicht explizit initialisiert
('local variable is not initialized' trotz 'static'). Der Meldung nach
gehe ich davon aus, dass der Fallback auf Null nicht mehr gilt. Ob das
seit ISO C++17 verlangt wird - k. A. Früher war halt alles einfacher
(und mehr Lametta).
Klaus W. schrieb:> (Trotzdem empfehle ich es, explizit zu schreiben)
Mit welcher Begründung?
Wenn du der implizierten Initialisierung nicht trauen kannst, warum
solltest du der expliziten dann trauen können?
dfIas schrieb:> Der Meldung nach gehe ich davon aus, dass der Fallback auf Null nicht> mehr gilt.
Ich gehe der Meldung nach davon aus, dass der Compiler einen an der
Waffel hat.
Warnungen werden durch den C-Standard nicht geregelt, aber die implizite
Initialisierung statischer Variablen auf 0 gibt es zumindest seit dem
ersten (pre-ANSI) K&R-Buch.
Jörg W. schrieb:> Wenn du der implizierten Initialisierung nicht trauen kannst, warum> solltest du der expliziten dann trauen können?
Wegen der Lesbarkeit.
Schreibt man die Initialisierung explizit hin, sieht man klar ohne
Nachdenken, daß die Variable einen definierten Startwert hat und daß der
Schöpfer (m/w/d) das so will.
Dabei geht es nicht darum, ob die implizite Initialisierung
funktioniert.
Klaus W. schrieb:> Schreibt man die Initialisierung explizit hin, sieht man klar ohne> Nachdenken, daß die Variable einen definierten Startwert hat und daß der> Schöpfer (m/w/d) das so will.
Gut.
Ich bin allerdings ein Freund davon, Variablen nur dann zu
initialisieren, wenn es unbedingt nötig ist, weil das (im nicht-"static"
Fall) potenzielle Warnungen verhindert, die einen auf Fehler hinweisen
können ("may be used uninitialized" obwohl man eigentlich der Meinung
war, dass man überall passend Werte gesetzt hat).
Klaus W. schrieb:> (Trotzdem empfehle ich es, explizit zu schreiben)
Sehe ich auch so. So ist die Intention klarer. Und nicht jeder, der den
Code wartet, weiß direkt aus dem Stegreif, dass static Variablen
implizit zu 0 initialisiert werden.
Viele Coding-Standards schreiben schließlich auch die explizite
Initialisierung von static Variablen vor. Und viele Tools für statische
Code-Analyse bieten entsprechende Überprüfungen.
Zusammenfassung zum Keyword static:
- Static Variablen innerhalb einer Funktion behalten ihren Wert während
mehrmaliger Aufrufe der Funktion.
- Static Variablen außerhalb einer Funktion sind nur innerhalb ihres
Moduls sichtbar.
- Static Variablen werden nur einmalig initialisiert (entweder explizit
oder implizit zu 0).
- Static deklarierte Funktionen sind nur innerhalb ihres Moduls
sichtbar.
In Funktionen gibt es zwei Arten von Variablen, nämlich static (für
"static storage duration") und auto (für "automatic storage duration").
Wenn man keins der beiden Schlüsselwörter angibt, wird implizit auto
angenommen.
static storage duration bedeutet, dass die Variable über die gesamte
Programmlaufzeit existiert.
automatic storage duration bedeutet, dass die Variable jedes mal beim
Erreichen ihrer Definiton neu erzeugt und beim Verlassen des Blocks, in
dem sie definiert ist, automatisch wieder zerstört wird.
P. S. schrieb:> Sehe ich auch so. So ist die Intention klarer
Wenn man ein **static** Variable zwischen den Funktionsaufrufen
überleben lassen will,dann Initalisierung der Variablen
kontraproduktiv. Die Variable wird bei jedem Aufrauf neu initalisiert.
**Testprogramm** :
Das Segment der globalen Variablen wird vor Aufruf des Programmes mit
**0en** gefüllt.
Die lokale **static** Variable befindet sich im gleichen Segment (beim
Testprogramm liegt b unmittalbar nach a), als darf man davon ausgehen,
dass sie auch vorinitalisiert ist.
Dennis S. schrieb:> dfIas schrieb:>> Ob das>> seit ISO C++17 verlangt wird - k. A.>> Es gibt keinen C-Standard der in irgendeiner Weise C++17 genannt wird.
Es gibt allerdings einen, der C17 genannt wird. Mich würde es aber sehr
überraschen, wenn dort eine solche Warnung gefordert würde (und das gilt
für C und für C++).
Gerald K. schrieb:> P. S. schrieb:>> Sehe ich auch so. So ist die Intention klarer>> Wenn man ein **static** Variable zwischen den Funktionsaufrufen> überleben lassen will,dann Initalisierung der Variablen> kontraproduktiv. Die Variable wird bei jedem Aufrauf neu initalisiert!
Quatsch.
Gerald K. schrieb:> Das Segment der globalen Variablen wird vor Aufruf des Programmes mit> **0en** gefüllt.
Nicht "das Segment der globalen Variablen", sondern das .BSS-Segment.
Das ist das Segement für alle Variablen mit "static storage duration",
die (implizit oder explizit) mit 0 initialisiert wurden.
> Die lokalen **static** Variablen befinden sich im gleichen Segment,
Sofern sie ebenfalls (implizit oder explizit) mit 0 initialisiert
wurden.
> als darf man davon ausgehen, dass sie auch vorinitalisiert sind.
Allein auf Basis der Adresse kann man nicht davon ausgehen. Besser ist
es, als Basis die entsprechende Regel des C-Standards zu nehmen, die das
so vorschreibt.
Wenn der Code "static int a=0;" nach dem Funktionsheader steht, darf
nicht davon ausgegangen werden, dass dieser dort jedesmal aufgerufen
wird.
Wieder einmal was dazu gelernt. Danke!
Gerald K. schrieb:> printf("static *&a %d=%d, gobal *&b %d=%d\r\n",&a,a,&b,b);
Der Formatspecifier für Pointer ist %p.
Das weiß auch der Compiler und warnt dann - wenn man ihn läßt.
Schalt mal die Warnungen beim Compiler auf Maximum.
Klaus W. schrieb:> Schreibt man die Initialisierung explizit hin, sieht man klar ohne> Nachdenken, daß die Variable einen definierten Startwert hat und daß der> Schöpfer (m/w/d) das so will.
Dann solltest Du es zumindest auf die Fälle beschränken, wo die 0 auch
genau so gewollt ist und nicht einfach nur ein Default. Also nicht in
Code, der beim ersten Aufruf diese variable mit einem zur compilezeit
unbekannten Wert überschreibt.
P. S. schrieb:> Und nicht jeder, der den Code wartet, weiß direkt aus dem Stegreif,> dass static Variablen implizit zu 0 initialisiert werden
Mit dem Argument ist aber auch kein *p++ oder !a && b möglich.
Thomas R. schrieb:> Hier wunderbar erklärt:
Bei Aussagen wie
> Aber Achtung: Statische Variablen müssen schon bei ihrer Deklaration
initialisiert werden!
Ist das ganze aber eher ... unzuverlässig. Im Ernst: wie soll ich da
erkennen, was C ist und was der Autor meint.
Dirk B. schrieb:> Gerald K. schrieb:>> printf("static *&a %d=%d, gobal *&b %d=%d\r\n",&a,a,&b,b);>> Der Formatspecifier für Pointer ist %p.
Allerdings für Pointer auf void. Man muss also (void*)&a draus machen,
denn:
> Das weiß auch der Compiler und warnt dann - wenn man ihn läßt.
Rolf M. schrieb:> Allerdings für Pointer auf void. Man muss also (void*)&a draus machen,
nein, muß man nicht.
Jede Adresse lässt sich ohne Gemecker in eine void* konvertieren.
Und mit printf kann man ohne cast jede Adresse mit %p ausgeben.
Gerald K. schrieb im Beitrag #6728454:
> Dirk B. schrieb:>> Der Formatspecifier für Pointer ist %p.>> Das weiß auch der Compiler und warnt dann - wenn man ihn läßt.>> Als Text im printf() immer zulässig. Warum nicht?
Weil Pointer und int nicht unbedingt die selbe Größe haben müssen. Auf
einem üblichen 64-Bit-System ist z.B. ein Pointer doppelt so groß wie
ein int. Das heißt je nach System, dass im schlimmsten Fall hier auf
Speicher zugegriffen wird, der dafür nicht vorgesehen ist, im besten
Fall wird dir nur die Hälfte des Zeigerwerts angezeigt. Außerdem können
Pointer je nach System ein eigenes Darstellungsformat haben.
> Ich habe immer alle Warnungen eingeschaltet.
Dann solltest du da eigentlich auch eine bekommen.
Klaus W. schrieb:> Rolf M. schrieb:>> Allerdings für Pointer auf void. Man muss also (void*)&a draus machen,>> nein, muß man nicht.>> Jede Adresse lässt sich ohne Gemecker in eine void* konvertieren.
Ja, aber bei variadischen Funktionen muss man das explizit tun, weil das
dort implizit nicht möglich ist.
> Und mit printf kann man ohne cast jede Adresse mit %p ausgeben.
Das muss nicht gezwungenermaßen so sein. Pointertypen dürfen durchaus
unterschiedlich groß sein. Es ist nur garantiert, dass ein void* jeden
Wert einees Objektzeigers aufnehmen kann.
Mein gcc warnt übrigens auch:
1
#include<stdio.h>
2
3
intmain()
4
{
5
inta=3;
6
int*b=&a;
7
printf("%p\n",b);
8
}
Ausgabe von gcc printpointer.c -pedantic:
1
printpointer.c: In function ‘main’:
2
printpointer.c:7:14: warning: format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘int *’ [-Wformat=]
Klaus W. schrieb:> Rolf M. schrieb:>> Allerdings für Pointer auf void. Man muss also (void*)&a draus machen,>> nein, muß man nicht.>> Jede Adresse lässt sich ohne Gemecker in eine void* konvertieren.> Und mit printf kann man ohne cast jede Adresse mit %p ausgeben.
Nur dass der Compiler bei einer Funktion mit variabler Argumentliste wie
printf keine Ahnung hat, dass er den Pointer konvertieren soll.
Verschiedene Arten von Pointern können unterschiedliche interne
Repräsentationen haben, auch bei der Länge.
Funktionen wie printf sind insoweit besonders, als der Compiler bei
ihnen den Formatstring mit den Parametertypen abgleicht. Das ist aber
ein Luxus von gcc, kein Sprachbestandteil.
Rolf M. schrieb:> ...>> Und mit printf kann man ohne cast jede Adresse mit %p ausgeben.>> Das muss nicht gezwungenermaßen so sein. Pointertypen dürfen durchaus> unterschiedlich groß sein. Es ist nur garantiert, dass ein void* jeden> Wert einees Objektzeigers aufnehmen kann.> Mein gcc warnt übrigens auch:#include <stdio.h>> ...
ja, ok.
(Aber doch schon sehr pedantisch...
Ein System, bei dem Adressen unterschiedlich lang sind, hatte ich noch
nicht in der Mache, und werde sowas auch nie anfassen hoffe ich.)
Klaus W. schrieb:> Ein System, bei dem Adressen unterschiedlich lang sind, hatte ich noch> nicht in der Mache, und werde sowas auch nie anfassen hoffe ich.
Dann hast du noch nie mit AVRs gearbeitet, wirst sie auch nie anfassen:
Rolf M. schrieb:> Dann solltest du da eigentlich auch eine bekommen.
Danke, bekomme ich.
Beispiel:
- erstes printf() mit %x
- zweites printf() mit %p
Beim Raspberry Pi haben int und Zeiger die gleiche Größe.
1
static *&a 0x2102c=4711
2
pi@test:~/test $ gcc -Wall t1.c
3
t1.c: In function ‘test’:
4
t1.c:13:22: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int *’ [-Wformat=]
P. S. schrieb:> Viele Coding-Standards schreiben schließlich auch die explizite> Initialisierung von static Variablen vor.
Da würde ich allerdings die Kompetenz der Ersteller dieser Standards
anzweifeln. Wie ja oben schon geschrieben wurde, bringt eine explizite
Initialisierung im besten Falle rein gar nichts *), im schlechteren
Falle sogar eine Pessimierung (mehr Code-Verbrauch).
*) Wenn die Initialisierung des .bss mit 0 kaputt ist, dann hat man
sowieso ganz andere Probleme. Allerdings hatte ich den Fall in der
Praxis tatsächlich noch nicht, den Fall, dass .data nicht sauber mit
Werten vorgeladen worden ist, hatte ich dagegen schon mal (Diskrepanz
zwischen Startup-Code und Linkersript in einer MCU-Implementierung).
Mit Coding-Standards, die eine zwingende Initialisierung von
auto-Variablen fordern, gehe ich gerade noch so mit, wenngleich (wie
oben schon geschrieben) ich auch diese aufgrund meiner Erfahrung für
teils kontraproduktiv halte, da sie Warnungen kaschieren kann, die einen
auf Fehler im Code hinweisen. Die entsprechenden Warnungen beherrschen
die Compiler mittlerweile nämlich genauso gut (manchmal geringfügig zu
aggressiv), wie sie zuverlässig ein
Jörg W. schrieb:> P. S. schrieb:>> Viele Coding-Standards schreiben schließlich auch die explizite>> Initialisierung von static Variablen vor.>> Da würde ich allerdings die Kompetenz der Ersteller dieser Standards> anzweifeln. Wie ja oben schon geschrieben wurde, bringt eine explizite> Initialisierung im besten Falle rein gar nichts *), im schlechteren> Falle sogar eine Pessimierung (mehr Code-Verbrauch).
Es gibt/gab halt anscheinend doch Compiler bzw. linker-Startscripsts,
die vom C-Standard noch nie was gehört haben. Und einmal eingeführt,
halten sich solche Regeln dann unendlich lange.
Oliver
P. S. schrieb:> Viele Coding-Standards schreiben schließlich auch die explizite> Initialisierung von static Variablen vor.
Viele Coding-Standards dienen nur dazu, den Code von Leuten Prüfen zu
lassen, die die Sprache nicht kennen.
Bei C muss man nicht viel wissen, aber das ist dann aber auch wichtig.
Dirk B. schrieb:> Viele Coding-Standards dienen nur dazu, den Code von Leuten Prüfen zu> lassen, die die Sprache nicht kennen.
Vor allem erhöht die unnötige Initialisierung die Anzahl der
(erfolgreichen) Tests und damit die Qualität. Wenn dann noch etwas
schief geht (z.B. weil jemand das static vergessen hat und der Compiler
nicht warnen konnte), lag es nicht an mangelnder Sorgfalt.
Sebastian schrieb:>> Allerdings hatte ich den Fall in der Praxis tatsächlich noch nicht,>> AVR Bootloader?
Auch da spricht nichts dagegen, dass man .bss ausnullt. Ist ja allemal
noch einfacher, als .data zu kopieren.
Am Ende ist ein Bootloader aber ohnehin ein "ganz normales Programm",
das lediglich auf eine höhere Adresse gelinkt wird und sein Ende durch
einen Sprung auf Adresse 0 findet. Wenn man den Bootloader in C
schreibt, gibt es folglich erstmal keinen Grund, auf den regulären
C-Startup-Code zu verzichten.
Aber wie geschrieben, den Fall, dass das Kopieren von .data vermurkst
worden ist, hatten wir tatsächlich schon.
Snapshots vom feinweichen Studio beiliegend.
Die Meldung vom as-you-type analyzer (die Glühbirne "Helferlein" ganz
links) gibt's dann und wann auch vom Compiler selbst. Nicht immer, aber
immer öfter.
Und laut Feinweich gibt es sowohl einen ISO C++14 Standard, als auch
einen ISO C++17 Standard. So nennen die es halt.
dfIas schrieb:> Die Meldung vom as-you-type analyzer (die Glühbirne "Helferlein" ganz> links) gibt's dann und wann auch vom Compiler selbst.
Compiler sind natürlich frei darin, was sie alles warnen, aber diese
Warnung hat keinen Sinn – egal, ob sie nun von der IDE oder dem Compiler
kommt.
Jörg W. schrieb:> aber diese Warnung hat keinen Sinn – egal, ob sie nun von der IDE oder> dem Compiler kommt.
Vermutlich ging es um was ganz anderes bei
dfIas schrieb:> Und laut Feinweich gibt es sowohl einen ISO C++14 Standard, als auch> einen ISO C++17 Standard.
Er hat den Post oben missverstanden:
Dennis S. schrieb:> Es gibt keinen C-Standard der in irgendeiner Weise C++17 genannt wird.
Weil er den OP nicht ganz gelesen hat.
Thomas Z. schrieb:> Bedeutung von static in C
Das kann schon sein, es gibt ein C++17 aber kein C17. Das ändert aber
nichts daran, dass eine Warnung über eine "nicht initialisierte"
static-Variable weder in C noch in C++ sinnvoll ist. Daher hatte ich mir
diesen Aspekt heraus genommen.
Jörg W. schrieb:> Das kann schon sein, es gibt ein C++17 aber kein C17.
Nur ist C++17 eben kein C-Standard, und ich denke, das ist es, was damit
gemeint war:
Dennis S. schrieb:> Es gibt keinen C-Standard der in irgendeiner Weise C++17 genannt wird.
Der aktuelle C-Standard ist übrigens sowohl als C17, als auch als C18
bekannt. Es gibt also ein C17.
https://en.wikipedia.org/wiki/C17_(C_standard_revision)
von Jörg W. (dl8dtl) (Moderator) Benutzerseite
>16.06.2021 07:16>Klaus W. schrieb:>> (Trotzdem empfehle ich es, explizit zu schreiben)>Mit welcher Begründung?
Ich empfehle hier, sich an die MISRA-Regeln zu halten und explizit zu
initialisieren:
https://rules.sonarsource.com/c/RSPEC-836
Meiner Meinung nach sollte Code immer einfach und eindeutig sein und
dafür sind die MISRA-Regeln sehr gut geeignet.
chris_ schrieb:> Ich empfehle hier, sich an die MISRA-Regeln zu halten und explizit zu> initialisieren:>> https://rules.sonarsource.com/c/RSPEC-836>> Meiner Meinung nach sollte Code immer einfach und eindeutig sein und> dafür sind die MISRA-Regeln sehr gut geeignet.
Einfacher und dennoch eindeutig ist das Weglassen der expliziten
Initialisierung bei statischen Variablen.
Die verlinkte MISRA-Regel bezieht sich auf automatische Variablen. Da
steht nämlich "to avoid unexpected behaviors due to garbage values". Man
schaue sich auch die Beispiele an.
Für statische Variablen ist die Regel sowieso erfüllt, da diese
automatisch immer initialisiert sind.
chris_ schrieb:> Meiner Meinung nach sollte Code immer einfach und eindeutig sein und> dafür sind die MISRA-Regeln sehr gut geeignet.
MISRA ist gut, um Manager zu beruhigen. Da ist auch viel Sinnvolles
dabei, aber auch manches, was mit heutigen Compilern eigentlich nicht
mehr sinnvoll ist, sich aber trotzdem hartnäckig hält. Beispiele habe
ich genannt.
>MISRA ist gut, um Manager zu beruhigen. Da ist auch viel Sinnvolles>dabei, aber auch manches,
Du hast schon recht. Ich befolge auch nicht alle sklavisch. Wenn's aber
um die Frage geht, ob der Compiler die static Variable sicher auf 0
initialisiert oder nicht, will ich da gar nicht nachschauen, sondern
schreibe es einfach hin. Dann müssen sich andere, die den Code lesen, da
auch keine Gedanken machen.
MISRA fordert NICHT die explizite Initialisierung von static Variablen,
sondern nur von Variablen mit "automatic storage duration".
Im "Rationale" der entsprechenden Regel heißt es auch:
According to the Standard, objects with static storage duration are
automatically initialized to zero unless initialized explicitly. Objects
with automatic storage duration are not automatically initialized and
can therefore have indeterminate values.
Jörg W. schrieb:> MISRA ist gut, um Manager zu beruhigen.
MISRA ist auch für Entwickler(Teams) gut. Keine Forderung ist wirklich
unsinnig und bietet gerade für Anfänger oder leidenschaftslose
Programmierer eine gute Möglichkeit der Reflektion.
Würde MISRA wirklich fordern, Variablen explizit zu initialisieren, dann
gäbe es eine gut begründete Abweichung. Dann man hat eine verbindliche
Stelle, um "selbsternannte Experten" davon abzubringen.
Für kleine, engagierte Teams mit 10+ Jahren Erfahrung klingen natürlich
manche Punkte wie Gängelung. Da ist das aber auch in wenigen Stunden
abgehakt.
chris_ schrieb:> Ich empfehle hier, sich an die MISRA-Regeln zu halten und explizit zu> initialisieren:>> https://rules.sonarsource.com/c/RSPEC-836>> Meiner Meinung nach sollte Code immer einfach und eindeutig sein und> dafür sind die MISRA-Regeln sehr gut geeignet.
Manchen Code muss man umständlicher schreiben, weil man nach MISRA
bestimmte Sprachfeatures nicht nutzen darf.
P. S. schrieb:> MISRA fordert NICHT die explizite Initialisierung von static Variablen,> sondern nur von Variablen mit "automatic storage duration".>> Im "Rationale" der entsprechenden Regel heißt es auch:> According to the Standard, objects with static storage duration are> automatically initialized to zero unless initialized explicitly. Objects> with automatic storage duration are not automatically initialized and> can therefore have indeterminate values.
Wobei ich selbst die erzwungene Initialisierung von automatischen
Variablen in Frage stellen würde. Meistens ist es natürlich sinnvoll,
aber es gibt Fälle, in denen man an der Stelle der Definition einfach
noch keinen sinnvollen Initialisierungswert hat. An der Stelle würde ich
die Variable dort auch nicht initialisieren wollen mit irgendwas, nur
damit sie halt einen Wert hat. Das Problem: Wenn ich später dann
vergesse, der Variablen dann einen sinnvollen Wert zuzuweisen, sagt der
Compiler nichts. Wäre sie nicht initialisiert gewesen, hätte ich eine
Warnung bekommen.
A. S. schrieb:> Jörg W. schrieb:>> MISRA ist gut, um Manager zu beruhigen.>> MISRA ist auch für Entwickler(Teams) gut. Keine Forderung ist wirklich> unsinnig und bietet gerade für Anfänger oder leidenschaftslose> Programmierer eine gute Möglichkeit der Reflektion.
Das ist gerade mein Problem mit MISRA: Schlechte Programmierer werden
mit MISRA nicht automatisch zu guten Programmierern. Und gute
Programmierer wissen, wo sie auch von dem, was MISRA vorgibt, problemlos
abweichen können. Es gibt fast kein Sprachfeature, das niemals nützlich
ist.
Sinnvoller, als irgendein Dokument zu haben, dass einem solche
Einschränkungen auferlegt, wäre, die Programmierer im Umgang mit ihrem
Werkzeug ordentlich zu schulen, mit Prüfung.
Rolf M. schrieb:> Sinnvoller, als irgendein Dokument zu haben, dass einem solche> Einschränkungen auferlegt, wäre, die Programmierer im Umgang mit ihrem> Werkzeug ordentlich zu schulen, mit Prüfung.
Aber genau das macht und fordert MISRA. Zwar jetzt kein Test zur
Kenntnis der Regeln, aber immerhin eine Prüfung der Einhaltung. Hast Du
einen MISRA-Standard mal gelesen?
A. S. schrieb:> Aber genau das macht und fordert MISRA. Zwar jetzt kein Test zur> Kenntnis der Regeln, aber immerhin eine Prüfung der Einhaltung.
Genau das ist es aber, was ich nicht meine. Leute, die ihr Werkzeug
beherrschen, brauchen diese Regeln, um dessen Benutzung einzuschränken,
nicht. Da muss man hin, und nicht dahin, dass stumpf irgendwelche Regeln
befolgt werden und man dann glaubt, nichts mehr falsch machen zu können.
Rolf M. schrieb:> Wobei ich selbst die erzwungene Initialisierung von automatischen> Variablen in Frage stellen würde.
+1
Ich habe halt schon Fälle gesehen, wo mir die Warnung des Compilers "foo
may be used uninitialized" sehr viel wichtiger war, denn sie hat mich
auf einen Fehler in meinem Code hingewiesen. (Da sollte im Programmfluss
ein Wert zugewiesen werden, aber die Stelle wurde aufgrund eines Bugs
nicht immer erreicht.) Ein lemminghaftes "= 0", wie es MISRA leider
fordert, hätte mir genau diesen Fehler kaschiert und mich hinterher
tagelang suchen lassen.
Wenn ich nun aber die MISRA-Ausnahme erst explizit in einem 10seitigen
Antrag begründen muss, warum ich da kein "= 0" geschrieben habe, mal
ehrlich, dann bin ich als Programmierer faul und schreibe halt
lemminghaft das "= 0" immer hin. Und suche hinterher den Bug, den mir
sonst der Compiler angewarnt hätte.
Sorry, ist off-topic, es ging ja um statische Objekte.
Jörg W. schrieb:> Wenn ich nun aber die MISRA-Ausnahme erst explizit in einem 10seitigen> Antrag begründen muss, warum ich da kein "= 0" geschrieben habe, mal> ehrlich, dann bin ich als Programmierer faul und schreibe halt> lemminghaft das "= 0" immer hin. Und suche hinterher den Bug, den mir> sonst der Compiler angewarnt hätte.
Genau das sollst Du bei Misra nicht! Du stellst einmal fest, dass Dein
Compiler/Lint Dich zuverlässig warnt und fertig.
Es geht doch gerade darum, einen gemeinsamen Standard zu finden. Sonst
gibt es die einen, die auto-Variablen grundweg initialisieren, und
zwingen quasi die anderen diese "exotischen" Warnungen abzuschalten.
Schau doch mal alleine hier, wie viele mit Wall schon meinen, sie hätten
alle Warnungen eingeschaltet. Und MISRA richtet sich ja auch an typische
Programmierer mit 2-3 Jahren Erfahrung. Was ein Betrieb daraus macht,
hängt von den erfahrenen Profis der Firma ab.
Betrachte MISRA einfach als große Checkliste, in der die 100 wichtigsten
Punkte der Sprache C einmal angesprochen und entschieden werden. Es ist
ausdrücklich NICHT gedacht, den Standard ungefragt zu übernehmen.
A. S. schrieb:> Betrachte MISRA einfach als große Checkliste, in der die 100 wichtigsten> Punkte der Sprache C einmal angesprochen und entschieden werden. Es ist> ausdrücklich NICHT gedacht, den Standard ungefragt zu übernehmen.Rolf M. schrieb:> A. S. schrieb:>> Aber genau das macht und fordert MISRA. Zwar jetzt kein Test zur>> Kenntnis der Regeln, aber immerhin eine Prüfung der Einhaltung.>> Genau das ist es aber, was ich nicht meine. Leute, die ihr Werkzeug> beherrschen, brauchen diese Regeln, um dessen Benutzung einzuschränken,> nicht. Da muss man hin, und nicht dahin, dass stumpf irgendwelche Regeln> befolgt werden und man dann glaubt, nichts mehr falsch machen zu können.
Und Leute, die ihr Werkzeug nicht beherrschen, sollten die Zeit nicht
mit dem Lesen dieser Regeln verschwenden und statt dessen den Umgang mit
dem Werkzeug lernen.
Und nein, die Regeln helfen nicht beim Lernen. Das sieht man schön an
der oben verlinkten Regel. Es gibt keine wirkliche Erklärung
- was das Problem ist,
- warum ein =0 das Problem beseitigt,
- was man macht, wenn =0 nicht genutzt werden kann und
das Beispiel ist, wie üblich, schlecht gewählt.
mh schrieb:> Und nein, die Regeln helfen nicht beim Lernen. Das sieht man schön an> der oben verlinkten Regel. Es gibt keine wirkliche Erklärung> - was das Problem ist,> - warum ein =0 das Problem beseitigt,> - was man macht, wenn =0 nicht genutzt werden kann und> das Beispiel ist, wie üblich, schlecht gewählt.
Eine Vorschrift, wie Du sie beschreibst, kenne ich nicht. chris_ hat das
gefordert, nicht sein verlinkter Artikel (der auch nicht MISRA ist).
In Misra 2012 heißt die Regel z.B. (9.1):
> The value of an object with automatic storage duration> shall not be read before it has been set
Ich würde es eher als selbstverständlich ansehen, denn als unsinnig.
Es gibt dazu eine Seite Erklärung und Beispiele. Das Beispiel ist nicht
trivial:
1
voidf(bool_tb,uint16_t*p)
2
{
3
if(b)*p=3U;
4
}
5
6
voidg(void)
7
{
8
uint16_tu;
9
f(false,&u);
10
if(u==3U){..}// Non-complient ...
11
}
Misra fordert weder, dass u sofort initialisiert wird, noch dass es vor
dem Aufruf von f passiert. Sondern nur, dass es vor der Abfrage
geschehen ist.
Was bitte soll daran falsch sein?
Wo bitte sollte man sowas regelmäßig durchgehen lassen?
Sie gehen sogar extra noch daraus ein, dass man auch nicht mit goto um
die Initialisierung herumspringen sollte.
(Und natürlich kann man auch goto nach MISRA 2012 so oft verwenden wie
man will, wenn man sich darauf einigt, es ist nur "advisory".)
Ich glaube fast, manche haben sich ihre Meinung zu MISRA gebildet,
obwohl sie nur Erfahrung aus zweiter Hand haben.
Wer (auch als Experte) das Werk liest, wird feststellen, dass es
komprimiert UB, ID und Pitfalls von vielen Seiten beleuchtet.
Und ja, natürlich gibt es viele Dinge, die ich als lästig empfinde. Bei
jedem umstrittenen Punkt gab es aber auch (langjährige) Kollegen, denen
die Pitfalls gar nicht klar waren.
P.S.: die Regel ist eine von 2 unter dem Kapitel "Overlapping storage",
also wo Speicher mehrfach genutzt wird. Die andere ist, keine union zu
verwenden, ist aber nur "advisory" und daher: Wer's mag, machts.
Und auch da ist erklärt: "Wenn man es verwendet, denkt an Padding,
Alignment, Endianess und Bit-order." Das ist doch konstruktiv.
A. S. schrieb:> Misra fordert weder, dass u sofort initialisiert wird, noch dass es vor> dem Aufruf von f passiert. Sondern nur, dass es vor der Abfrage> geschehen ist.>> Was bitte soll daran falsch sein?
Daran ist nichts falsch. Genau so würde ich es auch sehen. Ich war davon
ausgegangen, dass das stimmt:
P. S. schrieb:> MISRA fordert NICHT die explizite Initialisierung von static Variablen,> sondern nur von Variablen mit "automatic storage duration".> Sie gehen sogar extra noch daraus ein, dass man auch nicht mit goto um> die Initialisierung herumspringen sollte.>> (Und natürlich kann man auch goto nach MISRA 2012 so oft verwenden wie> man will, wenn man sich darauf einigt, es ist nur "advisory".)
Soweit ich sehen kann, galt das aber nicht immer. Vor 2012 war es
komplett verboten.
> Ich glaube fast, manche haben sich ihre Meinung zu MISRA gebildet,> obwohl sie nur Erfahrung aus zweiter Hand haben.
Ich muss zugeben, dass du da zumindest im Bezug auf mich Recht hast.
> P.S.: die Regel ist eine von 2 unter dem Kapitel "Overlapping storage",> also wo Speicher mehrfach genutzt wird. Die andere ist, keine union zu> verwenden, ist aber nur "advisory" und daher: Wer's mag, machts.>> Und auch da ist erklärt: "Wenn man es verwendet, denkt an Padding,> Alignment, Endianess und Bit-order." Das ist doch konstruktiv.
Wenn es tatsächlich so einfach ist, ja.
Jörg W. schrieb:> Ein lemminghaftes "= 0", wie es MISRA leider> fordert
Auch das ist falsch. MISRA fordert lediglich, dass ein Wert nicht
gelesen werden darf bevor er gesetzt wurde.
P. S. schrieb:> MISRA fordert lediglich, dass ein Wert nicht> gelesen werden darf bevor er gesetzt wurde.
Das wiederum ist nun eine völlig selbstverständliche Foderung.
Zumindest haben einige Styleguides (wie Barr) jedoch eher solche
lemminghaften Vorschriften kreiert, und sie berufen sich dabei
zuallererst auf MISRA.
Jörg W. schrieb:> Das wiederum ist nun eine völlig selbstverständliche Foderung.
Ziele von MISRA sind auch weniger die Forderungen, ... als deren
Einhaltung. Also Lint & Co so scharf zu schalten, dass Verletzungen der
vereinbarten ("selbstverständlichen") Regeln erkannt werden.
Misra verwenden heißt:
* ein fester Rahmen für die Vereinbarungen
* ein gemeinsames Verständnis der Regeln
* eine Testsuite zur Prüfung, ob die vereinbarten automatisch prüfbaren
Regeln auch sicher erkannt und sicher nicht falsch erkannt werden
* Verfahren, um die nicht automatisch prüfbaren Regeln zu prüfen.
Ist zwar ein wenig Aufwand, aber geballte Erfahrung auf wenigen Seiten
für fast kostenlos.