Hallo,
sicher kam das schonmal hier aber habe es nicht gefunden.
Müssen #define Berechnungen gecasted werden, wenn Zwischenergebnisse zb
größer sind als 16 Bit? Auch für float die gleiche Frage: Müssen da dann
kommastellen an die Zahlen? Also 500.0
Beispiel
1
#define WERT1 1000
2
#define WERT2 500
3
#define WERT3 300
4
5
#define ERGEBNIS = WERT1*WERT2 / WERT3
Die andere Frage ist: Kann man mit
#if sizeof (struct....) > GRENZWERT
#pragma error Errortext
#endif
oder ähnlich einen Fehler erzeugen zur Kompilierzeit, wenn mein Struct
in diesem Fall die Größe des EEPROMS übersteigt? So wie geschrieben
klappt es jedenfalls nicht. Der Fehler wird immer ausgelöst. Derzeit
behelfe ich mir diese Angaben zur Laufzeit zu prüfen, was aber
umständlich ist, da sie ja nicht dynamisch sind.
Gruss,
Thorsten
Meine Meinung zu #define: lass sie weg. Da passieren nur Fehler.
Man kann auch mit
const int xxx=6;
ganz normal Konstanten deklarieren und dann damit rechnen. Und dann
werden die normalen C++ Vorschriften benutzt.
Alle Rechnereien mit #define machen nur Probleme, weil dort nichts
gerechnet wird, sondern eine Textersetzung stattfindet.
PittyJ schrieb:> Meine Meinung zu #define: lass sie weg.
In C++ geht das fast vollständig, in C sind defines oft sinnvoll.
Zu den asserts Google nach "Compile time asserts"
PittyJ schrieb:> Meine Meinung zu #define: lass sie weg. Da passieren nur Fehler.
Ich frage mich wie ich ein Programm mit derzeit 13.000 Zeilen und 5
Modulen ohne sowas schreiben soll, wenn es wartbar bleiben soll?
Natürlich ersetzt das nur aber ausgerechnet wird es trotzdem!
Nur nach welchen Regeln?
Habe kein assert etc. Läuft auf einem Cortex M0 Bare Metal Code
geschrieben...
Thorsten M. schrieb:> oder ähnlich einen Fehler erzeugen zur Kompilierzeit, wenn mein Struct> in diesem Fall die Größe des EEPROMS übersteigt?
Nein. Der Präprozessor heisst so, weil er im Prinzip als selbstständiges
Programm dem Compiler voran läuft und keine intime Kenntnis vom dem hat,
was erst der Compiler weiss. Und sizeof kennt erst der Compiler.
Thorsten M. schrieb:> Müssen #define Berechnungen gecasted werden, wenn Zwischenergebnisse zb> größer sind als 16 Bit? Auch für float die gleiche Frage: Müssen da dann> kommastellen an die Zahlen? Also 500.0
Berechnungen finden im grössten Integer Typ statt. Es geht nur Integer
und sonst nix. Casten gibt es im Präprozessor nicht.
Wenn das Ergebnis unsigned sein soll, kannst du U dranhängen.
Siehe Beispiel:
Oliver S. schrieb:> Aber nur X86, keine andere Architektur.
Doch, ESP32, ARM, MIPS, 6502 u.v.m. Ist aber nicht so ganz einfach zu
durchschauen, das Teil.
Thorsten M. schrieb:> Oliver S. schrieb:>> Aber nur X86, keine andere Architektur.>> Doch, ESP32, ARM, MIPS, 6502 u.v.m. Ist aber nicht so ganz einfach zu> durchschauen, das Teil.
Was muss man denn tun, damit diese Versionen ausgeführt werden?
Nix.
Die binaries aller anderen Architekturen ausser X86 können zwar erzeugt,
aber nicht ausgeführt werden. Wie auch. Der Server müsste die anderen
Architekturen dafür ja alle emulieren können.
Oliver
Oliver S. schrieb:> Nix.>> Die binaries aller anderen Architekturen ausser X86 können zwar erzeugt,> aber nicht ausgeführt werden.
Ja, aber es wurde ja behauptet, dass das auch mit anderen ginge:
Thorsten M. schrieb:> Doch, ESP32, ARM, MIPS, 6502 u.v.m. Ist aber nicht so ganz einfach zu> durchschauen, das Teil.> Der Server müsste die anderen Architekturen dafür ja alle emulieren> können.
Ja, wobei das ja zumindest für einen Teil der Architekturen nicht ganz
unmöglich wäre. Aber deshalb die Frage, wie das denn gehen soll.
> Nein. Der Präprozessor heisst so, weil er im Prinzip als selbstständiges> Programm dem Compiler voran läuft und keine intime Kenntnis vom dem hat,> was erst der Compiler weiss. Und sizeof kennt erst der Compiler.
Zumindest bei dem GCC weiss ich nicht, ob das so stimmt.
Folgendes Beipiel habe ich mal angehängt zum Gegenbeweis:
Übersetzen mit gcc x.c -O0 --save-temps erzeugt die angehängte *.s Datei
(Assembler output)
in func1() (per define) wird die Division durch 10 durch eine
Multiplikation und anschliessendes Shift ersetzt.
In func2() (per const Variable) wird der HW Divisionsbefehl benutzt.
(Den will ich vermeiden, weil auf den meisten Prozessoren arschlangsam.)
Wenn ich mit -O2 übersetze, kommt bei func1() und func2() dasselbe
heraus.
Das ist zumindest merkwürdig.
Irgendetwas macht der Prepro, was eigentlich Aufgabe des Compilers wäre.
Jörg W. schrieb:> Vincent H. schrieb:>> C kennt mittlerweile auch static_assert>> Ist ein C23-Feature.
Nö, das gibt es seit C11.
Martin B. schrieb:>> Nein. Der Präprozessor heisst so, weil er im Prinzip als selbstständiges>> Programm dem Compiler voran läuft und keine intime Kenntnis vom dem hat,>> was erst der Compiler weiss. Und sizeof kennt erst der Compiler.>> Zumindest bei dem GCC weiss ich nicht, ob das so stimmt.
Beim GCC ist der Präprozessor mit im Compiler eingebaut, aber dadurch
darf sich dessen Verhalten natürlich nicht ändern.
> In func2() (per const Variable) wird der HW Divisionsbefehl benutzt.> (Den will ich vermeiden, weil auf den meisten Prozessoren arschlangsam.)
Das hat aber nichts mit dem Präprozessor zu tun, sondern damit, dass
eine const-Variable eben keine Konstante ist, sondern immer noch eine
Variable, während die direkt hingeschriebene Zahl 10 eine echte
Konstante ist, und durch die ersetzt ja der Präpozessor jedes Vorkommen
von "Teilfaktor" (das eher "Divisor" heißen müsste).
Rolf M. schrieb:>> Ist ein C23-Feature.>> Nö, das gibt es seit C11.
OK, war mir nicht mehr klar. Ist natürlich vorteilhaft, dann sollte es
schon recht gut ausgerollt sein.
> Variable, während die direkt hingeschriebene Zahl 10 eine echte> Konstante ist, und durch die ersetzt ja der Präpozessor jedes Vorkommen> von "Teilfaktor" (das eher "Divisor" heißen müsste).
Eben nicht! Genau das passiert nicht. Schau Dir den Assemblercode noch
einmal an. Da wird in func1() vorher etwas errechnet, die 10 taucht
nicht auf.
Martin B. schrieb:> Schau Dir den Assemblercode noch> einmal an. Da wird in func1() vorher etwas errechnet.
Aber wie kommst du darauf, dass das am Präprozessor liegt?
Kommt was anderes raus, wenn du
1
>for(i=0;i<N;i++)in[i]=in[i]/Teilfaktor;
ersetzt durch:
1
for(i=0;i<(100);i++)in[i]=in[i]/10;
? Denn das würde exakt dem entsprechen, was der Präprozessor hier tun
sollte.
Rolf M. schrieb:> Martin B. schrieb:>> Schau Dir den Assemblercode noch>> einmal an. Da wird in func1() vorher etwas errechnet.>> Aber wie kommst du darauf, dass das am Präprozessor liegt?
Weil ich dem Compiler verboten habe, zu optimieren. Vielleicht tut er es
trotzdem, kann ja sein.
Aber in func2() tut er es ja nicht. Erst wenn ich Optimierungen
einschalte.
Test mit /10 entlastet aber den Praeprozessor, selbes Ergebnis.
Martin B. schrieb:> Eben nicht! Genau das passiert nicht. Schau Dir den Assemblercode noch> einmal an. Da wird in func1() vorher etwas errechnet.
Bei mir wird da immer der identische Code erzeugt, mit -O1 und -O2.
Nur wenn das "const" bei tf weggelassen wird, dann wird func2 kompakter,
weil nur ein idiv verwendet wird.
Die Optimierung sollte eigentlich nur bei -O2 angemacht werden, und
nicht bei -O1 / -Os, weil sie in 99/100 Fällen nichts bringt und Platz
braucht.
Interessanterweise liefert der MS cl den kompaktesten Code.
Das static_assert Macro gibt es in Windows seit ca. NT 4.x, da wird es
so gemacht:
#define STATIC_ASSERT(expr) typedef char _assert_type[(expr)? 1 : -1]
Der Fehler wird aber nicht vom Präprozessor sondern vom Compiler
generiert, der ein ungültiges Typedef sieht.
Das BUILD_BUG_ON Macro vom Beitrag weiter vorne funktioniert bei mir
überhaupt nicht.
Martin B. schrieb:> Test mit /10 entlastet aber den Praeprozessor, selbes Ergebnis.
Das zeigt ja, dass der Präprozessor nichts damit zu tun hat.
Udo K. schrieb:> Das BUILD_BUG_ON Macro vom Beitrag weiter vorne funktioniert bei mir> überhaupt nicht.
Das wird daran liegen, dass es im Gegensatz zu den üblichen
static_asserts nicht außerhalb einer Funktion verwendet werden kann.
Udo K. schrieb:> dann wird func2> kompakter, weil nur ein idiv verwendet wird.
Der mag kompakter sein, er ist mit großer Wahrscheinlichkeit sehr viel
langsamer. (Auf PC Prozessoren habe ich das aber nie gemessen.)
Rolf M. schrieb:> Das zeigt ja, dass der Präprozessor nichts damit zu tun hat.
Jupp, der ist raus. Die Operation sieht der nicht als Optimierung.
Martin B. schrieb:> Irgendetwas macht der Prepro, was eigentlich Aufgabe des Compilers wäre.
Macht er natürlich nicht.
Was er macht, brauchst du dir aber nicht zusammenzurätseln, du kannst es
dir doch einfach anschauen. -E, und schon weißt du es.
Oliver
Martin B. schrieb:> Weil ich dem Compiler verboten habe, zu optimieren. Vielleicht tut er es> trotzdem, kann ja sein.
Eine Compiler-Option zum Verbot des Optimierens wäre ungewöhnlich. Meist
gibt es nur verschieden wählbare "zusätzliche" Optimierungsstufen, die
mit mehr Aufwand Platz/Laufzeit weiter optimieren.
Nun, da musst Du dich eben dran gewöhnen.
Die Speicherklassen in C werden ihren Namen selten gerecht: an auto ist
nix automatisch, an static ist (meist) nichts statisch, volatile ist
keineswegs unberechenbar und const ist nicht konstant (sondern
readonly).
Noch nicht mal deine Präprozessor "Konstanten" sind Konstanten (sondern
Literale).
Markus F. schrieb:> Nun, da musst Du dich eben dran gewöhnen.>> Die Speicherklassen in C werden ihren Namen selten gerecht: an auto ist> nix automatisch,
Doch, natürlich. Der Speicher wird bei Verlassen des Blocks, in dem die
Variable definiert ist, automnatisch wieder frei.
> an static ist (meist) nichts statisch,
Außerhalb von Funktionen nicht, aber innerhalb schon. Außerhalb von
Funktionen hat es eine andere Bedeutung zugewiesen bekommen.
> volatile ist keineswegs unberechenbar
volatile heißt nicht unberechenbar, sondern flüchtig oder unbeständig,
und genau darum geht es da. Der Wert muss jedesmal neu gelesen werden,
weil nicht unbedingt das mehr drin steht, was man im Kontrollfluss
vorher reingeschriebne hat.
> und const ist nicht konstant (sondern readonly).
Zumindest da würde ich zustimmen.
> Noch nicht mal deine Präprozessor "Konstanten" sind Konstanten (sondern> Literale).
Wie schon gesagt: Doch, sind sie. Der Begriff "integer literal" kommt im
C-Standard im Gegensatz zu "integer constant" überhaupt nicht vor.
Rolf M. schrieb:> Markus F. schrieb:>> Nun, da musst Du dich eben dran gewöhnen.>>>> Die Speicherklassen in C werden ihren Namen selten gerecht: an auto ist>> nix automatisch,>> Doch, natürlich. Der Speicher wird bei Verlassen des Blocks, in dem die> Variable definiert ist, automnatisch wieder frei.>
das gilt für alle (auch die globalen) Variablen in C. Ausser denen,
die statisch sind. auto ist nicht "automatischer" als eine globale
Variable.
>> an static ist (meist) nichts statisch,>> Außerhalb von Funktionen nicht, aber innerhalb schon. Außerhalb von> Funktionen hat es eine andere Bedeutung zugewiesen bekommen.>>> volatile ist keineswegs unberechenbar>> volatile heißt nicht unberechenbar, sondern flüchtig oder unbeständig,> und genau darum geht es da. Der Wert muss jedesmal neu gelesen werden,> weil nicht unbedingt das mehr drin steht, was man im Kontrollfluss> vorher reingeschriebne hat.>
volatile heisst (in meinem Oxford dictionary) durchaus unberechenbar
(von mir aus auch instabil oder leicht verdunstend. "flüchtig" fällt nur
dem englischen Chemiker zuerst ein, dem von der Strasse nicht.
>> Noch nicht mal deine Präprozessor "Konstanten" sind Konstanten (sondern>> Literale).>> Wie schon gesagt: Doch, sind sie. Der Begriff "integer literal" kommt im> C-Standard im Gegensatz zu "integer constant" überhaupt nicht vor.>
Wenn "3.141" eine Konstante ist, ist es längst nicht dasselbe wie "const
double pi = 3.141". Da ist in der Begrifflichkeit schon was falsch. Von
pi z.B. kann ich eine Adresse ermitteln, von 3.141 jedoch nicht - das
kann also nicht dasselbe sein.
Markus F. schrieb:>> Doch, natürlich. Der Speicher wird bei Verlassen des Blocks, in dem die>> Variable definiert ist, automnatisch wieder frei.>> das gilt für alle (auch die globalen) Variablen in C. Ausser denen, die> statisch sind.
Globale Variablen sind immer statisch.
"static" ist (auf oberstem Niveau) ein im Nachhinein gesehen sehr
unglücklich gewählter Name. Heute weiß man es besser, dass man hätte die
Voreinstellung (ohne Schlüsselwort) so machen sollen, dass die Objekte
(und Funktionen) dabei nur innerhalb der Übersetzungseinheit sichtbar
sind, während man eine globale Sichtbarkeit mit einem extra
Schlüsselwort ("extern" oder "global") explizit anfordert. Ist leider
nicht so, also hat "static" auf oberstem Niveau eine andere Bedeutung
als innerhalb von Funktionen.
Markus F. schrieb:> volatile heisst (in meinem Oxford dictionary) durchaus unberechenbar> (von mir aus auch instabil oder leicht verdunstend.
Wobei ich diese Übersetzung durchaus auch zutreffend finde. Der Inhalt
er Variable kann sich "unberechenbar", durch für an dieser Stelle im
Programmfluss nicht erkennbare Einflüsse, verändern und ist daher als
"instabil" anzusehen. Gegenmaßnahme, der Wert muss bei jedem Zugriff
erneut von der Originaladresse gelesen und geschrieben werden (das
Atomar zu gestalten ist wieder eine andere Baustelle). Der Inhalt darf
nicht über mehrere Befehle hinweg z.B. in einem Register
zwischengespeichert werden wie es bei einem "stabilen" Wert gemacht
werden darf der sich nicht "unvorhersehbar" verändern kann.
Markus F. schrieb:>> Doch, natürlich. Der Speicher wird bei Verlassen des Blocks, in dem die>> Variable definiert ist, automnatisch wieder frei.>>> das gilt für alle (auch die globalen) Variablen in C.
Globale Variablen stehen gar nicht in einem Block. Sie existieren über
die gesamte Programmlaufzeit hinweg. Aus Sicht des Programms werden sie
nie freigegeben.
> Ausser denen, die statisch sind.
Bezüglich der Lebensdauer gibt es auf globaler Ebene keinerlei
Unterschied zwischen static und nicht-static Variablen.
> auto ist nicht "automatischer" als eine globale Variable.
Doch. Ich kann in einem Programm 100 mal eine Funktion aufrufen, und
dann werden deren lokale Variablen 100 mal erzeugt und 100 mal wieder
freigegeben. Bei rekursiven Funktionen gibt es für jede Aufrufebene eine
eigene Instanz der Variablen.
Nach dem Rücksprung aus der Funktion ist die Variable jeweils nicht mehr
existent und wird beim nächsten Aufruf neu erzeugt.
> Wenn "3.141" eine Konstante ist, ist es längst nicht dasselbe wie "const> double pi = 3.141". Da ist in der Begrifflichkeit schon was falsch. Von> pi z.B. kann ich eine Adresse ermitteln, von 3.141 jedoch nicht - das> kann also nicht dasselbe sein.
Richtig. Deshalb habe ich weiter oben geschrieben, dass
Rolf M. schrieb:> eine const-Variable eben keine Konstante ist, sondern immer noch eine> Variable, während die direkt hingeschriebene Zahl 10 eine echte> Konstante ist
Rolf M. schrieb:> Globale Variablen stehen gar nicht in einem Block. Sie existieren über> die gesamte Programmlaufzeit hinweg. Aus Sicht des Programms werden sie> nie freigegeben.
Komisch. Bei mir sind die globalen Variablen nach dem Programmende immer
weg.
Joke aside - was ich meinte war: Variablen verschwinden, wenn ihr Scope
verlassen wird. Globale Variablen sind auf dem äussersten Scope
definiert (völlig wurscht, wie sie intern organisiert sind und dass sie
nicht auf dem Stack liegen). Dieser Scope wird nur ein einziges Mal
betreten. Wird dieser Scope verlassen (bei einem µC-Programm passiert
das nie), sind sie weg (also genauso "automatisch" wie auto-Variablen).
Markus F. schrieb:> Globale Variablen sind auf dem äussersten Scope definiert> (völlig wurscht, wie sie intern organisiert sind und dass> sie nicht auf dem Stack liegen).
Die könnten doch genauso gut auf dem Stack liegen? Man könnte die
ganze bss section sparen, das wäre doch für ein Mikro-RTOS durchaus
vorteilhaft. Aber wie sähe die C-Syntax dafür aus?
Markus F. schrieb:> Wird dieser Scope verlassen (bei einem µC-Programm passiert> das nie), sind sie weg
oh, das passiert sehr wohl. Man kann aus main() zurückkehren und landet
dann im Startupcode. Wenn in main jetzt threads gestartet wurden und auf
nicht mehr gültige Variablen oder Objekte aus main zugreifen wollen,
dann gibt das lustige Effekte und man sucht Fehler erstmal ganz
woanders.
Bauform B. schrieb:> Markus F. schrieb:>> Globale Variablen sind auf dem äussersten Scope definiert>> (völlig wurscht, wie sie intern organisiert sind und dass>> sie nicht auf dem Stack liegen).>> Die könnten doch genauso gut auf dem Stack liegen? Man könnte die> ganze bss section sparen, das wäre doch für ein Mikro-RTOS durchaus> vorteilhaft. Aber wie sähe die C-Syntax dafür aus?
Das könnten sie - völlig ohne Änderung der Syntax. Der Compiler müsste
sich eben eine Tabelle mit Stack-Offsets anlegen.
Es würde allerdings den Austausch von Daten zwischen Startup-Code und
dem C-Programm erschweren.
PittyJ schrieb:> const int xxx=6;>> ganz normal Konstanten deklarieren und dann damit rechnen. Und dann> werden die normalen C++ Vorschriften benutzt.
Für sowas gibts mittlerweile auch constexpr seit C++11, ist explizit für
solche Vorberechnungen gedacht.
Jörg W. schrieb:> "static" ist (auf oberstem Niveau) ein im Nachhinein gesehen sehr> unglücklich gewählter Name. Heute weiß man es besser, dass man hätte die> Voreinstellung (ohne Schlüsselwort) so machen sollen, dass die Objekte> (und Funktionen) dabei nur innerhalb der Übersetzungseinheit sichtbar> sind, während man eine globale Sichtbarkeit mit einem extra> Schlüsselwort ("extern" oder "global") explizit anfordert.> Ist leider> nicht so, also hat "static" auf oberstem Niveau eine andere Bedeutung> als innerhalb von Funktionen.
Hmm, na ja, ganz anders ja aber nun auch nicht.
1
int foobar =1;
2
static int foobar2 =2;
3
4
static void funcFoo() {}
5
void funcFoo2() {}
6
7
int main()
8
{
9
static int foobar3=3;
10
foobar = 0;
11
foobar2= 0;
12
foobar3= 0;
13
14
funcFoo();
15
funcFoo2();
16
17
return 0;
18
}
Da sagt der nm dann schon für Funktionen und Variablen, dass die groß-
bzw. kleingeschrieben werden bei den Symbolen: (groß: global, klein:
local)
1
0000000000601028 D foobar
2
0000000000601030 d foobar2
3
0000000000400520 t funcFoo
4
00000000004004c0 T funcFoo2
5
00000000004004d0 T main
6
000000000060102c d main.foobar3
Ansonsten aber stimm ich dir zu, es wär besser, per Default Variablen
und Funktionen aus Linker-Sicht lokal zu halten - zumindest bei C/C++.
Markus F. schrieb:> Rolf M. schrieb:>> Globale Variablen stehen gar nicht in einem Block. Sie existieren über>> die gesamte Programmlaufzeit hinweg. Aus Sicht des Programms werden sie>> nie freigegeben.>> Komisch. Bei mir sind die globalen Variablen nach dem Programmende immer> weg.
Ich schrieb ganz bewusst "aus Sicht des Programms". Nach dem
Programmende gibt's keine Sicht des Programms mehr, weil es weg ist. Was
dann mit dem Speicher passiert, interessiert das Programm nicht mehr:
"Not my job". Und erzeugt werden sie bereits vor der Ausführung von
main(). Also existieren diese Variablen aus Sicht des Programms für
immer.
Bei automatischen Variablen ist das anders. Die werden innerhalb des
Programms beliebig oft erzeugt und entsprechend natürlich auch wieder
freigegeben, und das ganz automatisch.
db8fs schrieb:> Ansonsten aber stimm ich dir zu, es wär besser, per Default Variablen> und Funktionen aus Linker-Sicht lokal zu halten - zumindest bei C/C++.
Dafür gibt es in C++ ja Namespaces.
Udo K. schrieb:> Thorsten M. schrieb:>> Müssen #define Berechnungen gecasted werden, wenn Zwischenergebnisse zb>> größer sind als 16 Bit? Auch für float die gleiche Frage: Müssen da dann>> kommastellen an die Zahlen? Also 500.0>> Berechnungen finden im grössten Integer Typ statt. Es geht nur Integer> und sonst nix. Casten gibt es im Präprozessor nicht.> Wenn das Ergebnis unsigned sein soll, kannst du U dranhängen.>
Fast. Berechnungen im Präprozessor werden in intmax_t
(vorzeichenbehaftet) bzw. uintmax_t (vorzeichenlos) ausgeführt.
Seit C23 ist aber (u)intmax_t nicht mehr notwendigerweise der größte
Ganzzahltyp.