Präprozessor - "Vorabberechnungen"

Hallo,

der Präprozessor macht nur Textersetzung, ich weiß, deswegen etwas ungünstige Formulierung. Ich möchte jedoch wissen ob der Compiler danach so schlau ist und das als einmalige Berechnung erkennt und nicht jedesmal die #define Formeln nimmt und neu rechnet im laufenden Programm?

Bsp. für den Präprozessor :

#define microSek_pro_Grad  (1000.0 / (WINKEL_L-WINKEL_R))          
#define CompareMatch_TOP  (int) ((16.0/(1/microSek_pro_Grad)/8)-0.5)                                          
#define ISR_refresh_Time  ((CompareMatch_TOP+1) * 8.0 / 16)
#define COUNT_maxPULS  (int) (2400/ISR_refresh_Time)

und dann soll zum Bsp. COUNT_maxPULS in einer Timer ISR genutzt werden.

ISR(TIMER2_COMPA_vect)    
{
  ...
  ...
  
  if (countPulslaenge > COUNT_maxPULS) {        
    stop_Timer2();
    countPulslaenge = 0;
  }
}

Theoretisch müsste er alle define Formeln in der ISR jedesmal aufs neue durchrechnen? Wenn ja, wäre es dann von Vorteil das man eine Funktion schreibt und innerhalb setup einmalig rechnen lässt?

Natürlich erkennt der Kompiler konstante Ausdrücke und berechnet sie zu Kompilezeit.

Aber nicht immer ist irgendwas so konstant, wie der ursprüngliche Programmierer sich das gedacht hat.
Zumindest hat der Kompiler u.U. andere Vorstellungen davon, als der Programmierer beabsichtigt.

Darum:
Mal angenommen, das per #define zu tun wäre ein dumme Idee...
(und für sowas halte ich es)

Also:
#define COUNT_maxPULS (int) (2400/ISR_refresh_Time)
const int COUNT_maxPULS = 2400/ISR_refresh_Time;

Sage dem Kompiler das, was du tun willst!
Gib ihm keine Chance Mist zu bauen!

Gerade in C++/Arduino haben wir die Chance es richtig zu machen.

Lesestoff:
http://en.cppreference.com/w/cpp/language/constexpr

Leitsatz:
Jedes vermiedene Define, ist ein gutes Define.
Jedes vermeidbare Define ist ein böses Define.

combie:
Leitsatz:
Jedes vermiedene Define, ist ein gutes Define.
Jedes vermeidbare Define ist ein böses Define.

Gut, dass das hier gleich so gesagt wird.

Allerdings müssen gestandene C Programmierer wahrscheinlich erstmal tief Luft holen, wenn sie manchen Code mit constexpr sehen.

Hier mal aus den aktuellen Teensy Sourcen die Implementierung der Funktionen min und max:

Zum Vergleich vorher die alte Variante mit define

#define min(a, b) ({ \
  typeof(a) _a = (a); \
  typeof(b) _b = (b); \
  (_a < _b) ? _a : _b; \
})
#define max(a, b) ({ \
  typeof(a) _a = (a); \
  typeof(b) _b = (b); \
  (_a > _b) ? _a : _b; \
})

jetzt sieht das so aus :slight_smile:

template<class A, class B>
constexpr auto min(A&& a, B&& b) -> decltype(a < b ? std::forward<A>(a) : std::forward<B>(b)) {
  return a < b ? std::forward<A>(a) : std::forward<B>(b);
}
template<class A, class B>
constexpr auto max(A&& a, B&& b) -> decltype(a < b ? std::forward<A>(a) : std::forward<B>(b)) {
  return a >= b ? std::forward<A>(a) : std::forward<B>(b);
}

combie:

Leitsatz:
Jedes vermiedene Define, ist ein gutes Define.
Jedes vermeidbare Define ist ein böses Define.

Gut, dass ich mit dieser Ansicht nicht alleine bin.

Gruß

Gregor

Hallo,

so drastisch habe ich das noch nie betrachtet. Eigentlich wird ja nur "Codetext" verlagert. Sollte dem Compiler wiederum egal sein und das machen was da steht. Ob die Formel oder was weiß ich nun im Präprozessor steht oder im eigentlichen Code oder durch den Präprozessor in den Code kopiert wird. Nun denn, dann baue ich das um. Der tiefere Sinn dahinter war anfangs noch eine Kompilerabbruchbedingung einzubauen, wenn ein bestimmter Wert zu klein wird. Nur deshalb hatte ich define überhaupt verwendet. Das davon nun so sehr abgeraten wird, war mir nicht bewusst.
Jedenfalls Danke.

Doc_Arduino:
... Eigentlich wird ja nur "Codetext" verlagert. Sollte dem Compiler wiederum egal sein ...

Nein, der Code wird nicht einfach nur verlagert. Das Problem mit solchen #define-Zeilen ist, dass damit die Typprüfung des Compilers umgangen wird.

Gruß

Gregor

Doc_Arduino:
Ob die Formel oder was weiß ich nun im Präprozessor steht oder im eigentlichen Code oder durch den Präprozessor in den Code kopiert wird.

Wieso sind dir die Datentypen egal?

Hallo,

natürlich muss der Programmierer wissen was er tut, nur in dem Fall habe ich alles berücksichtigt?

ersteres wird ein float, zweiteres ein int

#define ISR_refresh_Time  ((CompareMatch_TOP+1) * 8.0 / 16)
#define COUNT_maxPULS  (int) (2400/ISR_refresh_Time)

daraus wird

#define COUNT_maxPULS  (int) ((CompareMatch_TOP+1) * 8.0 / 16)

und daraus wird

ISR(TIMER2_COMPA_vect)   
{
  static int countPulslaenge = 0;
  ...
  ...
  countPulslaenge++;
    
  if (countPulslaenge > (int) ((CompareMatch_TOP+1) * 8.0 / 16) {   
    stop_Timer2();
    countPulslaenge = 0;
  }
}

Ich weiß worauf ihr hinaus wollt. Also laut euer Meinung/Erfahrung bleibt für define dann nur noch sowas übrig ... wo nicht gerechnet werden muss

#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))  
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 
#define P28_OUT  sbi (DDRA,6)

Hallo,

ich stecke dennoch in einem Dilemma fest. Ich bin dabei meinen Servocode umzubauen. Um flexibel verschiedene Servos nutzen zu können. Dabei muss ich jedoch den ersten Wert microSek_pro_Grad abhängig berechnen können mit dem kleinsten und größten vorgebenen Servowinkel.

Im Deklarationsbereich (vor setup) kann ich keine for Schleife verwenden zum itererieren. Das alles in eine Funktion verfrachten und in setup aufrufen geht auch nicht, weil zu dem Zeitpunkt die const Variablen entweder nicht bekannt sind oder eben wegen const schon schreibgeschützt sind.

Muss ich auf const verzichten oder gibts eine mir unbekannte Lösung?

const byte _sum_servos = 3;
typedef struct servo_data_t {       // Eigenschaften eines Servos
  byte port;
  byte portBit;
  int winkel;
  int cal_winkel_R;         // ° bei Pulsdauer 1ms
  int cal_winkel_L;         // ° bei Pulsdauer 2ms
  int limit_winkel_R;       // ° der maximalen Auslenkung
  int limit_winkel_L;       // 
  volatile int pulslaenge;
}servo_data;                
servo_data servos[_sum_servos];      // alle Servos zusammengefasst
  
// Berechnungen für Timer Einstellung
constexpr float microSek_pro_Grad = (1000.0 / (MAX-MIN));        // Auflösung pro Grad (Diff Norm Pulszeit - Diff Winkel)
constexpr float Aufl = 1.0;                                     // einstellbare Auflösung "Grad pro Änderung"
constexpr int CompareMatch_TOP = (int) ((16.0/(1/microSek_pro_Grad/Aufl)/8)-0.5);                                          
constexpr float ISR_refresh_Time = ((CompareMatch_TOP+1) * 8.0 / 16);      // echte Timer ISR Intervallzeit [µs]
constexpr int COUNT_maxPULS = (int) (2400/ISR_refresh_Time);               // nach X ms Pulslänge wird Timer2 abgeschalten

void setup()  {

  servos[0].cal_winkel_R = 30;      // realer Servowinkel
  servos[0].cal_winkel_L = 150;     // realer Servowinkel 
  servos[1].cal_winkel_R = 45;
  servos[1].cal_winkel_L = 135;
  servos[2].cal_winkel_R = 20;
  servos[2].cal_winkel_L = 160;
}

von den servos.cal_winkel wollte ich den min/max Wert erhalten um das zu automatisieren.

In C++14 würde es eventuell mit constexpr gehen:

In gcc wurde das in Version 5 implementiert:
https://gcc.gnu.org/projects/cxx-status.html#cxx14

Die Arduino Toolchain verwendet aber höchstens 4.9.2 sowie es aussieht. Jedenfalls wenn man nach den Release Notes geht

Hallo,

ich werde das probieren ... :wink:

Wie gesagt, wird nicht gehen, da der verwendete Compiler das nicht unterstützt. constexpr in C++11 ist sehr beschränkt. In C++14 kann man mehr damit machen

Hallo,

da geht schon was, jedoch noch nicht wie gewünscht, oder ich möchte etwas was wirklich nicht geht, ob es an der Compiler Version liegt weiß ich nicht.

Testcode (IDE 1.8.5) funktioniert:

const int maxi = 135;
const int mini = 45;
const float Aufl = 1.0;
  
constexpr int microSek_pro_Grad (int L, int R) {         
  return (1000.0/(L-R));     
}

constexpr int CompareMatch_TOP (float aufloe) {               
  return (int) ((16.0/(1/aufloe)/8)-0.5);      
}
 

void setup() {
  Serial.begin(500000);

  constexpr int WERT1 = microSek_pro_Grad (maxi, mini);
  constexpr int WERT2 = CompareMatch_TOP (Aufl);
    
  Serial.print(WERT1);
  Serial.print('\t');
  Serial.print(WERT2);
  Serial.println();
}

void loop() {
  
}

Was nicht geht ist, das Ergebnis der ersten Funktion (WERT1) als Übergabeparameter für die zweite Funktion zuverwenden. Deswegen ist die 2. Formel unvollständig, weil ich das Zwischenergebnis nicht verwenden kann.

const int maxi = 135;
const int mini = 45;
const float Aufl = 1.0;
  
constexpr int microSek_pro_Grad (int L, int R) {         
  return (1000.0/(L-R));     
}

constexpr int CompareMatch_TOP (int microS, float aufloe) {               
  return (int) ((16.0/(1/microS/aufloe)/8)-0.5);      
}


void setup() {
  Serial.begin(500000);

  constexpr int WERT1 = microSek_pro_Grad (maxi, mini);
  constexpr int WERT2 = CompareMatch_TOP (WERT1, Aufl);
    
  Serial.print(WERT1);
  Serial.print('\t');
  Serial.print(WERT2);
  Serial.println();
}

void loop() {
  
}

Arduino: 1.8.5 (Windows 10), Board: "Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)"

C:\Users\Worker\AppData\Local\Temp\arduino_modified_sketch_593735\constexpr_test_003.ino: In function 'void setup()':

C:\Users\Worker\AppData\Local\Temp\arduino_modified_sketch_593735\constexpr_test_003.ino:18:54: in constexpr expansion of 'CompareMatch_TOP(11, 1.0e+0f)'

constexpr_test_003:18: error: '(1.6e+1 / 0.0)' is not a constant expression

constexpr int WERT2 = CompareMatch_TOP (WERT1, Aufl);

exit status 1
'(1.6e+1 / 0.0)' is not a constant expression

Hallo,

was geht ist, wenn man die erste Formel komplett in die 2. Funktion einsetzt. Im Grunde möchte ich mir zum debuggen noch Zwischenwerte ausgeben lassen. Am Ende werde ich das sicherlich nicht mehr benötigen. Dann wird die eine letzte Formel mit constexpr ein ganz schön langes Konstrukt ... genau das wollte ich vermeiden, falls man doch mal wieder debuggen möchte. Dann müsste man wieder alles zerlegen. Dabei fällt mir auf, dass ich die Berechnung der Differenz von min L und max R noch gar nicht probiert habe so wie es letztenendes gemacht werden soll ... :o

const int maxi = 135;
const int mini = 45;
const float Aufl = 1.0;
  
constexpr int microSek_pro_Grad (int L, int R) 
{         
  return (1000.0/(L-R));     
}

constexpr int CompareMatch_TOP (int microS, float aufloe) 
{               
  return (int) ((16.0/(1/microS/aufloe)/8)-0.5);      
}


void setup() {
  Serial.begin(9600);

  const int WERT1 = microSek_pro_Grad (maxi, mini);
  const int WERT2 = CompareMatch_TOP (WERT1, Aufl);
    
  Serial.print(WERT1);
  Serial.print('\t');
  Serial.print(WERT2);
  Serial.println();
}

void loop() {
  
}

Arduino IDE 1.8.3 sagt:

11 0

für den Mega kompiliert

Hallo,

seltsam das es bei dir kompiliert, :o als Ergebnis muss jedoch 11 und 21 rauskommen. Jetzt kam ich auf eine verrückte Idee, wenn ich microS als float deklariere, funktioniert es komischerweise, obwohl es ein int ist. Verstehe ich nicht. Warum will der jetzt float und nicht int? Bleibe ich bei int, kommt wieder das die Werte keine Konstanten sind.

const int maxi = 135;
const int mini = 45;
const float Aufl = 1.0;
  
constexpr int microSek_pro_Grad (int L, int R) {         
  return (1000.0/(L-R));     
}

constexpr int CompareMatch_TOP (float microS, float aufloe) {               
  return (int) ((16.0/(1/microS/aufloe)/8)-0.5);      
}
 

void setup() {
  Serial.begin(500000);
  
  constexpr int WERT1 = microSek_pro_Grad (maxi, mini);
  constexpr int WERT2 = CompareMatch_TOP (WERT1, Aufl);
    
  Serial.print(WERT1);
  Serial.print('\t');
  Serial.print(WERT2);
  Serial.println();
}

void loop() {
  
}

Vielleicht bringt deine IDE 1.8.3 dann als Ergebnis 0 und meine 1.8.5 meckert. Nur Vermutung.

error: '(1.6e+1 / 0.0)' is not a constant expression

Wo kommt die 0.0 her?

Hallo,

man oh man, man muss die Formel ändern das er 1.0 durch microS durch Auflösung rechnen kann, dann klappts auch mit int. :slight_smile: Die 1.0 benötigte ich vor der constexpr Sache nicht.

Danke für den Ansporn auf Grund deiner Tests. :slight_smile: Serenifly wird etwas staunen, nehme ich an. :slight_smile:

const int maxi = 135;
const int mini = 45;
const float Aufl = 1.0;
  
constexpr int microSek_pro_Grad (int L, int R) {         
  return (1000.0/(L-R));     
}

constexpr int CompareMatch_TOP (int microS, float aufloe) {               
  return (int) ((16.0/(1.0/microS/aufloe)/8)-0.5);      
}
 

void setup() {
  Serial.begin(500000);
  
  constexpr int WERT1 = microSek_pro_Grad (maxi, mini);
  constexpr int WERT2 = CompareMatch_TOP (WERT1, Aufl);
    
  Serial.print(WERT1);
  Serial.print('\t');
  Serial.print(WERT2);
  Serial.println();
}

void loop() {
  
}

return (int) ((16.0/(1/microS/aufloe)/8)-0.5);

Der erste verwendete Wert, ist der erste in der innersten Klammer.
Das ist bei dir halt die 1, und damit ist die Berechnung auf Integer festgelegt.
Das ist im C++ Standard so verankert.
Und auch immer und an allen Stellen so.

Wenn du also eine Float Berechnung möchtest, muss du daraus eine 1.0 machen, wie du richtig bemerkt hast.

Hallo,

ich dachte das die 16.0 ausreichend ist, weil define kein 1.0 benötigt. Siehe ganz oben. Deshalb wundert mich das jetzt so sehr. Dennoch danke für die Auffrischung. Morgen gehts dann weiter mit der for Schleife. Das eigentliche Ziel. Bis jetzt sehr interessant. :slight_smile:

Ich befürchte, dass du dich da irrst.

const int maxi = 135;
const int mini = 45;
const float Aufl = 1.0;
  
constexpr int microSek_pro_Grad (const int L,const int R) 
{         
  return (1000.0/(L-R));     
}

constexpr int CompareMatch_TOP (const int microS, const float aufloe) 
{               
  return (int) ((16.0/(1/microS/aufloe)/8)-0.5);      
}

#define TUE_ES_3 (int) ((16.0/(1/11/1.0)/8)-0.5)
#define TUE_ES_4 (int) ((16.0/(1.0/11/1.0)/8)-0.5)

void setup() {
  Serial.begin(9600);

  const int WERT1 = microSek_pro_Grad (maxi, mini);
  const int WERT2 = CompareMatch_TOP (WERT1, Aufl);
  int WERT3 = TUE_ES_3;  
  int WERT4 = TUE_ES_4;  
    
  Serial.print(WERT1);
  Serial.print('\t');
  Serial.print(WERT2);
  Serial.print('\t');
  Serial.print(WERT3);
  Serial.print('\t');
  Serial.print(WERT4);
  Serial.println();
}

void loop() {
  
}

ich dachte das die 16.0 ausreichend ist

Bevor die Floatberechnung, mit der 16.0 gemacht wird, muss erstmal die innere Klammer aufgelöst werden.
Und in dieser Klammer erzwingst du eine Integerberechnung.
Damit ist das Ergebnis der inneren Klammer vom Type Integer.
Dass hinterher mit float 16.0 weiter gerechnet wird, verhindert nicht das vorherige falsche Teilergebnis.