GCC Preprozessor Berechnungen und Forced Errors

Gast #7306361
Lesenswert?

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

Gast #7306409
Lesenswert?

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.

#7306412
Lesenswert?

  1. Der Preprozessor macht nur Textersetzungen. Immer. Ohne Ausnahmen.
  2. Ausnahmen bestätigen die Regel.

https://microchipdeveloper.com/c:preprocessor-arithmetic

Die andere Antwort: In C++ kannst du sizeof in static_assert verwenden. In C brauchts wohl etwas schwarze Magie, aber irgendwie geht das dann auch: https://scaryreasoner.wordpress.com/2009/02/28/checking-sizeof-at-compile-time/

Oliver

Gast #7306523
Lesenswert?

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...

#7306546
Lesenswert?

Oliver S. schrieb:

  1. Der Preprozessor macht nur Textersetzungen. Immer. Ohne Ausnahmen.
  2. Ausnahmen bestätigen die Regel.

https://microchipdeveloper.com/c:preprocessor-arithmetic

Die andere Antwort: In C++ kannst du sizeof in static_assert verwenden. In C brauchts wohl etwas schwarze Magie, aber irgendwie geht das dann auch: https://scaryreasoner.wordpress.com/2009/02/28/checking-sizeof-at-compile-time/

Oliver

C kennt mittlerweile auch static_assert

1
#include <assert.h>
2

3
typedef struct {
4
    int x;
5
    int y;
6
} S;
7

8
static_assert(sizeof(S) <= 8);

https://godbolt.org/z/5Pbo3jeEW

Gast #7306605
Lesenswert?

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:

1
#define WERT1 1024
2
#define WERT2 64
3
#define WERT3 1
4
#define ERGEBNIS (WERT1*WERT2 / WERT3)
5

6
#if ERGEBNIS >= (1 << 16)
7
  #error ERGEBNIS zu gross
8
#endif
9

10
#define X (1 << 63)
11

12
#if X < 0
13
  #error X negative
14
#endif
15

16
#if X < 0U
17
  #error Tritt nie auf
18
#endif
Gast #7307276
Lesenswert?

Der Vollständigkeit halber, mit diesem Makro funktioniert es auch:

1
#define BUILD_BUG_ON(condition)  ((void)sizeof(char[1 - 2*!!(condition)]))
2

3
char some_array[10];
4

5
BUILD_BUG_ON(sizeof(some_array) != 10);
#7307297
Lesenswert?

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.

#7307305
Lesenswert?

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.

Angehängte Dateien:
#7307313
Lesenswert?

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).

#7307337
Lesenswert?

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.

#7307357
Lesenswert?

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.

#7307370
Lesenswert?

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.

Gast #7307388
Lesenswert?

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.

1
/* Code auf die das Wesentliche reduziert */
2
#define Teilfaktor 10
3
int tf = 10; // kein const
4
int func1(int in) { return in / Teilfaktor; }
5
int func2(int in) { return in / tf; }
6

7
Liefert mit cl -O1:
8

9
func1:
10
  0000000000000000: B8 67 66 66 66     mov         eax,66666667h
11
  0000000000000005: F7 E9              imul        ecx
12
  0000000000000007: C1 FA 02           sar         edx,2
13
  000000000000000A: 8B C2              mov         eax,edx
14
  000000000000000C: C1 E8 1F           shr         eax,1Fh
15
  000000000000000F: 03 C2              add         eax,edx
16
  0000000000000011: C3                 ret
17

18
func2:
19
  0000000000000000: 8B C1              mov         eax,ecx
20
  0000000000000002: 99                 cdq
21
  0000000000000003: F7 3D 00 00 00 00  idiv        eax,dword ptr [tf]
22
  0000000000000009: C3                 ret

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.

#7307408
Lesenswert?

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.

Gast #7307699
Lesenswert?

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.

#7307730
Lesenswert?

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).

#7307738
Lesenswert?

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.

#7307790
Lesenswert?

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.

Moderator Persönliche Seite #7307803
Lesenswert?

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.

(Firma: egal) #7308037
Lesenswert?

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.

#7308139
Lesenswert?

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

#7308269
Lesenswert?

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).

#7308308
Lesenswert?

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.

#7308311
Lesenswert?

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.

Gast #7308346
Lesenswert?

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++.

#7308371
Lesenswert?

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.

#7308606
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren