Überlauf in AVR mit C

OP #7306615
Lesenswert?

Hallo zusammen,

ich möchte einen P-Regler programmieren und bin gerade zu dumm dafür. Hier mein Code:

1
int32_t P_Teil;
2
int32_t e;     // Regeldifferenz
3
uint8_t P;     // Verstärkung
4
while(1){
5
e = T_soll - T_ist;
6
    
7
    if ((P*e) < 65535 && (P*e) > -65535)
8
    {
9
      P_Teil = P * e;
10
    }
11
    else if ((P*e) > 65535) P_Teil = 65535;
12
    else if ((P*e) < (-65535)) P_Teil = (-65535);
13
}

Mein Problem ist, dass sobald der P_Teil bei einer negativen Regeldifferenz zu 65535 wird anstatt negativ zu werden. Ich probiere jetzt schon einige Zeit und übersehe meinen Fehler. Vielen Dank für eure Hilfe, leider bin ich noch Anfänger in der Programmierung.

Gruß Palavel

Gast #7306628
Lesenswert?

Pavel schrieb:

Mein Problem ist, dass sobald der P_Teil bei einer negativen Regeldifferenz zu 65535 wird anstatt negativ zu werden.

Der signed Überlauf ist in C und C++ unspezifiziert und mündet in ein Undefined Behavior.

Dann gilt das ganze Programm als Fehler behaftet, ist als total kaputt anzusehen.

#7306641
Lesenswert?

Was willst Du denn regeln und wie schnell muß es sein? Ich hab die Erfahrung gemacht, wenn nichts dagegen spricht, macht man eine Regelung vorzugsweise in float. Besonders alle Arten von Temperaturregelungen sind aus CPU-Sicht schnarch langsam.

Ich würde aber nicht absichtlich Rechenzeit vernichten, d.h. einen Ausdruck nicht an 5 Stellen nochmal ausrechnen lassen. Einmal sollte reichen. Das ist dann auch übersichtlicher.

#7306652
Lesenswert?

Welchem Wertebereich hat e?

Davon hängt das Vorzeichen ab. Evtl kannst du dies als Unterscheidung nehmen

1
P_Teil = P * e; // muss immer berechnet werden.
2
if (((e<0) && (P_Teil > 0)) || (P_Teil < -65535)) P_Teil = -65535;
3
if (((e>0) && (P_Teil < 0)) || (P_Teil >  65535)) P_Teil =  65535;
(Firma: egal) #7306681
Lesenswert?

Pavel schrieb:

1
> int32_t P_Teil;
2
> int32_t e;     // Regeldifferenz
3
> uint8_t P;     // Verstärkung
4
> P_Teil = P * e; und ((P*e) > 65535)
5
>

Und da fangen vermutlich die Probleme an. Die vermischst hier nicht nur signed und unsigned, sondern auch noch 8Bit und 32Bit (und je nach Proz. auch noch 16Bit) und hoffst bei dem ganzen das der Compiler schon von sich aus den besten davon aussucht. Irrtum...:-)

Recht gemeine Falle ist, dass zuerst der gesamte Ausdruck berechnet wird und dann erst die Zuweisung (ggf. mit erf. cast) zur linken Seite erfolgt und noch so einige Fallen die du dir durch dein wildes Typendurcheinander eingebaut hast. Stichwort zum suchen/lernen ist hier unter anderem "integer promotion".

Gast #7306763
Lesenswert?

Fehlen in Zeile 7 nicht Klammern?

1
if (((P*e) < 65535) && ((P*e) > -65535))

Ich würde das Ganze aber in zwei if-Abfragen umformulieren, das ist dann auch übersichtlicher und besser zu warten.

1
int32_t P_Teil;
2
int32_t e;     // Regeldifferenz
3
uint8_t P;     // Verstärkung
4

5
while(1){
6
    e = T_soll - T_ist;
7
    P_Teil = P * e;
8
    if (P_Teil > 65535 ) { P_Teil = 65535; }
9
    if (P_Teil < -65535 ) { P_Teil = -65535; }
10
}
#7306893
Lesenswert?

Rolf M. schrieb:

Oliver S. schrieb:

Matthias Thiele schrieb:

65535 ist eine Integer Konstante

Nein, ist es nicht. Das ist ein Integer Literal.

Doch, ist es. Die Bezeichnung dafür im C-Standard ist "integer constant".

Vermutlich deshalb hat C++ das in Integer literal umgetauft. Es ist eben kein Integer, sondern ein literal, auch wenn’s C anders nennt.

Oliver

#7306934
Lesenswert?

Oliver S. schrieb:

Rolf M. schrieb:

Oliver S. schrieb:

Matthias Thiele schrieb:

65535 ist eine Integer Konstante

Nein, ist es nicht. Das ist ein Integer Literal.

Doch, ist es. Die Bezeichnung dafür im C-Standard ist "integer constant".

Vermutlich deshalb hat C++ das in Integer literal umgetauft. Es ist eben kein Integer, sondern ein literal, auch wenn’s C anders nennt.

Ich nehme an, du meinst "Konstante" statt "Integer". Ein direkt als Zahl hingeschriebener Wert ist die einzige Art von echter Konstante, die C kennt. Wie EAF schon schreibt, ist in C eine Variable, die mit const definiert ist, keine Konstante. Das ist in C++ anders, daher musste man dort den Begriff ändern.

Gast #7307859
Lesenswert?

Matthias T. schrieb:

Und ein Overflow in einem Literal ist undefined behavior.

Ähhhmm....

Was ist denn ein "Overflow in einem Literal"? Davon habe ich ja noch nie gehört....

"undefined behavior" da kann ich nichts zu sagen, da ich nicht weiß Was ein "Overflow in einem Literal" ist.

Sage es mir, bitte...

Matthias T. schrieb:

warning: overflow in implicit constant conversion [-Woverflow] int x = -655365;

Das ist übrigens KEIN Literal Überlauf, sondern eine verlustbehaftete Konvertierung.

#7307871
Lesenswert?

EAF schrieb:

Matthias T. schrieb:

warning: overflow in implicit constant conversion [-Woverflow] int x = -655365;

Das ist übrigens KEIN Literal Überlauf, sondern eine verlustbehaftete Konvertierung.

Wie bereits geschrieben - Du kannst bei den Begriffen beliebig viele Haare Spalten aber dem OP hilft das nicht weiter. Entscheidend ist, dass er den Bereich eines 16 Bit Integer verlassen hat und sich wundert, dass es nicht funktioniert.

Ob ein Literal einen Typ hat oder nicht ist eine etwas akademische Diskussion. Ich verstehe den Standard so, dass ein numerisches Literal dieser Art ein Integer (und kein Long) Typ besitzt. Natürlich kann man argumentieren, dass das Literal selber gar keinen Typ besitzt und erst bei der konkreten Verwendung in etwas konvertiert wird. Aber am Ende kommt das gleiche dabei raus - in diesem Fall undefiniertes Verhalten wegen eines Überlaufs.

Moderator #7307909
Lesenswert?

Matthias T. schrieb:

Ob ein Literal einen Typ hat oder nicht ist eine etwas akademische Diskussion.

Nein, das ist in der Sprachdefinition genau festgelegt.

Ich verstehe den Standard so, dass ein numerisches Literal dieser Art ein Integer (und kein Long) Typ besitzt.

Der Typ eines Integer-Literals wird passend zu seinem Wert festgelegt.

Natürlich kann man argumentieren, dass das Literal selber gar keinen Typ besitzt

Auch das ist genau festgelegt: Typlos ist es nur dann, wenn wenn der Wert durch keinen der verfügbaren Integer-typen dargestellt werden kann.

Einfach mal nachlesen.

Persönliche Seite #7307957
Lesenswert?

Man kann sich den Typ eines Literals oder allgemeiner eines Ausdrucks auch "anzeigen" lassen:

1
void (*f1) (__typeof__(65535));
2
void (*f2) (__typeof__(0.0));
3

4
void func (void)
5
{
6
    f1 = f2;
7
}

Übersetzt mit -W:

1
warning: assignment to 'void (*)(long int)' from incompatible pointer type 'void (*)(double)' [-Wincompatible-pointer-types]
2
     f1 = f2;
3
        ^

Hier ist 65535 also "long int".

#7308130
Lesenswert?

Matthias T. schrieb:

Meine Vorstellung ist hier nicht relevant. Was der Standard sagt ist entscheidend.

Ja, aber wenn man dir sagt, was im Standard steht, tust du das als Haarspalterei ab und behauptest stattdessen etwas anderes, das da gar nicht steht.

Und ein Overflow in einem Literal ist undefined behavior.

Nein. Nochmal: Im C-Standard gibt es keine integer literals, und einen Overflow bei Integer-Konstanten gibt es auch nicht.

Yalu X. schrieb:

Einfach mal nachlesen.

Ja, z.B. im letzten Draft von C18: https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf Siehe dort Kapitel 6.4.4.1 "Integer constants".

Gast #7308138
Lesenswert?

Matthias T. schrieb:

Aber am Ende kommt das gleiche dabei raus - in diesem Fall undefiniertes Verhalten wegen eines Überlaufs

Ernste Frage: hast Du verstanden, dass Dein Beispiel fehlerhaft war? Zumindest im Bezug auf den Code des TO?

Dass hingeschriebene Zahlen einen Typ haben und beim Rechnen damit Überlaufe möglich sind, ist ja richtig. 3x3000O ist nicht 90000. Und 0xFFFF etwas anderes als 65535. Ist verwirrend, wenn man erstmals darauf stößt. Kommt aber beim TO nicht vor.

#7308892
Lesenswert?

Kernigham und Ritchie sind mir leider nicht zur Hilfe gekommen. Integer Konstanten (und ja, die beiden nennen sie constants und bestehen nicht auf literals) werden automatisch zu unsigned int und auch zu long, wenn sie nicht in ein int passen - schon immer. Damit ist der modifier L scheinbar nur Dekoration. Vermutlich habe ich die falsche Information von einem Compiler, der nicht complient war.

Gast #7308912
Lesenswert?

Matthias T. schrieb:

scheinbar nur Dekoration

Nein, eben nicht: damit man sie in Rechnungen wie 3x30.000 verwenden kann, ist ein L (für eine der beiden Zahlen zumindest) erforderlich.

Und (0xffff+1 == 65535+1) ist nur dann true, wenn links ein L oder bei 65535 ein U steht. Dass im zweiten Fall beide Seiten 0 ergeben, macht es nicht einfacher

(Alle Beispiele mit 16 Bit ints)

Gast #7309090
Lesenswert?

Kernigham & Ritchie schrieb:

Dafür fehlt mir jede Art der Logik.

Ich will das jetzt gar nicht schönreden. Aber ich brauche Hex-Zahlen > 0x7fff (kleinere sind egal) meist für Masken, Bit-Felder oder Schiebeoperationen und die sind meist uint. Während Dezimalzahlen oft int sind.

Das eigentliche Problem liegt m.E. nicht bei den Literal-Überläufen (die Warnungen sind ja meist an), sondern z.B. bei

1
int i=-1;
2
unsigned int u = 4;
3

4
   if(i<u) {doit();} /*warum wird doit nie aufgerufen  ???*/
Gast #7309097
Lesenswert?

A. S. schrieb:

Das eigentliche Problem liegt m.E. nicht bei den Literal-Überläufen (die Warnungen sind ja meist an), sondern z.B. bei int i=-1; unsigned int u = 4; if(i<u) {doit();} /warum wird doit nie aufgerufen ???/

Mein C warnt mich! [C] c:#####\test.c: In function 'test': c:#####\test.c:10:7: warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int' [-Wsign-compare] 10 | if(i<u) {doit();} /warum wird doit nie aufgerufen ???/ | ^

[/C]

Gast #7309108
Lesenswert?

Pavel (pavelpalavel)

ich möchte einen P-Regler programmieren und bin gerade zu dumm dafür. Hier mein Code:

Du solltest Rechenzeit sparen und die Multiplikation nur einmal rechnen. Für einen guten Codestil noch ein Tipp: Keine "Magic Numbers".

1
// https://www.mikrocontroller.net/topic/548660
2

3
#define LIMIT_LOW -65535
4
#define LIMIT_HIGH 65535
5

6
const uint8_t P = 1;
7

8
int32_t P_controller(int32_t e)
9
{
10
  int32_t P_Teil;
11

12
  P_Teil = P * e;
13

14
  if (P_Teil < LIMIT_LOW) P_Teil = LIMIT_LOW;
15
  if (P_Teil > LIMIT_HIGH) P_Teil = LIMIT_HIGH;
16

17
  return P_Teil;
18
}
19

20
void setup()
21
{
22
  Serial.begin(115200);
23
}
24

25
int32_t T_ist = 0;
26
int32_t T_soll = -1000;
27

28
void loop()
29
{
30
  int32_t e;
31

32
  e = T_soll - T_ist;
33
  T_ist = P_controller(e);
34
  
35
  Serial.println(T_ist);
36
  delay(100);
37

38
}
#7309117
Lesenswert?

A. S. schrieb:

Kernigham & Ritchie schrieb:

Dafür fehlt mir jede Art der Logik.

Ich will das jetzt gar nicht schönreden. Aber ich brauche Hex-Zahlen > 0x7fff (kleinere sind egal) meist für Masken, Bit-Felder oder Schiebeoperationen und die sind meist uint.

Warum sind kleinere egal? 0x5000 << 1 ergibt undefiniertes Verhalten.

Während Dezimalzahlen oft int sind.

Vor allem: Will man wirklich, dass 30000 vorzeichenlos ist, 40000 aber vorzeichenbehaftet? Das fände ich eher nicht sehr intuitiv.

Gast #7309130
Lesenswert?

Rolf M. schrieb:

Warum sind kleinere egal? 0x5000 << 1 ergibt undefiniertes Verhalten.

Vor allem: Will man wirklich, dass 30000 vorzeichenlos ist, 40000 aber vorzeichenbehaftet? Das fände ich eher nicht sehr intuitiv.

Natürlich will man keines davon. Das Typsystem von C ist komplett kaputt. Von vorne bis hinten. Unrettbar.

Gast #7309131
Lesenswert?

#include <avr/io.h> #include <avr/interrupt.h>

#define Kp 1.0 // Proportional gain #define dT 0.1 // Timestep (seconds) #define PWM_FREQUENCY 500 // PWM frequency (Hz) #define PWM_RANGE 100 // PWM range (0-100)

float proportional_control(float setpoint, float measured_value) { float error = setpoint - measured_value; return Kp * error; }

int main() { float setpoint = 50.0; // Setpoint (rpm) float measured_value = 45.0; // Measured value (rpm)

1
// Set up Timer1 for PWM generation
2
TCCR1A |= (1 << WGM10) | (1 << WGM11); // Fast PWM mode
3
TCCR1B |= (1 << WGM12) | (1 << WGM13);
4
TCCR1A |= (1 << COM1A1); // Non-inverting mode
5
TCCR1B |= (1 << CS10); // No prescaler
6
OCR1A = 0; // Initial PWM duty cycle
7
ICR1 = F_CPU / PWM_FREQUENCY / PWM_RANGE; // Set PWM frequency
8

9
// Enable global interrupts
10
sei();
11

12
while (1)
13
{
14
    // Calculate control signal
15
    float control_signal = proportional_control(setpoint, measured_value);
16

17
    // Set PWM duty cycle
18
    OCR1A = (uint16_t)(control_signal * PWM_RANGE);
19

20
    // Sleep for timestep dT
21
    _delay_ms(dT * 1000);
22
}
23

24
return 0;

}

Gast #7309145
Lesenswert?

Gerd schrieb:

Wäre ziemlich beeindruckend.

Solche einfachen Regler gibt es zu Hauf im Internet. Ich finde das weniger beeindruckend, wenn eine "KI" aus dieser Menge sich eine "eigene" Lösung zurechtraubkopieren kann.

Ist und bleibt eine gut versteckte Urheberrechtsverletzung.

Gast #7309151
Lesenswert?

ChatGPT mit Integer

#include <avr/io.h> #include <avr/interrupt.h>

#define Kp 100 // Proportional gain #define dT 100 // Timestep (milliseconds) #define PWM_FREQUENCY 500 // PWM frequency (Hz) #define PWM_RANGE 100 // PWM range (0-100)

int proportional_control(int setpoint, int measured_value) { int error = setpoint - measured_value; return Kp * error / 100; }

int main() { int setpoint = 5000; // Setpoint (rpm) int measured_value = 4500; // Measured value (rpm)

1
// Set up Timer1 for PWM generation
2
TCCR1A |= (1 << WGM10) | (1 << WGM11); // Fast PWM mode
3
TCCR1B |= (1 << WGM12) | (1 << WGM13);
4
TCCR1A |= (1 << COM1A1); // Non-inverting mode
5
TCCR1B |= (1 << CS10); // No prescaler
6
OCR1A = 0; // Initial PWM duty cycle
7
ICR1 = F_CPU / PWM_FREQUENCY / PWM_RANGE; // Set PWM frequency
8

9
// Enable global interrupts
10
sei();
11

12
while (1)
13
{
14
    // Calculate control signal
15
    int control_signal = proportional_control(setpoint, measured_value);
16

17
    // Set PWM duty cycle
18
    OCR1A = control_signal * PWM_RANGE / 100;
19

20
    // Sleep for timestep dT
21
    _delay_ms(dT);
22
}
23

24
return 0;

}

#7309269
Lesenswert?

A. S. schrieb:

scheinbar nur Dekoration

Nein, eben nicht: damit man sie in Rechnungen wie 3x30.000 verwenden kann, ist ein L (für eine der beiden Zahlen zumindest) erforderlich.

Prinzipiell schon. Allerdings werten alle Compiler, die ich unter "Compiler Explorer" getestet habe, constant expressions schon zur compile time aus und benötigen das L ebenfalls nicht. In der Dokumentation zum Microsoft Compiler steht auch ausdrücklich, dass constant expressions immer zur compile time ausgewertet werden (was ja auch Sinn ergibt).

Ich weiß aber nicht, ob sich der Standard dazu äußert...

Moderator #7309284
Lesenswert?

Matthias T. schrieb:

A. S. schrieb:

scheinbar nur Dekoration

Nein, eben nicht: damit man sie in Rechnungen wie 3x30.000 verwenden kann, ist ein L (für eine der beiden Zahlen zumindest) erforderlich.

Prinzipiell schon. Allerdings werten alle Compiler, die ich unter "Compiler Explorer" getestet habe, constant expressions schon zur compile time aus und benötigen das L ebenfalls nicht.

Die Auswertung zur Compile-Zeit ändert aber am (falschen) Ergebnis nichts.

Moderator #7309285
Lesenswert?

So macht es die KI schrieb:

ChatGPT mit Integer

Dass der Code durch eine KI generiert wurde, ist schon beeindruckend. Er ist aber weitgehend nutzlos, weil er etliche Fehler enthält. Diese zu finden und zu korrigieren ist mindestens so aufwendig, als den Code einfach neu zu schreiben (so ein P-Regler ist ja keine Raketentechnik).

Zeile 12:

1
    return Kp * error / 100;

Das Produkt 100 * 500 = 50000 führt zum Überlauf (INT_MAX = 32767). Der gesamte Ausdruck muss in int32_t gerechnet werden, das Ergebnis kann wieder in int gecastet werden. Der Rückgabewert ist dann 100 * 500 / 100 = 500.

Zeile 21:

1
    TCCR1A |= (1 << WGM10) | (1 << WGM11); // Fast PWM mode

WGM10 darf nicht gesetzt werden, da ICR1 als TOP-Wert des Timers verwendet werden soll (s. Zeile 26).

Zeile 37:

1
        OCR1A = control_signal * PWM_RANGE / 100;

Diese Zeile setzt voraus, dass control_signal auf [0..100] (in der float-Version auf [0..1.0]) normiert ist. Eine solche Normierung findet aber nirgends im Programm statt. Konkret ist – nachdem der Fehler in Zeile 12 korrigiert wurde – control_signal = 500, damit ist das Ergebnis des Ausdrucks ebenfalls 500 und damit größer als PWM_RANGE. PWM_RANGE wird deswegen seinem Namen nicht gerecht und ist einfach nur ein weiterer Skalierungsfaktor.

Da ICR1 (Zeile 26) maximal 20 MHz ÷ 500 ÷ 100 = 400 sein kann, wird der Timer den Wert von OCR1A = 500 nie erreichen. Die Regelung verlässt damit für OCR1A > 400 den linearen Bereich. Das kann man zwar akzeptieren, aber schön ist das nicht und sollte höchstens in Ausnahmefällen (bspw. beim Starten des Systems, wenn es noch nicht eingeschwungen ist) geschehen.

Da hat die KI offensichtlich aus mehreren, möglicherweise ebenfalls fehlerhaften Code-Fragmenten aus dem Web etwas zusammengestückelt. Dass die KI den Code nicht wirklich versteht, dürfte klar sein.

Gast #7309297
Lesenswert?

Yalu X. schrieb:

Er ist aber weitgehend nutzlos, weil er zu etliche Fehler enthält.

Natürlich. Das sind ja auch nur Codestücke aus tausenden Open Source Programmen. Ein Verständnis für Algorithmus, Sprache und Mikrocontroller liegt hier seitens "KI" ja nicht vor.

Ich kann mir zwar durchaus vorstellen, dass Implementationen von Algorithmen in Zukunft in einigen Bereichen mit solchen "KI"-Generatoren generiert werden. Aber das sehe ich eigentlich grundsätzlich nur für Programmiersprachen, die extra dafür gemacht sind und wo solche primitiven Fehler wie Integerüberläufe oder Typfehler von der Sprache selbst verhindert werden.

Es ist nutzlos ein Programm zu schreiben oder sich schreiben zu lassen, das fehlerhaft ist. Und es ist fast genau so nutzlos, wenn man keinerlei Aussage über dessen Fehlerhaftigkeit hat.

Nicht ohne Grund haben seriöse Machine-Learning-Einsatzgebiete auch immer noch Plausibilisierungen und Fehlerbehandlungen. Entweder nachgeschaltet klassisch implementiert, oder teilweise vom Netz selbst generiert (Softmax), oder oft eine Kombination aus beidem.

tl;dr Dahingeklatschter generierter Code ist nutzlos. Wenn man eine "KI" nach etwas fragt, dann gibt sie immer eine Antwort. Die Kunst ist herauszufinden, wann sie falsch liegt.

Gast #7309306
Lesenswert?

Der Spiegel hat gerade einen Artikel veröffentlicht, wonach KI Systeme wie ChatGPT womöglich bald die Google Suchmaschine ersetzten könnten. Vorteil sei vor allen das Textverständnis der KI. Mann müsse seine Suchanfragen nicht mehr Google-Konform formulieren, sondern kann natürliche Sprache verwenden. https://www.spiegel.de/netzwelt/netzpolitik/bessere-treffer-durch-chatgpt-das-ende-von-google-wie-wir-es-kannten-kolumne-a-77820af6-51d7-4c03-b822-cf93094fd709

Ich denke, dass der Autor folgende erhebliche Nachteile außer Acht gelassen hat:

  1. Die KI ist nicht imstande, ihre Quellen zu nennen.

  2. Die KI Antwortet immer überzeugend, selbst wenn sie Null Ahnung hat.

  3. Die KI gibt immer nur eine Antwort.

  4. Die Traningsdaten sind nicht aktuell und werden es wohl auch nie sein. Würde die KI das ganze Internet direkt (ohne manuelle Filterung) verwenden, dann würde sie viel Bullshit nachplappern.

#7309328
Lesenswert?

Yalu X. schrieb:

compile time aus und benötigen das L ebenfalls nicht.

Die Auswertung zur Compile-Zeit ändert aber am (falschen) Ergebnis nichts.

Bei der Auswertung zur Compile-Zeit ist das Ergebnis korrekt. Der Compiler erkennt, dass er den int Bereich verlässt und wechselt automatisch auf long (oder er rechnet diese Ausdrücke intern immer in long). Zumindest alle Compiler, die ich getestet habe. Ob der Standard das fordert oder ob es vielleicht obskure Compiler gibt, die es nicht tun, ist mir nicht bekannt.

Gast #7309334
Lesenswert?

Stefan F. schrieb:

  1. Die KI ist nicht imstande, ihre Quellen zu nennen.

Es ist jedefalls extrem aufwändig und auch nicht immer im Einzelfall zweifelsfrei möglich.

  1. Die KI Antwortet immer überzeugend, selbst wenn sie Null Ahnung hat.

Ja. Die einfachste Implementierung einer "KI" ohne Plausibilisierung tut genau das. Und das ist auch ihr größtes Problem. Dieses Problem macht die Ausgabe nutzlos.

Meine Hochachtung vor dem, was die Wissenschaftler und Ingenieure hier geschaffen haben. Aber das ist erst ein ganz ganz ganz kleiner Teil einer allgemeinen und nutzbaren Antwortmaschine.

  1. Die KI gibt immer nur eine Antwort.

Das würde ich so nicht unterschreiben.

So macht es die KI schrieb:

Proportionalregler in ansi c code

Bitte für Atmel AVR Controller

Bitte mit Integer statt float….

Ja. Das ist dein Programm. Du hast dein Programm nicht in C formuliert, sondern in Deutsch.

Das kann natürlich sinnvoll sein. Schließlich ist es eine Erhebung in eine höhere Hochsprache. Aber es muss nicht sinnvoll sein. Ziemlich sicher sogar ist es oft nicht sinnvoll, weil sich in der Sprache Deutsch viele Dinge gar nicht eindeutig formulieren lassen. Jeder, der einmal Anforderungsengineering gemacht hat, wird das wissen.

#7309346
Lesenswert?

MaWin schrieb:

Yalu X. schrieb:

Er ist aber weitgehend nutzlos, weil er zu etliche Fehler enthält.

Natürlich. Das sind ja auch nur Codestücke aus tausenden Open Source Programmen. Ein Verständnis für Algorithmus, Sprache und Mikrocontroller liegt hier seitens "KI" ja nicht vor.

Würd mich interessieren, wie die "KI" das selbst bewertet hat. Um die 87%?! :DDD

Gast #7309349
Lesenswert?

J. S. schrieb:

Man kann die KI auf Fehler hinweisen und die schafft es auch die zu korrigieren

Aber sobald du einen neuen Chat beginnst (selbst ohne dich auszuloggen) hat sie deine Korrektur schon wieder vergessen.

Du frage dabei ist: Wie kann die KI deine Korrektur bewerten?

Man könnte der KI 20x sagen, dass Menschen aus bestimmten Regionen wegen einem Gendefekt geistig behindert seien. Das könnte sogar wahr sein. Die Entwickler werden wahrscheinlich dafür sorgen, dass sie KI solche Aussagen trotzdem nicht übernimmt. Dann ich sie aber noch weniger neutral, als wenn sie bei den Fakten bleiben würde. Ein fieses Dilemma.

Gast #7309351
Lesenswert?

J. S. schrieb:

Man kann die KI auf Fehler hinweisen und die schafft es auch die zu korrigieren

Das ist ja nur eine Rekursionsschleife, in der du dein in Deutsch geschriebenes Programm konkretisierst und Zweideutigkeiten entfernst, bis dein Compiler (hier ChatGPT) dir das korrekte Kompilat ausgibt. Fühlt sich an wie zurückversetzt in die Anfänge der Compilertechnik vor Jahrzehnten.

Moderator #7309352
Lesenswert?

Matthias T. schrieb:

Yalu X. schrieb:

compile time aus und benötigen das L ebenfalls nicht.

Die Auswertung zur Compile-Zeit ändert aber am (falschen) Ergebnis nichts.

Bei der Auswertung zur Compile-Zeit ist das Ergebnis korrekt. Der Compiler erkennt, dass er den int Bereich verlässt und wechselt automatisch auf long (oder er rechnet diese Ausdrücke intern immer in long).

Mit welchem fehlerhaften Compiler hast du das getestet?

Edit:

330000 auf einem 16-Bit-System ist ohnehin undefined, da darf der Compiler ein beliebiges Ergebnis liefern, also bspw. auch 90000 (wobei das aber wahrscheinlich kein real existierende Compiler tun wird). In diesem Fall ist nicht der Compiler, sondern der Quellcode mit dem 330000 fehlerhaft.

Nehmen wir als besseres Beispiel 3u*30000u (Berechnung erfolgt in unsigned int), dann ist auf einem 16-Bit-System das einzige standardkonforme Ergebnis 24464 und nicht etwa 90000.

#7309356
Lesenswert?

Teo D. schrieb:

Ein Verständnis für Algorithmus, Sprache und Mikrocontroller liegt hier seitens "KI" ja nicht vor.

Würd mich interessieren, wie die "KI" das selbst bewertet hat. Um die 87%?! :DDD

Das ist in der Tat gar nicht so eine schlechte Idee und solche Techniken werden ja bereits eingesetzt. Man kann z.B. mit Softmax eine Aussage darüber erhalten, "wie sicher" sich ein einfaches MLP-Netz mit seiner Antwort ist, oder ob das Netz "zweifelt".

Eine Aussage darüber zu bekommen, wie sicher sich ChatGPT ist und wie tief es "verstanden" hat worüber es redet, ist die eigentliche knifflige Aufgabe, die es zu lösen gilt.

Beitrag #7309372 wurde von einem Moderator gelöscht.
#7309418
Lesenswert?

Yalu X. schrieb:

Bei der Auswertung zur Compile-Zeit ist das Ergebnis korrekt. Der Compiler erkennt, dass er den int Bereich verlässt und wechselt automatisch auf long (oder er rechnet diese Ausdrücke intern immer in long).

Mit welchem fehlerhaften Compiler hast du das getestet?

Edit:

330000 auf einem 16-Bit-System ist ohnehin undefined, da darf der Compiler ein beliebiges Ergebnis liefern, also bspw. auch 90000. In diesem Fall ist nicht der Compiler, sondern der Quellcode mit dem 330000 fehlerhaft.

Das hatte ich bis gestern auch geglaubt und in meinen eigenen Quellen würde ich auf jeden Fall (und werde es weiterhin auch tun) mit 30000L arbeiten. Getestet habe ich mit verschiedenen 16 Bit GCC und 16 MSVC - das ist beim Compiler Explorer eine Fragen von wenigen Maus Klicks. Die Zahl der 16 Bit Compiler ist allerdings überschaubar.

...das einzige standardkonforme Ergebnis 24464 und nicht etwa 90000.

Wenn das Ziel ein (16 Bit) int ist - ja. Wenn das Ziel ein long ist, dann ist es nicht so einfach. Für eine einzelne Konstante steht schon bei K&R ausdrücklich, dass der Compiler automatisch erst auf U und dann auf L wechselt, wenn es notwendig ist. Warum sollte er es bei konstanten Ausdrücken nicht dürfen? Da bin ich Neugierig auf die Stelle im Standard, die es verbietet.

Persönliche Seite #7309428
Lesenswert?

MaWin schrieb:

Du hast dein Programm nicht in C formuliert, sondern in Deutsch.

Und wie würde es aussehen, wenn man in eine KI C-Code reinfüttert und gerne eine Assembler-Ausgabe hätte, d.h. man möchte den Code compilieren?

Nach aktuellem Ansatz würde man i.W. Compiler-generierten Code als Trainingsdaten verwenden. Mal angenommen, der Compiler liefert immer korrekten Code (d.h. für alle spezifizierten Eingaben) und das C-Programm ist sinnvoll (d.h. nicht UB oder so).

Würde die KI immer korrekte Ausgaben (Asm-Code) liefern?

Die Antwort ist wohl "nein". In diesem Falle: Wie würde man korrekte von inkorrekten Ausgaben unterscheiden? Mit klassichen Compilern hat man dieses Prolbem ja nicht, weil ein Compiler ja "per Design" korrekten Code erzeugt, Compiler-Bugs mal außen vorgelassen.

Moderne Compiler sind überaus komplex und erzeugen Code, der oft weit entfernt von optimal ist, egal nach welcher Metrik bemessen. Eine KI würde all diese Schwächen vom Compiler erlernen.

Gibt es eine Möglichkeit, der KI den Sprachstandard beizubringen? Dite Hardware-Beschreibung? Oder gibt es andere Möglichkeiten, dass der erzeugte Code "korrekt per Design" ist?

Gast #7309435
Lesenswert?

Oliver S. schrieb:

Yalu X. schrieb:

3*30000 auf einem 16-Bit-System ist ohnehin undefined

Warum?

Oliver

Weil 30000 dezimal ist, dass ist signed und signed Überläufe sind UB.

Nochmal zurück zum Thema. Weiß jemand warum das so völlig verrückt ohne Logik festgelegt wurde? 0xFFFF >> HEX ist uint16_t 65535 >> DEZ ist int32_t

#7309442
Lesenswert?

Johann L. schrieb:

Würde die KI immer korrekte Ausgaben (Asm-Code) liefern?

Eine "KI" gibt nie eine 100% korrekte Antwort. Machine Learning hat mehr die Eigenschaften einer analogen Schaltung, als die einer eindeutigen digitalen Rechnung.

Die Antwort ist immer mit einem Fehler behaftet.

Das ist eine der wichtigsten Grundlagen, wie man bei "KI" und ML verstehen muss. Leider geben populäre "KI"-Tools wie ChatGPT diese Fehlerabschätzung nicht an den Nutzer aus. Entweder, weil die Abschätzung gar nicht stattfindet, oder weil das den Nutzer ja nur verwirren würde (tm).

Johann L. schrieb:

Die Antwort ist wohl "nein". In diesem Falle: Wie würde man korrekte von inkorrekten Ausgaben unterscheiden? Mit klassichen Compilern hat man dieses Prolbem ja nicht, weil ein Compiler ja "per Design" korrekten Code erzeugt, Compiler-Bugs mal außen vorgelassen.

Ja, genau deshalb ist es nicht sinnvoll ein Programm mit einer "KI" zu kompilieren. Jedenfalls dann, wenn man den Anspruch hat, dass das Ergebnis 100% korrekt sein muss.

Machine Learning ist dann sinnvoll, wenn man einen gewissen Fehler in den generierten Daten tolerieren kann. Und das mag bei einem generierten C-Programm ja in gewissem Rahmen durchaus tolerierbar sein. Aber der derzeitige Stand ist davon extrem weit entfernt.

Selbst ein Assembly-Code lässt ja noch Spielraum für "Korrektheit". Ein etwas langsamer laufendes Programm kann ja immer noch korrekt sein. Aber der Spielraum ist in diesem Fall wohl eher sehr klein.

Gast #7309450
Lesenswert?

Johann L. schrieb:

Gibt es eine Möglichkeit, der KI den Sprachstandard beizubringen? Dite Hardware-Beschreibung? Oder gibt es andere Möglichkeiten, dass der erzeugte Code "korrekt per Design" ist?

Da eine KI auch nur von jemanden programmiert wird, wird es nie eine echte KI geben. Der Begriff KI allein ist schon total bescheuert. Diese Selbstlernmechanismen sind vom Programmierer vorgegeben. Von ganz alleine kann eine KI nichts lernen. Außerdem werden noch unzählige Trainingsdaten hinterlegt. Wer weiß welche ungenügenden Trainingsdaten die Twitter KI hatte wo sie Bilder eines Raketenstarts falsch eingeordnet hatte. Was auch nur ein Beweis ist das eine KI einfach nur dumm ist und nur einen Abgleich machen kann. Nur was daran ist KI? Irgendwelche Differenzen bilden? Der Begriff KI muss für zu viel Mist herhalten. Wenn es eine wirklich echte KI gebe müßte man davor Angst haben, denn dann hat man keine Kontrolle darüber.

#7309454
Lesenswert?

Johann L. schrieb:

Gibt es eine Möglichkeit, der KI den Sprachstandard beizubringen? Dite Hardware-Beschreibung? Oder gibt es andere Möglichkeiten, dass der erzeugte Code "korrekt per Design" ist?

Ich könnte mir vorstellen, dass wir in nicht allzu ferner Zukunft einzelne Optimierersteps sehen werden, die mit "KI" / ML realisiert wurden. Wenn man so einen Optimierer von außen stark beschränkt und kontrolliert/plausibilisiert, dann könnte das eventuell nützlich sein. (Hardwarehersteller wie Intel/AMD sollen ja solche ML-basierten Optimierer für die Hardwareauslegung nutzen)

Auf der anderen Seite ist hier mit klassischen Methoden auch heute noch mehr rauszuholen.

Denn die Regel, dass man keine "KI" nutzen sollte, wenn das Problem sich klassisch lösen lässt, die gilt ja immer.

Gast #7309457
Lesenswert?

Kernigham & Ritchie schrieb:

Weiß jemand warum das so völlig verrückt ohne Logik festgelegt wurde? 0xFFFF >> HEX ist uint16_t 65535 >> DEZ ist int32_t

Vielleicht hatte sich der Erfinder dabei gedacht, dass Hex Zahlen bei ihm normalwerweise nie ein Vorzeichen haben, daher das "u". Und so passen sie in einen int16, also uint16.

In C ist vieles unlogisch. Das muss man einfach hinnehmen, oder in anderen Sprachen programmieren. Aber glaube mir: Auch da ist nicht alles für jeden Menschen logisch.

Es ist Aufgabe des Programmierers, die Brücke zwischen Mensch (ich will ...) und Maschine (ich kann ...) zu schlagen.

#7309463
Lesenswert?

Kernigham & Ritchie schrieb:

Weiß jemand warum das so völlig verrückt ohne Logik festgelegt wurde? 0xFFFF >> HEX ist uint16_t 65535 >> DEZ ist int32_t

Genau diese Konstellation erscheint mir eigentlich sehr sinnvoll. Wie oben schon geschrieben wurde, arbeitet man mit Hex-Werten eher in vorzeichenlosem Kontext, bei Dezimal-Werten ist es gut, wenn die Signedness nicht vom Wert abhängt (also z.B. 30000 signed und 40000 unsigned). Was mir nur recht willkürlich erscheint ist, dass bei Hex-Zahlen eben doch auch signed verwendet wird, denn 0x7FFF wäre z.B. int16_t.

#7309465
Lesenswert?

Kernigham & Ritchie schrieb:

Da eine KI auch nur von jemanden programmiert wird

Eine "KI", also ein Machine Learning Algorithmus ist im Kern recht einfach. Der Programmcode ist nicht das, wo die Leistung und die Innovation drin steckt.

Kernigham & Ritchie schrieb:

Diese Selbstlernmechanismen sind vom Programmierer vorgegeben

Ja. Die Mechanismen, Architektur, Hyperparameter und Datensätze geben die Grenzen des Möglichen vor.

Die eigentliche "Magie" findet dann aber im Lernprozess selbst statt. Dieser Prozess als Ganzes wird vom Menschen dann in der Regel nicht mehr komplett verstanden. Aus vielen Gründen. Darunter z.B. eine hohe Multidimentionalität, die man sich nicht mehr bildlich vorstellen kann. Es gibt dann Methoden, die einem dabei helfen zu verstehen, was die "KI" gelernt hat. Aber das ist alles auch nur sehr beschränkt und gezielt möglich. Nicht allgemein.

Die "KI" ist im eingelernten Zustand eben nicht von einem Menschen im Detail vorgegeben.

Das wird schon alleine dadurch klar, das zwei Netz-Instanzen mit ziemlich verschiedenen Gewichten ( = die Betriebsparameter) zu praktisch identischen Ergebnissen kommen können. Die Betriebsparameter der Netze starten in der Regel mit zufälligen Werten vor dem Trainingslauf. D.h. nach jedem Trainingslauf sehen auch die Parameter anders aus. Aber (!) die Gesamteigenschaften, also die Ausgaben, der Netze unterscheiden sich kaum (wenn man seine Hyperparameter, Architektur und so weiter im Griff hat).

Beitrag #7309474 wurde von einem Moderator gelöscht.
Gast #7309492
Lesenswert?

Rolf M. schrieb:

Kernigham & Ritchie schrieb:

Weiß jemand warum das so völlig verrückt ohne Logik festgelegt wurde? 0xFFFF >> HEX ist uint16_t 65535 >> DEZ ist int32_t

Genau diese Konstellation erscheint mir eigentlich sehr sinnvoll. Wie oben schon geschrieben wurde, arbeitet man mit Hex-Werten eher in vorzeichenlosem Kontext, bei Dezimal-Werten ist es gut, wenn die Signedness nicht vom Wert abhängt (also z.B. 30000 signed und 40000 unsigned). Was mir nur recht willkürlich erscheint ist, dass bei Hex-Zahlen eben doch auch signed verwendet wird, denn 0x7FFF wäre z.B. int16_t.

Soeben selbst getestet, stimmt. Bei sowas frage ich mich immer was das soll. Programmieren hat für mich sehr viel mit Logik zu tun. Das hier hat aber keine Logik mehr. Wenn man jeden Mist im Standard nachlesen muss, weil jetzt auch noch Wertebereiche unterschiedlich behandelt werden, dann macht das alles keinen Spass mehr. Das Ende vom Lied wird sein man castet alles. Ob das sinnvoll ist? Und dann wundert man sich das es so viele Programmfehler gibt die kaum entdeckt werden. Muss ein C/C++ Programmierer das Standard-Draft auswendig lernen?

Die Einzigste Erklärung die ich habe ist, weil int der Default Typ ist und 0x7FFF in ein int passst ist das noch int. Aber dann soll bitte schön alles gleich behandelt werden. Ob man octal, binär, dezimal oder hex schreibt sollte keinen Unterschied machen. Auf der einen Seite legt das Gremium irgendwelche (un)logischen Standards fest. Und auf der anderen Seite definieren sie UB auf alles worauf sie keine Lust haben. So kommt mir das mittlerweile vor.

Aber gut wieder was gelernt, auch wenn mich das langsam anko....

Danke auch an Stefan und MaWin O.

#7309502
Lesenswert?

Kernigham & Ritchie schrieb:

Muss ein C/C++ Programmierer das Standard-Draft auswendig lernen?

Ja. Das muss man eigentlich tun. Denn wenn man gegen den Standard verstößt, dann löst man oft genug UB aus und dann hat man verloren. Von den Compilerentwicklern bekommt man dann nur die Anweisung sich halt an den Standard zu halten.

Einer der vielen Gründe, weshalb C/C++ endlich ersetzt werden sollten.

Gast #7309510
Lesenswert?

MaWin O. schrieb:

Einer der vielen Gründe, weshalb C/C++ endlich ersetzt werden sollten.

Ganz genau. Sowas wie UB sollte es in einer Programmiersprache nicht geben. Das zu fordernde Minimum für eine moderne Programmiersprache ist, dass der Compiler sowas selber erkennt und eine Fehlermeldung produziert. Alles darunter ist vollkommen indiskutabel.

#7309517
Lesenswert?

c-hater schrieb:

Das zu fordernde Minimum für eine moderne Programmiersprache ist, dass der Compiler sowas selber erkennt und eine Fehlermeldung produziert.

UB muss es immer geben. Denn UB gibt es auch auf Hardwareebene. Eine komplett UB-freie Sprache, die keinen "Notausgang" aus dieser Situation bietet, kann kein Ersatz für C/C++ sein.

Aber eine moderne Programmiersprache sollte UB auf das absolut Notwendigste einschränken. z.B. indem Bereiche, die ein Compiler nicht als UB-frei beweisen kann, entsprechend vom Programmierer deutlich markiert werden müssen.

Dass in C/C++ an jeder Ecke die UB lauern kann, wenn man den Standard nicht auswendig kennt, ist einfach nicht mehr tragbar.

Gast #7309599
Lesenswert?

MaWin O. schrieb:

UB muss es immer geben. Denn UB gibt es auch auf Hardwareebene.

Ja, sicher. Dort sind sie aber dokumentiert und es gibt mindestens einen Weg, zu einem defined behavior zu gelangen.

Außerdem spielt das im Kontext einer Hochsprache keine Rolle (sollte es zumindest nicht spielen). Denn der einzige Sinn einer Hochsprache ist, solche Details der Hardware zu abstrahieren. Kann sie das nicht, taugt sie nix.

#7309604
Lesenswert?

c-hater schrieb:

Ja, sicher. Dort sind sie aber dokumentiert und es gibt mindestens einen Weg, zu einem defined behavior zu gelangen.

Das ist bei C/C++ auch ganz genau so. Und das ist genau das Problem. Wer sich nicht an die Doku hält, wird nicht freundlich ermahnt, sondern streng abgestraft.

c-hater schrieb:

Denn der einzige Sinn einer Hochsprache ist, solche Details der Hardware zu abstrahieren. Kann sie das nicht, taugt sie nix.

Es gibt natürlich viele Sprachen ohne UB. Aber die meisten davon sind nicht für hardwarenahe Software verwendbar und sind somit kein Ersatz für C/C++. Wer nicht mit Rohzeigern hantieren kann, kann nicht auf Hardware zugreifen. Wer nicht mit handkodiertem ASM kommunizieren kann, kann keines der notwendigen Basisprimitive implementieren. etc, etc...

Eine Sprache komplett ohne UB und ohne eine explizite Möglichkeit diesen garantierten kein-UB-Regeln zu entkommen, kann kein Ersatz für C/C++ sein.

Gast #7309615
Lesenswert?

MaWin O. schrieb:

Es gibt natürlich viele Sprachen ohne UB. Aber die meisten davon sind nicht für hardwarenahe Software verwendbar und sind somit kein Ersatz für C/C++.

OK. Das Problem ist also im Kern, dass C/C++ die Eierlegende Wollmilchsau sein soll. (und damit naturgemäß scheitert)

Genau so nehme ich das auch: hardwarenahes passiert in Asm, alles darüber mit RICHTIGEN Hochsprachen. No need for C/C++.

Wer nicht mit Rohzeigern hantieren kann, kann nicht auf Hardware zugreifen. Wer nicht mit handkodiertem ASM kommunizieren kann, kann keines der notwendigen Basisprimitive implementieren.

All das geht auch mit echten Hochsprachen. Aus der unsäglichen (aber leider auch nicht mehr änderbaren) Historie resultiert nur, dass man dafür typisch das C-ABI benutzen muß, auch wenn kein bissel C-Code involviert ist...

It's a shame...

#7309652
Lesenswert?

c-hater schrieb:

Wer nicht mit Rohzeigern hantieren kann, kann nicht auf Hardware zugreifen. Wer nicht mit handkodiertem ASM kommunizieren kann, kann keines der notwendigen Basisprimitive implementieren.

All das geht auch mit echten Hochsprachen.

Aha. Wie gehen denn Rohpointer garantiert ohne UB? Und wie führe ich Asm-Code so aus, dass der Compiler die Abwesenheit von UB gleich erkennt und ich ihm das nicht sagen muss?

Richtig. Beides geht nicht.

Deshalb braucht ein C/C++ Ersatz immer die Möglichkeit die UB-Freiheit stellenweise, dort wo es notwendig ist, vom Programmierer attestieren zu lassen. (Also das, was der Programmierer in C/C++ für das gesamte Programm tun muss) An diesen Stellen kann der Compiler prinzipbedingt die Abwesenheit von UB nicht beweisen. Somit sind die Stellen nicht garantiert UB-frei.

#7309653
Lesenswert?

Kernigham & Ritchie schrieb:

Und dann wundert man sich das es so viele Programmfehler gibt die kaum entdeckt werden.

Nö, ich wundere mich nicht. Wem float zu einfach ist, der muß eben jeden einzelnen Rechenschritt darauf prüfen, daß das gewählte Ganzzahlformat nicht überschritten wird. Auch das Ganzzahlen in C per default signed sind, ist auch nicht so schwer zu merken.

Gast #7309666
Lesenswert?

Tja, und so kommt es, dass z.B. Windows vom ersten Tag bis heute gespickt ist mit Bugs (und täglich werden es mehr), die die Mitarbeiter von Microsoft nicht in den Griff bekommen, weil jeder eine andere Vorstellung vom Programmieren hat und einen anderen Erfahrungs- und Wissenstand, u.s.w.

Es muss doch völlig klar sein, dass die Menge der unbehobenen Bugs in "professioneller" Software proportional zur Menge des Source-Codes inkl. Libraries ist.

Gast #7309668
Lesenswert?

Peter D. schrieb:

Kernigham & Ritchie schrieb:

Und dann wundert man sich das es so viele Programmfehler gibt die kaum entdeckt werden.

Nö, ich wundere mich nicht. Wem float zu einfach ist, der muß eben jeden einzelnen Rechenschritt darauf prüfen, daß das gewählte Ganzzahlformat nicht überschritten wird.

Alles pauschal mit float zu erschlagen ist noch dümmer. Außerdem ging es nicht um eigene Variablendefinitionen.

Auch das Ganzzahlen in C per default signed sind, ist auch nicht so schwer zu merken.

Das ist und war nicht das Thema. Bitte nochmal lesen.

#7309676
Lesenswert?

Justin S. schrieb:

Es muss doch völlig klar sein, dass die Menge der unbehobenen Bugs in "professioneller" Software proportional zur Menge des Source-Codes inkl. Libraries ist.

Wieso sollte das völlig klar sein? Das würde ja bedeuten, dass (1) alte Bugs nie behoben werden und (2) neuer Code in etwa immer die gleiche Bugdichte hat.

Bei (2) gehe ich mit, aber (1) ist doch Unsinn.

Gast #7309696
Lesenswert?

Johann L. schrieb:

MaWin O. schrieb:

Dass in C/C++ an jeder Ecke die UB lauern kann, wenn man den Standard nicht auswendig kennt, ist einfach nicht mehr tragbar.

UB in C geht ja noch, aber in C++ gibt es ein neues Leckerli:

"Ill-formed, [no] diagnostics required"

Wenn man das liest wird es immer mehr Multikulti . https://stackoverflow.com/questions/22180312/difference-between-undefined-behavior-and-ill-formed-no-diagnostic-message-requ

Einerseits wird alles haarklein festgelegt, Standardantwort ist "lies den Standard" und alles andere lässt man komplett offen. Nur was hat der Programmierer von komplett offen? Man lässt den Programmierer im Stich. Man sollte bei den Compilern bzw. in den Standard mehr Energie reinstecken das weniger UB definiert ist anstatt immer neue Sprachfeatures aus den Fingern zu saugen. Hat sich schon jemand gefragt wo das noch hinführen soll mit dem C++? Es ist doch mittlerweile so das kaum noch jemand die neuen Sprachfeatures versteht geschweige den anwenden kann. Man müßte erstmal aufräumen anstatt immer höher schneller weiter ausbauen. Ich habe grundsätzlich nichts gegen C++ aber die Entwicklung stimmt nachdenklich und die Compilerqualität wird nicht besser.

#7309706
Lesenswert?

Kernigham & Ritchie schrieb:

in den Standard mehr Energie reinstecken das weniger UB definiert ist

Das ist aber aus Rückwärtskompatibilitätsgründen kaum möglich. Die Primitive um vernünftig, modern und garantiert UB-frei zu programmieren, sind in C++ ja eigentlich fast alle vorhanden. Nur der Compiler zwingt den Programmierer nicht sie zu nutzen. Und er kann es auch gar nicht, ohne die Rückwärtskompatibilität zu brechen.

C++ ist hier in einer Sackgasse.

Gast #7309711
Lesenswert?

MaWin O. schrieb:

Justin S. schrieb:

Es muss doch völlig klar sein, dass die Menge der unbehobenen Bugs in "professioneller" Software proportional zur Menge des Source-Codes inkl. Libraries ist.

Wieso sollte das völlig klar sein? Das würde ja bedeuten, dass (1) alte Bugs nie behoben werden und (2) neuer Code in etwa immer die gleiche Bugdichte hat.

Bei (2) gehe ich mit, aber (1) ist doch Unsinn.

Dabei muss ich Justin recht geben. Je mehr Codezeilen umso mehr potentielle Fehler können enthalten sein. Überlege mal wieviel Programmzeilen man pro Tag so schreibt und wieviel Zeit für Testen und Debuggen verplempert wird.

#7309712
Lesenswert?

El Capitan schrieb:

darüber mit RICHTIGEN Hochsprachen.

Welche nutzt Du für AVR/ESP/STM32?

Ich vermeide das böse Wort hier ganz bewusst, weil sonst die Trolle wieder aus den Löchern geschossen kommen. :)

Ja, es gibt mindestens eine moderne Hochsprache ohne UB (und mit expliziter UB-Freischaltung) für AVR/ESP/STM32. Mehr sage ich dazu in diesem Thread nicht. Dazu gibt es schon einen in "PC-Programmierung" (Ja, ist das falsche Forum. Der Thread hat sich halt so entwickelt). Beitrag "Rust - ist das hier um zu bleiben?"

#7309716
Lesenswert?

Kernigham & Ritchie schrieb:

Dabei muss ich Justin recht geben. Je mehr Codezeilen umso mehr potentielle Fehler können enthalten sein.

Das ist ja auch grundsätzlich richtig. Aber Bugs werden auch behoben. Also sinkt die Anzahl der Bugs in einem Programm, das (angenommen) keinen neuen Code erhält stetig. Das heißt, dass die Anzahl der Fehler eben nicht proportional zu den Codezeilen ist, sondern eher umgekehrt-proportional zu deren Alter.

Gast #7309729
Lesenswert?

von MaWin O. (mawin_original)

Kernigham & Ritchie schrieb:

Da eine KI auch nur von jemanden programmiert wird

Eine "KI", also ein Machine Learning Algorithmus ist im Kern recht einfach. Der Programmcode ist nicht das, wo die Leistung und die Innovation drin steckt.

Das sehe ich ganz anders. Die Architekturen der Netze können sehr komplex werden und erfordern ein langes Design mit menschlicher Unterstützung. Google ist dabei, auch das zu automatisieren:

https://ai.googleblog.com/2017/05/using-machine-learning-to-explore.html

#7309751
Lesenswert?

c-hater schrieb:

MaWin O. schrieb:

Einer der vielen Gründe, weshalb C/C++ endlich ersetzt werden sollten.

Ganz genau. Sowas wie UB sollte es in einer Programmiersprache nicht geben. Das zu fordernde Minimum für eine moderne Programmiersprache ist, dass der Compiler sowas selber erkennt und eine Fehlermeldung produziert. Alles darunter ist vollkommen indiskutabel.

Bei UB geht es um Situationen, die der Compiler nicht immer erkennen kann, weil sie oft erst zur Laufzeit entstehen. Könnte der Compiler alle Fehler immer zur Compilezeit erkennen, dann gäbe es auch in C kein UB. Wenn ich den Benutzer zwei Zahlen eingeben lasse und die dann dividiere, weiß der Compiler nicht, dass der Benutzer später mal als zweite Zahl eine 0 eingeben wird. Da gibt es eben zwei Möglichkeiten, damit umzugehen: Entweder der Compiler baut einen Laufzeitcheck ein, der vor jeder Division prüft, ob der Divisor 0 ist und dann das Programm terminiert oder eine Exception wirft oder was auch immer, oder man akzeptiert eben, dass das UB ist. Bei C hat man sich aus Effizienzgründen für zweiteres entschieden, wobei das dem Compiler nicht verbietet, ersteres trotzdem zu tun, aber es zwingt ihn eben auch nicht dazu.

#7309759
Lesenswert?

Rolf M. schrieb:

Entweder der Compiler baut einen Laufzeitcheck ein, der vor jeder Division prüft, ob der Divisor 0 ist und dann das Programm terminiert oder eine Exception wirft oder was auch immer, oder man akzeptiert eben, dass das UB ist.

Es gibt natürlich noch eine dritte Möglichkeit.

Wenn der Compiler beweisen kann, dass der Divisor nicht 0 werden kann, braucht er auch keinen Check und es ist auch kein UB.

Dazu gibt es dann für nicht-triviale Fälle den Non-Zero-Integer, mit dem der Programmierer dem Compiler beim Programmverständnis unter die Arme greifen kann.

Gast #7309774
Lesenswert?

Je mehr Codezeilen, je mehr Fehler

Das ist sogar schon durch wissenschaftliche Studien belegt.

Bug-Beseitigung

Gerade in kommerzieller Software werden Bugs oft nicht beseitigt, sondern "abgefangen", d.h. für den speziellen Fall wird zusätzlicher Code hinzugefügt.

M.E. sind die riesigen Frameworks ein Teil des Problems, die können doch gar nicht fehlerfrei sein. Dann haben wir noch eine Inflation an neuen Features und schließlich noch die Kurzlebigkeit der Produkte. Betriebssysteme "leben" heute nur noch 18 Monate, Programmiersprachen wie JAVA gar nur noch 6 Monate. Von .NET gibt es schon x-Versionen, wobei selbst für 2.x noch Updates kommen (immerhin). Sogar vor der Hardware macht das nicht halt: auch die großen Hersteller können heute eine Verfügbarkeit von einem Jahr nicht mehr garantieren. Da können Systeme doch gar nicht mehr "ausreifen". Und weil man sich der alten BWLer-Formel "nicht die Guten fressen die Schlechten, sondern die Schnellen die langsamen" nicht entziehen kann, läßt sich das auch gar nicht ändern.

Und wenn ich sehe, wie meine Kollegen Software entwickeln, dann wir mir ohnehin schwummerig. Daß alles mit copy&paste zusammengeschustert wird, geht ja noch. Aber teilweise werden Funktionen/Module/Bibliotheken verwendet ohne den geringsten Versuch, deren Funktionsweise verstehen zu wollen. Es reicht wenn mit den Testdaten das gewünschte Ergebnis erzielt wird. Paßt schon.

Gast #7309777
Lesenswert?

@ Rolf Bei dem Division 0 Bsp. gebe ich dir recht. Kann erst zur Laufzeit überprüft werden und ist damit für den Compiler schwer zu handhaben. Ein anderes negativ Bsp. für den Standard sind signed Überläufe auf UB Status zu belassen. Das macht im Vergleich zu unsigned Überläufen keinen Sinn. Wenn unsigned von 0 (min) auf max und umgekehrt springen kann, dann sollte auch signed von min auf max und umgekehrt springen können. Das erwartet die Programmiererlogik wenn man das unsigned Überlaufverhalten kennt.

#7309788
Lesenswert?

kleiner Admin schrieb:

Gerade in kommerzieller Software werden Bugs oft nicht beseitigt,

bla bla bla

Hast du auch Belege für deine ganzen Behauptungen? In meiner Realität stimmt das alles nicht.

Software wird im Mittel immer komplexer und immer besser/fehlerfreier. Das ist meine Wahrnehmung. Und das lässt sich auch durch die zunehmende Anzahl und Qualität der Entwicklungstools begründen (checker, linter, compiler, etc...).

Dass Hersteller keine Updates liefern ist doch auch kompletter Unsinn.

In Zukunft wird es trotzdem noch einige Verbesserungssprünge geben (müssen). Aber wenn ich heutige Software mit dem Schrott aus den 90ern vergleiche, dann bin ich ganz froh im Jetzt zu leben. Auch wenn heutige Software bei Weitem noch nicht perfekt und so fehlerfrei ist, wie ich mir das wünschen würde.

Gast #7309790
Lesenswert?

MaWin O. schrieb:

Software wird im Mittel immer komplexer und immer besser/fehlerfreier. Das ist meine Wahrnehmung.

Denke nur mal daran, wie oft man unter Windows 95 den Bluescreen bekam, und wie oft heute. Zumindest in dieser Hinsicht wurde Windows erheblich besser, trotz der 100 Fachen Größe.

Gast #7309806
Lesenswert?

Dirk B. schrieb:

Liegt das nur am Windows oder sind die Anwenderprogramme auch besser geworden.

Windows wurde in der Hinsicht mit XP deutlich besser.

Modernere CPUs, OS/2 und seine Weiterentwicklung Da ist die Verbesserung zu finden In Hauptsache: Speicherschutz und preemptives Multitasking

#7309811
Lesenswert?

Ich denke, dass man für das Thema undefined behavior nicht so ein großes Fass aufmachen muss. Natürlich ist es an ein paar Ecken ärgerlich. Meistens liegt man dann aber in Grenzbereichen, in denen man sich eigentlich ohnehin zusätzliche Gedanken darüber machen müsste, was man hier tut.

Der originale Fragesteller (der sich schon längst verabschiedet hat) ist offensichtlich ein Anfänger der wild Datentypen gemischt hat und scheinbar nicht die geringste Aufmerksamkeit in das Thema Wertbereich gesteckt hat. Wenn sich ein Neuling ungeschickt mit der Kettensäge anstellt, fließt nun mal Blut. In Java gibt es viel weniger UB - aber dass an dieser Stelle wrap around definiert ist, würde dem fehlerhaften Programm kein Stück weiterhelfen. Aus der Sicht des Regelkreises wäre ein INT_MAX beim overflow vermutlich hilfreicher - aber das kostet Performance wenn es nicht durch die Hardware unterstützt wird. Gibt es vielleicht bei DSPs?

99% der Probleme lassen sich vermeiden, indem man warnings beachtet und im Prinzip wie Fehler behandelt. Es gibt seltene Situationen, in denen die warning nicht angemessen ist und auch nicht leicht behoben werden kann. Dann kann man meiner Meinung nach auch mal eine Ausnahme machen und die warning stehen lassen (natürlich gut Kommentiert damit andere Entwickler nicht darüber stolpern). Ich könnte aber aus dem Stehgreif noch nicht mal ein gutes Beispiel liefern, so selten sehe ich das Problem.

#7309823
Lesenswert?

Matthias T. schrieb:

Ich denke, dass man für das Thema undefined behavior nicht so ein großes Fass aufmachen muss. Natürlich ist es an ein paar Ecken ärgerlich.

Es ist die Ursache für den Großteil der Sicherheitslücken, die ständig gefunden werden.

Das halte ich für etwas mehr als nur "ärgerlich".

In Java gibt es viel weniger UB

Bin kein Java-Experte, aber in Java gibt es doch gar kein UB, oder nicht?

dass an dieser Stelle wrap around definiert ist, würde dem fehlerhaften Programm kein Stück weiterhelfen.

Doch, es hilft enorm. Die Abwesenheit von UB transformiert nämlich ein "Der Compiler macht einfach irgendwas" zu einem "Der Compiler macht das, was definiert ist". Nach der Ausführung von UB ist der komplette restliche Programmablauf undefiniert. Und moderne C-Compiler nutzen diese Eigenschaft massiv aus, um den Code zu optimieren.

99% der Probleme lassen sich vermeiden, indem man warnings beachtet und im Prinzip wie Fehler behandelt.

Das stimmt nicht. Die Bug-Statistiken sprechen eine andere Sprache.

In C können nur sehr wenige - praktisch keine - UBs zur Compilezeit abgefangen werden.

#7309861
Lesenswert?

Matthias T. schrieb:

Ich denke, dass man für das Thema undefined behavior nicht so ein großes Fass aufmachen muss. Natürlich ist es an ein paar Ecken ärgerlich. Meistens liegt man dann aber in Grenzbereichen, in denen man sich eigentlich ohnehin zusätzliche Gedanken darüber machen müsste, was man hier tut.

Ja. Bei dem Beispiel der Division durch 0 muss man als Entwickler eben seinen Code so schreiben, dass sie nicht vorkommen kann. Wenn die Zahl also vom Benutzer kommt, baut man einen Check ein. Das UB kommt erst ins Spiel, wenn der Entwickler das versäumt hat.

99% der Probleme lassen sich vermeiden, indem man warnings beachtet und im Prinzip wie Fehler behandelt. Es gibt seltene Situationen, in denen die warning nicht angemessen ist und auch nicht leicht behoben werden kann. Dann kann man meiner Meinung nach auch mal eine Ausnahme machen und die warning stehen lassen (natürlich gut Kommentiert damit andere Entwickler nicht darüber stolpern).

Ich mache meinen Code komplett warnungsfrei, denn zwischen "unwichtigen" Warnungen gehen die, die kritisch sind, gerne mal unter.

Ich könnte aber aus dem Stehgreif noch nicht mal ein gutes Beispiel liefern, so selten sehe ich das Problem.

Leider warnt gcc heute auch immer öfter bei stilistischen Dingen, die für sich erst mal kein Problem sind, und da sind manchmal Sachen dabei, wo ich anderer Meinung bin. Dann beiße ich eben in den sauren Apfel und schreibe was unnütiges hin, damit der Compiler glücklich ist. (Übrigens: Es heißt "Stegreif". Das Wort hat weder mit stehen, noch mit greifen etwas zu tun).

MaWin O. schrieb:

Matthias T. schrieb:

Ich denke, dass man für das Thema undefined behavior nicht so ein großes Fass aufmachen muss. Natürlich ist es an ein paar Ecken ärgerlich.

Es ist die Ursache für den Großteil der Sicherheitslücken, die ständig gefunden werden.

Da verwechselst du die Ursache mit der Wirkung. Die Ursache sind Fehler im Programm, die Wirkung ist, dass dessen Verhalten dadurch undefiniert wird. Und das kann im schlimmsten Fall ein Sicherheitsproblem sein.

In Java gibt es viel weniger UB

Bin kein Java-Experte, aber in Java gibt es doch gar kein UB, oder nicht?

Soweit ich weiß. Dafür sind dort dann eben die oben genannten Laufzeitchecks enthalten und ein Fehler führt zu einer Exception, die dann das Programm terminiert. Man kann sie natürlich auch fangen, aber was soll man schon sinnvolles tun, wenn die geworfen wurde, weil irgendwo im Programm ein Fehler ist, mit dem dessen Programmierer natürlich nicht gerechnet hat?

99% der Probleme lassen sich vermeiden, indem man warnings beachtet und im Prinzip wie Fehler behandelt.

Das stimmt nicht. Die Bug-Statistiken sprechen eine andere Sprache.

Zumindest bei den Dingen, die zur Compilezeit erkannt werden können, dürfen natürlich vom Compiler auch Warnungen oder Fehlermeldungen ausgegeben werden, denn auch das ist eine gültige Instanz von "undefined behavior". Das ist dann aber keine Sache des Standards, sondern der quality of implementation. Wie oben schon gesagt: Der Standard verbietet es nicht, erzwingt es aber auch nicht.

In C können nur sehr wenige - praktisch keine - UBs zur Compilezeit abgefangen werden.

Naja, da gibt's schon ein paar, und da warnt ein gcc auch meistens.

#7309866
Lesenswert?

Rolf M. schrieb:

Da verwechselst du die Ursache mit der Wirkung. Die Ursache sind Fehler im Programm, die Wirkung ist, dass dessen Verhalten dadurch undefiniert wird.

Nein, ich verwechsele nichts.

Eine Programmiersprache, die kein UB hat, kann kein undefiniertes Verhalten hervorrufen. Egal, was der Programmierer sich zusammenprogrammiert. Und damit können diese Bugs nicht mehr derart eskalieren. Sie sind dann maximal noch DoS-tauglich.

Rolf M. schrieb:

Ja. Bei der Division durch 0 muss man als Entwickler eben seinen Code so schreiben, dass sie nicht vorkommen kann. Wenn die Zahl also vom Benutzer kommt, baut man einen Check ein. Das UB kommt erst ins Spiel, wenn der Entwickler das versäumt hat.

Ja. "man muss es nur richtig machen". Ein Konzept, dass jetzt Jahrzehnte erprobt ist und nicht funktioniert. Wir brauchen ein besseres Konzept, was diese Bugs sicher verhindert.

#7309870
Lesenswert?

MaWin O. schrieb:

Rolf M. schrieb:

Da verwechselst du die Ursache mit der Wirkung. Die Ursache sind Fehler im Programm, die Wirkung ist, dass dessen Verhalten dadurch undefiniert wird.

Nein, ich verwechsele nichts.

Doch, schon.

Eine Programmiersprache, die kein UB hat, kann kein undefiniertes Verhalten hervorrufen. Egal, was der Programmierer sich zusammenprogrammiert.

Genau. Dann ist dort die Wirkung eben eine andere, z.B. Terminierung des Programms statt UB.

Rolf M. schrieb:

Ja. Bei der Division durch 0 muss man als Entwickler eben seinen Code so schreiben, dass sie nicht vorkommen kann. Wenn die Zahl also vom Benutzer kommt, baut man einen Check ein. Das UB kommt erst ins Spiel, wenn der Entwickler das versäumt hat.

Ja. "man muss es nur richtig machen". Ein Konzept, dass jetzt Jahrzehnte erprobt ist und nicht funktioniert. Wir brauchen ein besseres Konzept, was diese Bugs sicher verhindert.

Es verhindert nicht die Bugs, sondern ändert wie schon gesagt nur ihre Auswirkung. Damit will ich nicht sagen, dass das nicht von Vorteil wäre, sondern nur, dass dann immer noch ein Bug im Programm ist.

MaWin O. schrieb:

Es gibt natürlich noch eine dritte Möglichkeit.

Wenn der Compiler beweisen kann, dass der Divisor nicht 0 werden kann, braucht er auch keinen Check und es ist auch kein UB.

Aber es geht ja gerade um den Fall, in dem der Compiler das nicht beweisen kann. Damit muss er irgendwie umgehen.

#7309877
Lesenswert?

Rolf M. schrieb:

Doch, schon.

Ach komm. Das kann doch nicht dein Ernst sein. Ich weiß doch wohl selbst besser als du, was ich meine.

Es verhindert nicht die Bugs, sondern ändert wie schon gesagt nur ihre Auswirkung. Damit will ich nicht sagen, dass das nicht von Vorteil wäre, sondern nur, dass dann immer noch ein Bug im Programm ist.

Nein, das ist so allgemein gesagt nicht richtig. In vernünftigen modernen Sprachen sind sehr viele Dinge, die in C UB sind, einfach Compilerfehler. Es verhindert also die Bugs.

Nur bei den wenigen Dingen die zur Laufzeit geprüft werden, sind die Bugs weiterhin formell im Programm. Aber massiv entschärft.

Aber es geht ja gerade um den Fall, in dem der Compiler das nicht beweisen kann. Damit muss er irgendwie umgehen.

Das heißt aber noch lange nicht, dass er bei jeder Division die Prüfung machen muss. Und das wurde ja hier behauptet. Es ist falsch.

#7309887
Lesenswert?

MaWin O. schrieb:

Matthias T. schrieb:

Ich denke, dass man für das Thema undefined behavior nicht so ein großes Fass aufmachen muss. Natürlich ist es an ein paar Ecken ärgerlich.

Es ist die Ursache für den Großteil der Sicherheitslücken, die ständig gefunden werden.

Ich habe keine belastbaren Statistiken zur Hand. Aber ich denke, dass die manuelle Speicherverwaltung (Array Überlauf, use after free etc.) in der Praxis ein viel größeres Problem darstellt.

In Java gibt es viel weniger UB

Bin kein Java-Experte, aber in Java gibt es doch gar kein UB, oder nicht?

Im Zusammenhang mit multi threading und bei einzelnen Libs gibt UB auch in Java. Und zum Teil wird es auch durch Haarspalterei "getarnt" - dann heißt es eben "implementation defined behavior". Für den Anwender läuft es auf das gleiche hinaus, das Programmverhalten kann sich von Umgebung zu Umgebung deutlich ändern. Ist aber in Java recht selten.

dass an dieser Stelle wrap around definiert ist, würde dem fehlerhaften Programm kein Stück weiterhelfen.

Doch, es hilft enorm. Die Abwesenheit von UB transformiert nämlich ein "Der Compiler macht einfach irgendwas" zu einem "Der Compiler macht das, was definiert ist". Nach der Ausführung von UB ist der komplette restliche Programmablauf undefiniert.

Der Standard sagt (frei formuliert), dass der Compiler bei UB auch rosa Einhörner produzieren darf. Das heißt aber nicht, dass der Compiler das auch tut. Die meisten Compiler versuchten das zu tun, was am vernünftigsten erscheint (dem Compilerbauer, das muss sich nicht mit der Erwartungshaltung des Entwicklers decken). Und insbesondere kenne ich keinen Compiler, der mal so und mal so arbeitet. Beim Integer Überlauf in Berechnungen machen alle einen wrap around (wie bei Java definiert - also kein Unterschied im Verhalten).

Ein Laie mag es vielleicht überraschend finden, wenn der Compiler eine if-Entscheidung mit einem unsigned int gegen eine negative Zahl einfach wegoptimiert. Aber es ist nun mal ein bug im Programm, nicht im Compiler. Hier sind wir wieder beim Thema "warnings ernst nehmen!".

In C können nur sehr wenige - praktisch keine - UBs zur Compilezeit abgefangen werden.

Ich bin auch kein großer Fan von C. Aber man muss eben auch Zugeständnisse an die Entstehungsgeschichte machen und das Thema Kompatibilität beachten. Jedes hinreichend alte Produkt schleppt eine Reihe von Altlasten mit sich rum. Und wie bereits jemand anderes geschrieben hat - wie willst Du Deinen AVR (synonym für kleine Microcontroller) anders programmieren als mit C oder C++? Man kann Rust nehmen (wenn man es denn mag). Ich habe auch über ADA (Sparc) nachgedacht. Aber beide Umgebungen leiden dann wieder unter eingeschränkten Libraries. Und ich mag nicht ständig das Rad neu erfinden. Für mich ist es aber auch nur Hobby, beruflich arbeite ich nicht mit Microcontrollern.

#7309895
Lesenswert?

Matthias T. schrieb:

Ich habe keine belastbaren Statistiken zur Hand. Aber ich denke, dass die manuelle Speicherverwaltung (Array Überlauf, use after free etc.) in der Praxis ein viel größeres Problem darstellt.

Das ist alles UB. UB zu verhindern, verhindert also auch diese Dinge.

Matthias T. schrieb:

Im Zusammenhang mit multi threading und bei einzelnen Libs gibt UB auch in Java. Und zum Teil wird es auch durch Haarspalterei "getarnt" - dann heißt es eben "implementation defined behavior". Für den Anwender läuft es auf das gleiche hinaus, das Programmverhalten kann sich von Umgebung zu Umgebung deutlich ändern.

Was nicht heißt, dass es UB ist.

Der Standard sagt (frei formuliert), dass der Compiler bei UB auch rosa Einhörner produzieren darf. Das heißt aber nicht, dass der Compiler das auch tut.

Doch, das heißt es. Sehr oft. Die Bugtracker sind voll mit solchen Reports, die als invalid geschlossen werden.

Und insbesondere kenne ich keinen Compiler, der mal so und mal so arbeitet.

Das habe ich nicht behauptet.

Beim Integer Überlauf in Berechnungen machen alle einen wrap around

Das bei gängigen C-Compilern definitiv nicht so. Die nutzen signed-overflow-UB aktiv aus um zu optimieren.

Jedes hinreichend alte Produkt schleppt eine Reihe von Altlasten mit sich rum.

Ja. Das ist aber kein Grund es nicht besser zu machen.

wie willst Du Deinen AVR (synonym für kleine Microcontroller) anders programmieren als mit C oder C++? Man kann Rust nehmen (wenn man es denn mag).

Zum Beispiel.

Für mich ist es aber auch nur Hobby

Ja gut. Für mich nicht.

Gast #7309897
Lesenswert?

Rolf M. schrieb:

Leider warnt gcc heute auch immer öfter bei stilistischen Dingen, die für sich erst mal kein Problem sind, und da sind manchmal Sachen dabei, wo ich anderer Meinung bin. Dann beiße ich eben in den sauren Apfel und schreibe was unnütiges hin, damit der Compiler glücklich ist.

Mittlerweile unterdrücke ich Warnungen sogar. Hier die am meisten nerven: -pedantic (für mich nicht relevant, da nur GCC in Verwendung) -Wno-volatile (denn ich weiß, dass |= nicht atomar ist)

#7309910
Lesenswert?

MaWin O. schrieb:

Beim Integer Überlauf in Berechnungen machen alle einen wrap around

Das bei gängigen C-Compilern definitiv nicht so. Die nutzen signed-overflow-UB aktiv aus um zu optimieren.

Es ist mir nicht klar, was Du damit meinst? Der overflow in Berechnungen kann im Allgemeinen zur Compile-Zeit noch nicht erkannt werden. Die Hardware macht ein wrap around, das Overflow Flag wird von C genauso wie von Java einfach ignoriert. Und von Rust - wenn man nicht im debug mode ist - scheinbar genauso:

"When the programmer has enabled debug_assert! assertions (for example, by enabling a non-optimized build), implementations must insert dynamic checks that panic on overflow. Other kinds of builds may result in panics or silently wrapped values on overflow, at the implementation's discretion."

Jedes hinreichend alte Produkt schleppt eine Reihe von Altlasten mit sich rum.

Ja. Das ist aber kein Grund es nicht besser zu machen.

Das ist manchmal nicht einfach wenn man die Anwender mitnehmen will. Sieht Phyton 2 nach 3. Selbst kleine Änderungen/ Verbesserungen können dazu führen, dass viele Anwender nicht umsteigen können oder wollen.

Mir gefällt in Java auch viel besser, dass es kaum Probleme mit der Speicherverwaltung gibt. Aber es kommt halt auch mit einem Preis. Ja nach Situation kann es akzeptabel sein oder nicht.

#7309928
Lesenswert?

Matthias T. schrieb:

Es ist mir nicht klar, was Du damit meinst? Der overflow in Berechnungen kann im Allgemeinen zur Compile-Zeit noch nicht erkannt werden.

Es wird vom Compiler angenommen, dass kein overflow vorkommt. Das führt dann zum Beispiel dazu, dass Code eliminiert wird, der unter dieser Annahme niemals ausgeführt werden könnte.

Wenn es dann zur Laufzeit aber doch zu einem overflow kommt, dann fehlt der behandelnde Code.

Und das ist nur eine von vielen möglichen Optimierungen.

Das ist manchmal nicht einfach

Das hat ja auch niemand gesagt. Aber die Probleme, die UB bereitet, sind aber auch massiv. Da kann man sich schon einmal eine nicht-einfache Lösung anschauen.

Mir gefällt in Java auch viel besser, dass es kaum Probleme mit der Speicherverwaltung gibt. Aber es kommt halt auch mit einem Preis.

In Java schon. In anderen sicheren Sprachen aber nicht.

Zum Beispiel use-after-free und Objektlebenszeiten können in anderen Sprachen zur Compilezeit geprüft werden.

Markus F. schrieb:

Das ist eine ziemlich treffende Beschreibung, wie Ada die Problematik behandelt.

Ja. Und viele andere Sprachen. Es sollen ja nur die schlechten Konzepte über Bord geworfen werden. Auch in C gibt es ja gute Dinge. Die sollen ja weiterleben.

#7309931
Lesenswert?

Matthias T. schrieb:

MaWin O. schrieb:

Beim Integer Überlauf in Berechnungen machen alle einen wrap around

Das bei gängigen C-Compilern definitiv nicht so. Die nutzen signed-overflow-UB aktiv aus um zu optimieren.

Es ist mir nicht klar, was Du damit meinst? Der overflow in Berechnungen kann im Allgemeinen zur Compile-Zeit noch nicht erkannt werden. Die Hardware macht ein wrap around, das Overflow Flag wird von C genauso wie von Java einfach ignoriert.

Der Compiler darf alles machen, was er will unter der Annahme, dass ein Overflow nicht stattfindet. Da kann ein Wert auch mal größer werden als es der Typ eigentlich zulässt, z.B. weil ein ARM-Prozessor nicht in 8 oder 16 Bit rechnen kann, sondern nur in 32 Bit. Der Compiler müsste dann nach der Berechnung Zwischenwerte entweder maskieren, um den Wertebereich zu begrenzen oder den Wert in den Speicher schreiben und zurück lesen. Wenn er aber davon ausgehen darf, dass der Wertebereich nie verlassen wird, kann er diese zusätzliche Aktion weglassen. Das ist nur eine mögliche Optimierung, die zu einem anderen als dem von dir erwarteten Verhalten führt. Da gibt es noch ganz andere Sachen.

Mir gefällt in Java auch viel besser, dass es kaum Probleme mit der Speicherverwaltung gibt.

Dafür dann mit der Verwaltung anderer Ressourcen, weil die von der Speicherverwaltung abgekoppelt ist.

MaWin O. schrieb:

Rolf M. schrieb:

Doch, schon.

Ach komm. Das kann doch nicht dein Ernst sein. Ich weiß doch wohl selbst besser als du, was ich meine.

So ist das eben. Wenn man etwas verwechselt, weiß man nicht immer, dass man das gerade tut.

Es verhindert nicht die Bugs, sondern ändert wie schon gesagt nur ihre Auswirkung. Damit will ich nicht sagen, dass das nicht von Vorteil wäre, sondern nur, dass dann immer noch ein Bug im Programm ist.

Nein, das ist so allgemein gesagt nicht richtig. In vernünftigen modernen Sprachen sind sehr viele Dinge, die in C UB sind, einfach Compilerfehler. Es verhindert also die Bugs.

Nur bei den wenigen Dingen die zur Laufzeit geprüft werden, sind die Bugs weiterhin formell im Programm. Aber massiv entschärft.

Das klingt, als sei UB maßgeblich etwas, das zur Compilezeit auftritt, was so nicht stimmt. Außerdem ist es dem Compiler wie gesagt nicht verboten, solche Fehler dem Programmierer mitzuteilen.

Aber es geht ja gerade um den Fall, in dem der Compiler das nicht beweisen kann. Damit muss er irgendwie umgehen.

Das heißt aber noch lange nicht, dass er bei jeder Division die Prüfung machen muss. Und das wurde ja hier behauptet. Es ist falsch.

Er muss halt erkennen, bei welchen Divisionen die Prüfung nötig ist, und das ist oft alles andere als trivial. Aber ja, manchmal geht das.

Gast #7309938
Lesenswert?

MaWin O. schrieb:

Bei der Warnung geht es nicht darum, ob der Operator atomar ist,

Beispiel:

1
 #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]

Warum ist eigentlich auch egal, die Meldungen (diese spezielle) nerven dennoch. Zudem ist, zumindest bei den AVR, recht klar was da passiert.

Rolf M. schrieb:

Dann beiße ich eben in den sauren Apfel und schreibe was unnütiges hin, damit der Compiler glücklich ist.

Das ist irgendwie nicht wirklich die ultimative Lösung, da da solche read-modify-write Dinger/Operatoren in Massen bei den AVRs eingesetzt wurden und werden. Unmengen fertiger Anwendungen und Librarys müssen geändert werden.

#7309949
Lesenswert?

Rolf M. schrieb:

Er muss halt erkennen, bei welchen Divisionen die Prüfung nötig ist, und das ist oft alles andere als trivial.

Ich habe bereits geschrieben, wie man diese Entscheidung verbessern kann.

Zum Rest deines Beitrages sage ich nichts. Das ist mir zu dumm.

EAF schrieb:

Warum ist eigentlich auch egal, die Meldungen (diese spezielle) nerven dennoch.

Naja. Du hast halt den falschen Grund genannt und ich habe es korrigiert. Nicht mehr und nicht weniger.

Dass die AVR-Libc dieses veraltete Konstrukt nutzt, ist natürlich schlecht. Keine Frage. Das legitimiert das Abschalten dieser Warnung natürlich, bis die AVR-Libc gefixt ist.

Zudem ist, zumindest bei den AVR, recht klar was da passiert.

Ja. Das ist implementation defined.

#7309958
Lesenswert?

MaWin O. schrieb:

Rolf M. schrieb:

Er muss halt erkennen, bei welchen Divisionen die Prüfung nötig ist, und das ist oft alles andere als trivial.

Ich habe bereits geschrieben, wie man diese Entscheidung verbessern kann.

Und ich habe bereits mehrfach geschrieben, dass die Sprache dem Compiler nirgends verbietet, das zu tun.

Zum Rest deines Beitrages sage ich nichts. Das ist mir zu dumm.

Wie du meinst…

#7309960
Lesenswert?

MaWin O. schrieb:

Matthias T. schrieb:

Es ist mir nicht klar, was Du damit meinst? Der overflow in Berechnungen kann im Allgemeinen zur Compile-Zeit noch nicht erkannt werden.

Es wird vom Compiler angenommen, dass kein overflow vorkommt. Das führt dann zum Beispiel dazu, dass Code eliminiert wird, der unter dieser Annahme niemals ausgeführt werden könnte.

Wenn es dann zur Laufzeit aber doch zu einem overflow kommt, dann fehlt der behandelnde Code.

Welche Programmiersprache erzeugt extra Code für die overflow Behandlung in Berechungen? Ich meine nicht das Thema "Vergleiche unsigned mit negativen Wert", sondern ganz normale Berechnungen. Ich denke, dass es nicht viele Beispiele gibt. Und was machen die dann - außer einer Exception werfen (was natürlich schon mal eine Hilfe wäre)?

Ich glaube auch, dass das Thema bei 8 Bit und 16 Bit Prozessoren durchaus problematisch war. Bei 32 Bit und noch viel weniger bei 64 Bit Prozessoren wird es wohl nicht so ein großes Problem sein.

In Java schon. In anderen sicheren Sprachen aber nicht.

Zum Beispiel use-after-free und Objektlebenszeiten können in anderen Sprachen zur Compilezeit geprüft werden.

Nirgends wo kommt es ohne Aufpreis. In modernen C++ (Libs) wird es über escape analysis und/ oder reference counting gemacht, das erzeugt aber auch overhead. Und reference counting und multi tasking vertragen sich nicht besonders gut. In Rust erkauft man es sich mit einem etwas schrägen handling, an das man sich aber vermutlich gewöhnen kann. Die vollständige Ermittlung von Objektlebenszeiten nur zur Compilezeit wäre mir neu - welche Sprache macht das?

#7309965
Lesenswert?

Rolf M. schrieb:

Und ich habe bereits mehrfach geschrieben, dass die Sprache dem Compiler nirgends verbietet, das zu tun.

Ja. Was niemand jemals bestritten hat.

Es ist in C aber sehr sehr schwierig solche Optimierungen zu machen, weil das Typsystem so schlecht ist. Es gibt zum Beispiel (nur eines von vielen) keinerlei explizite Möglichkeiten die Wertebereiche von Skalaren so einzuschränken, dass es dem Compiler bessere Optimierungen ermöglicht.

Dass der C-Standard "X, Y, Z nicht verbietet" und "dem Compiler die Möglichkeiten lässt dies und jenes zu tun", das ist ja gerade der größte Teil des Problems.

Ich weiß, dass langjährige C-Programmierer sich das sehr schwer vorstellen können, dass Dinge die in C Laufzeit kosten, in vernünftigen Sprachen kostenlos und sicherer sein können.

#7309966
Lesenswert?

Matthias T. schrieb:

Welche Programmiersprache erzeugt extra Code für die overflow Behandlung in Berechungen?

Das hat niemand gesagt.

Matthias T. schrieb:

Nirgends wo kommt es ohne Aufpreis.

Eben doch.

Die vollständige Ermittlung von Objektlebenszeiten nur zur Compilezeit wäre mir neu - welche Sprache macht das?

Ich schrieb nichts von vollständig. Aber es reicht in der Regel aus, wenn es im weit überwiegenden Teil der Fälle so ist. Und wenn dir das in den restlichen wenigen Fällen immer noch nicht ausreicht, dann darfst du gerne für diese Fälle unsichere Konstrukte nutzen. Wäre mir jetzt aber noch nicht untergekommen.

#7309978
Lesenswert?

MaWin O. schrieb:

Matthias T. schrieb:

Welche Programmiersprache erzeugt extra Code für die overflow Behandlung in Berechungen?

Das hat niemand gesagt.

Also reduziert es sich doch wieder auf die if-Thematik. Das ist so eine Stelle an der die warning unbedingt wie ein Fehler behandelt werden sollte. Ich gehe davon aus, dass es nur eine warning ist, weil aus historischen Gründen viel "cleverer" oder auch schlampiger Code existiert und hier die Kompatibilität schwerer wiegt. Von mir aus könnte es ein normaler bug sein, der die Übersetzung abbricht.

Matthias T. schrieb:

Nirgends wo kommt es ohne Aufpreis.

Eben doch.

Die vollständige Ermittlung von Objektlebenszeiten nur zur Compilezeit wäre mir neu - welche Sprache macht das?

Ich schrieb nichts von vollständig.

Das hast Du geschrieben: "Zum Beispiel use-after-free und Objektlebenszeiten können in anderen Sprachen zur Compilezeit geprüft werden." Da steht nicht das Wort vollständig. Aber eine Speicherverwaltung, die nur manchmal funktioniert und manchmal nicht, ist nicht besonders praktisch.

#7309991
Lesenswert?

MaWin O. schrieb:

Matthias T. schrieb:

Aber eine Speicherverwaltung, die nur manchmal funktioniert und manchmal nicht, ist nicht besonders praktisch.

Tut mir leid. Du drehst mir jetzt zum wiederholten Male die Worte im Munde herum. Trolle unterstütze ich nicht weiter.

Wenn ich nicht verstehe oder errate welche Programmiersprache Du meinst, dann schreib es doch einfach. Eine Speicherverwaltung muss alle Objektinstanzen verwalten die ihr unterliegen. Das ist genau das Problem der manuellen Speicherverwaltung bei C und dem manchmal mangelhaften Verwalter "Mensch".

#7310003
Lesenswert?

Matthias T. schrieb:

Welche Programmiersprache erzeugt extra Code für die overflow Behandlung in Berechungen? Ich meine nicht das Thema "Vergleiche unsigned mit negativen Wert", sondern ganz normale Berechnungen. Ich denke, dass es nicht viele Beispiele gibt. Und was machen die dann - außer einer Exception werfen (was natürlich schon mal eine Hilfe wäre)?

Standard-Ada würde eine Exception werfen (so man das will). GNAT (Gnu Ada) in Verbindung mit SPARK ("reduziertes" Ada, das vollständige Verifikation erlaubt) würde über hinzuzufügende Contracts sicherstellen, dass der Überlauf gar nicht stattfinden kann. Ist das Programm einmal so verifiziert, kann man es ohne Runtime-Checks compilieren und erreicht (meist https://en.wikipedia.org/wiki/SPARK_(programming_language)) dieselbe Performance wie C/C++.

Moderator #7310066
Lesenswert?

Matthias T. schrieb:

Der Standard sagt (frei formuliert), dass der Compiler bei UB auch rosa Einhörner produzieren darf. Das heißt aber nicht, dass der Compiler das auch tut. Die meisten Compiler versuchten das zu tun, was am vernünftigsten erscheint (dem Compilerbauer, das muss sich nicht mit der Erwartungshaltung des Entwicklers decken). Und insbesondere kenne ich keinen Compiler, der mal so und mal so arbeitet.

Doch, z.B. der GCC. Das folgende Programm verhält sich je nach Optimierungsstufe (-O0 oder -O1) unterschiedlich, was aber wegen des UB völlig in Ordnung ist:

1
#include <stdio.h>
2

3
int main(void) {
4
  for (int i = 0; i != -1; i++)
5
    printf("loop\n");
6
}
#7310229
Lesenswert?

Yalu X. schrieb:

Doch, z.B. der GCC. Das folgende Programm verhält sich je nach Optimierungsstufe (-O0 oder -O1) unterschiedlich, was aber wegen des UB völlig in Ordnung ist...

Natürlich ist das GCC-Verhalten hier standardkonform, trotzdem finde ich das Beispiel einigermassen fragwürdig. Die Warnung

1
In function ‘main’:
2
tst014.c:3:29: warning: iteration 2147483647 invokes undefined behavior [-Waggressive-loop-optimizations]
3
    3 |   for (int i = 0; i != -1; i++)
4
      |                            ~^~
5
tst014.c:3:21: note: within this loop
6
    3 |   for (int i = 0; i != -1; i++)
7
      |                   ~~^~~~~

kommt nur bei eingeschalteter Optimierung (trotz -Wall). Obwohl der Compiler hier alles an der Hand hat, auch ohne Optimierung leicht UB festzustellen. Schmeckt mir ein bisschen nach Compilerbauer-Häme...

#7310234
Lesenswert?

Markus F. schrieb:

Schmeckt mir ein bisschen nach Compilerbauer-Häme...

Die Warnung könnte vielleicht trotzdem kommen, allerdings vermute ich, daß das im Compiler tatsächlich erst im loop-Optimierungsschritt auffällt.

Ohne Optimierung wirds halt einfach wie gewünscht funktionieren. Da nutzt der Compilers sein Wahlrecht, bei UB einfach was für ihn passendes zu machen, und lässt den signed integer klaglos überlaufen.

Oliver

#7310240
Lesenswert?

Oliver S. schrieb:

Die Warnung könnte vielleicht trotzdem kommen, allerdings vermute ich, daß das im Compiler tatsächlich erst im loop-Optimierungsschritt auffällt.

GNU Ada bringt - unabhängig von der Optimierungsstufe (und ohne irgendwelche extra Warnings einzuschalten):

1
5:20: warning: loop range is null, loop will not execute [enabled by default]

Dahinter steckt derselbe Compiler.

Persönliche Seite #7310260
Lesenswert?

Rolf M. schrieb:

Bei C hat man sich aus Effizienzgründen für zweiteres entschieden, wobei das dem Compiler nicht verbietet, ersteres trotzdem zu tun, aber es zwingt ihn eben auch nicht dazu.

Bei GCC zum Beispiel so:

1
int volatile b = 0;
2

3
int main (void)
4
{
5
    return 1 / b;
6
}

Übersetzt ohne Diagnostics und gibt beim Ausführen:

1
Floating point exception (core dumped)

zusätzlich generiert mit -fsanitize=undefined ergibt wie erwartet ebenfalls keine Diagnostic. Ausführen:

1
main.c:5:14: runtime error: division by zero
2
Floating point exception (core dumped)

Die Frage ist dann, ob es möglich ist, damit zu entwickeln, weil

  1. Man braucht genügend Resourcen (Speicher und Zeit) um die Instrumentierungen machen zu können. Aufm PC kein Problem, auf einem sehr beschränkten Embedded System aber schon. *)

  2. Jemandem muss es wichtig genug gewesen sein, das für die Zielarchitektur zu unterstützen. Für avr-gcc zum Beispiel gibt's keine entsprechenden Sanitizer (libubsan.a etc). Hindernis ist wohl, fehlende C++ und Atomic Unterstützung.

*) Prinzipiell wäre es möglich, entsprechende Features in einen Simulator auszulagern, aber gerade auf Echtzeitsystemen bringt Simulation ihre eigenen Probleme, die Simulation u.U. obsolet machen. Außerdem hätte man eine wenig wünschenserte Kopplung zwischen Simulator einerseits und Compiler(n) und Target-Libs andererseits.

#7310263
Lesenswert?

peda schrieb .. Ich hab die Erfahrung gemacht, wenn nichts dagegen spricht, macht man eine Regelung vorzugsweise in float. Besonders alle Arten von Temperaturregelungen sind aus CPU-Sicht schnarch langsam.

Sicher nicht. Was soll das denn bringen ? Exponenten.. Ich rechne Regelungen, auch Temperaturregelungen mit 32bit. Worauf regle ich ? Mit welchem Kooridinatensystem ? Auf ADC Werte, oder 2^N fache Der Sollwert ist der ADC Wert des Sensors. zB von einem 24bit Wandler. Oder wenn's nur ein 10bit Wander ist, auf des 16 fache. Auf Temperaturen umgerechnet wird nur bei Bedarf, wenn der Benutzer die auch abfragt. Sonst macht die keinen Sinn. Das Stellglied wird mit einem gerechneten 32bit Wert shift N angesteuert. Da auf ADC Fehler = Null geregelt wird, wird der optimalerweise beliebig gut erreicht und gehalten.

Ah, ja. Overflows. Um eine Addition vor Overflow zu schuetzen arbeitet man mit bounded Variablen. Der Overflow wird auf das Maximum resp Minimum gesetzt Seien die Variable A und B if ($7FFFFFFF-A) <B) { A+B => $7FFFFFFF) // A positiv overflow if ($80000000+A) >B) { A+B => $80000000) // A negativ overflow Waehrend des Entwicklungsprozesses sollte man das natuerlich testen und vermeiden. Was auch immer die Ursachen sind. Solange nur das Stellglied an den Anschlag geht, runterskalieren.

Beitrag #7310746 wurde von einem Moderator gelöscht.
Gast #7310770
Lesenswert?

EAF schrieb:

denn ich weiß, dass |= nicht atomar ist

Wen man damit ein einzelnes Bit in SFR <= 0x1F setzt, macht er eine atomare SBI Operation:

1
#include <avr/io.h>
2

3
int main() 
4
{
5
   PORTB |= 1;
6
}

avr-gcc -mmcu=atmega328 -O1 test.c avr-objdump -h -S a.out

1
00000080 <main>:
2
  80:  28 9a         sbi  0x05, 0  ; 5
3
  82:  80 e0         ldi  r24, 0x00  ; 0
4
  84:  90 e0         ldi  r25, 0x00  ; 0
5
  86:  08 95         ret
Gast #7310822
Lesenswert?

Wenn man damit ein einzelnes Bit in SFR <= 0x1F setzt, macht er eine atomare SBI Operation

EAF schrieb:

Was willst du mir damit sagen? Dass das so mit allen AVR und allen Registern klappt? Das wäre ein Irrtum!

Damit will ich genau das sagen, was ich geschrieben habe. Du darfst gerne davon abweichende Aussagen erfinden und dich darüber aufregen, solange du anerkennst, dass es deine Phantasien sind.

Gast #7312103
Lesenswert?

S.E. schrieb:

Von Pascal kann sich C sicher noch "ein paar Scheiben abschneiden".

Das wird nicht passieren, weil C viel älter ist und Kompatibilität zu diesem alten Standard dessen heilige Kuh ist. Wenn die Entwicklung von C diesbezüglich anders wären, dann würde es C wahrscheinlich nicht mehr geben.

Das "anders-sein" überlassen die C Entwickler mit voller Absicht den anderen Programmiersprachen. C wird sich nicht ändern.

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