Array Inhalte miteinander Multiplizieren.
-
@hustbaer sagte in Array Inhalte miteinander Multiplizieren.:
Finde ich nicht gut. Damit maskierst du bloss Fehler (Aufruf mit NULL und Länge > 0).
Dann brauchts mehr out-parameter.
-
@Swordfish sagte in Array Inhalte miteinander Multiplizieren.:
@hustbaer sagte in Array Inhalte miteinander Multiplizieren.:
Finde ich nicht gut. Damit maskierst du bloss Fehler (Aufruf mit NULL und Länge > 0).
Dann brauchts mehr out-parameter.
Oder „Garbage in, garbage out“
Die Parameter müssen halt gültig sein.
Man kann auch außerhalb testen, wenn es wichtig ist.
-
@DirkB Ich würde per assert die Gültigkeit der Parameter testen. Dann hat man was im Debug Modus und im Release ists dann halt tatsächlich GIGO.
-
Aber was ist denn ein "gültiger Parameter"? Insbesondere: ist Länge = 0 (mit pointer egal) gültig? Was erwartet man?
Wir haben eigentlich 4 Optionen:
a) UB - wir stellen uns auf den Standpunkt, dass die Funktion immer mit mindestens einenem Wert aufgerufen werden muss
b) 1 - 1 ist das neutrale Element der Multiplikation.
c) 0 - nichts geht rein, nichts kommt raus
d) Irgendein irgendwie geartetes Fehlerreporting
-
Wie wäre es mit dem float Wert NaN?
-
@wob sagte in Array Inhalte miteinander Multiplizieren.:
Aber was ist denn ein "gültiger Parameter"? Insbesondere: ist Länge = 0 (mit pointer egal) gültig? Was erwartet man?
Wir haben eigentlich 4 Optionen:
a) UB - wir stellen uns auf den Standpunkt, dass die Funktion immer mit mindestens einenem Wert aufgerufen werden muss
b) 1 - 1 ist das neutrale Element der Multiplikation.
c) 0 - nichts geht rein, nichts kommt raus
d) Irgendein irgendwie geartetes FehlerreportingAlso Länge größer 0 aber mit nullptr empfinde ich als ungültig. Wie auch immer man sich entscheidet, muss es halt dokumentiert werden.
-
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
Also Länge größer 0 aber mit nullptr empfinde ich als ungültig.
Ok, das ist klar! Aber das sollte UB sein. Das zu prüfen, sehe ich nicht als Aufgabe der Funktion.
NaN ist nur dann eine zusätzliche Möglichkeit, wenn ein float-Werte multipliziert werden sollen. Aber bei int? Und dann wäre es schön, wenn
multiply_array_int
undmultiply_array_float
sich gleich verhalten würden...
-
In C gibt es wohl noch keine Möglichkeit ein signaling NaN zu benutzen, dass würde eine Floating Point Exception der FPU auslösen.
#include <math.h> #include <stdlib.h> #include <stdio.h> float product(float *data, size_t length) { if (length == 0) return 1.0f; if (length >= 1 && !data) return NAN; float r = 1.0f; for (size_t i = 0; i < length; ++i) r *= data[i]; return r; } int main() { float p = 0.0f; float array[] = {1.0f, 2.0f, 3.0f, 4.0f}; p = product (array, sizeof(array)/sizeof(float)); printf("%f\n", p); p = product (NULL, 0); printf("%f\n", p); p = product (NULL, 2); printf("%f\n", p); }
-
@wob sagte in Array Inhalte miteinander Multiplizieren.:
NaN ist nur dann eine zusätzliche Möglichkeit, wenn ein float-Werte multipliziert werden sollen. Aber bei int? Und dann wäre es schön, wenn
multiply_array_int
undmultiply_array_float
sich gleich verhalten würden...Bei int kannst Du in C nicht viel machen, außer man gibt das Ergebnis als Parameter zurück und die Funktion hat als return Wert einen Fehlercode – Beispiel.
int multiply(int* result, int* input, size_t input_size);
-
@wob sagte in Array Inhalte miteinander Multiplizieren.:
Aber was ist denn ein "gültiger Parameter"? Insbesondere: ist Länge = 0 (mit pointer egal) gültig? Was erwartet man?
Wir haben eigentlich 4 Optionen:
a) UB - wir stellen uns auf den Standpunkt, dass die Funktion immer mit mindestens einenem Wert aufgerufen werden muss
b) 1 - 1 ist das neutrale Element der Multiplikation.
c) 0 - nichts geht rein, nichts kommt raus
d) Irgendein irgendwie geartetes FehlerreportingJe nach konkretem Anwendungsfall können die Antworten unterschiedlich aussehen. Wenn in der Anwendung Aufrufe mit
len == 0
einfach nicht vorkommen, macht es vermutlich Sinn (a) zu wählen. Evtl. auch noch (d), z.B. in Form von "write log message + abort()". Und wenn in der Anwendung Aufrufe mitlen == 0
möglich sind, kommt es halt drauf an in welchem Zusammenhang diese auftreten.Wenn man die Funktion als nicht-spezialisierte Hilfsfunktion für alle möglichen Anwendungen auslegen will, dann würde ich persönlich (b) wählen. U.a. weil man damit Aufrufe verketten kann, ala
mulall(arr1, len1) * mulall(arr2, len2)
, ohne sich Gedanken machen zu müssen wie die einzelnen Werte aufarr1
undarr2
verteilt sind. Ich könnte mir vorstellen dass das in manchen Fällen praktisch ist.
-
@Swordfish sagte in Array Inhalte miteinander Multiplizieren.:
@hustbaer sagte in Array Inhalte miteinander Multiplizieren.:
Finde ich nicht gut. Damit maskierst du bloss Fehler (Aufruf mit NULL und Länge > 0).
Dann brauchts mehr out-parameter.
Ich wüsste nicht wozu.
-
@hustbaer Wenn zwischen verschiedenartigen fehlerhaften Parametern unterscheiden können möchte. Aber nachdem ich die Diskussion die auf meinen Beitrag hin entstanden ist gelesen habe bin ich auch für GIGO.
-
@hustbaer sagte in Array Inhalte miteinander Multiplizieren.:
@wob sagte in Array Inhalte miteinander Multiplizieren.:
Aber was ist denn ein "gültiger Parameter"? Insbesondere: ist Länge = 0 (mit pointer egal) gültig? Was erwartet man?
Ein mathematisches Produkt ist als 1 definiert, wenn es leer ist z.B. .
-
@wob sagte in Array Inhalte miteinander Multiplizieren.:
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
Also Länge größer 0 aber mit nullptr empfinde ich als ungültig.
Ok, das ist klar! Aber das sollte UB sein.
Also gerade das häufige UB macht C++ ja so unhandlichund unsicher. Ich weiß nicht ob man da selber noch mit dazu beitragen sollte.
-
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
@wob sagte in Array Inhalte miteinander Multiplizieren.:
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
Also Länge größer 0 aber mit nullptr empfinde ich als ungültig.
Ok, das ist klar! Aber das sollte UB sein.
Also gerade das häufige UB macht C++ ja so unhandlichund unsicher. Ich weiß nicht ob man da selber noch mit dazu beitragen sollte.
Sorge beim Aufruf dafür, dass es kein UB gibt.
Evtl. mit einer eigenen Funktion product_s()
-
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
@wob sagte in Array Inhalte miteinander Multiplizieren.:
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
Also Länge größer 0 aber mit nullptr empfinde ich als ungültig.
Ok, das ist klar! Aber das sollte UB sein.
Also gerade das häufige UB macht C++ ja so unhandlichund unsicher. Ich weiß nicht ob man da selber noch mit dazu beitragen sollte.
Naja. "This is the way"
Ob Zeiger+Länge gültig sind kann man in C (und auch C++) halt nicht prüfen. Zumindest folgende Dinge könnten falsch sein (immer angenommen Länge > 0 wird übergeben):
- Der Zeiger ist NULL
- Der Zeiger zeigt ins Nirvana
- Der Zeiger zeigt auf gültigen Speicher aber das Alignment passt nicht
- Der Zeiger zeigt auf gültigen Speicher und das Alignment passt, aber dort liegen keine
float
s sondern ganz andere Objekte - Der Zeiger zeigt auf/in ein
float
Array aber das Array ist kürzer als die übergebene Länge - Der Zeiger zeigt auf/in ein
float
Array aber die Array-Elemente wurden nocht nicht initialisiert
Davon kann man nur die Fälle "Zeiger ist null" und "Alignment passt nicht" erkennen. Bei den anderen ist das Ergebnis unweigerlich UB. (Wenn der Zeiger nicht auf gültigen Speicher zeigt ist übrigens schon das Lesen/Übergeben des Zeigerwertes selbst UB. IIRC auch bei unpassendem Alignment, wobei da bin ich nicht zu 100% sicher.)
Da so viele UB Fälle übrigbleiben sollte man sich mMn. die Frage stellen ob es gut ist einen oder zwei davon zu prüfen und mit definiertem Verhalten zu versehen. Und mMn. ist das halt eher nicht so gut, da es einen "false sense of security" schafft. "Alle ungültigen Parameterkombinationen führen zu UB" ist eine klare Ansage. "Die meisten ungültigen Parameterkombinationen führen zu UB aber ein paar checken und behandeln wir" ... meh.
Aber nehmen wir mal an man entschliesst sich trotzdem dafür dass das geprüft werden soll. Wie soll dann die Reaktion sein? Exception werfen geht nicht, wir machen hier ja C. (Davon abgesehen würde ich auch bei C++ argumentieren dass es eher keine so gute Idee wäre hier ne Exception zu werfen.) Fehlercode zurückgeben ist auch so ne Sache. Da müsste man dem Programmierer der einen solchen Fehler macht vertrauen dass er den Fehlercode auch prüft und sinnvoll behandelt. 0 oder 1 zurückgeben kann leicht dazu führen dass der Fehler unerkannt bleibt. Eine Log-Meldung schreiben und dann 0 oder 1 zurückgeben wäre ne Spur besser. Aber wer guckt schon im Logfile nach wenn das Programm gemeldet hat dass es erfolgreich war? NaN zurückgeben kann auch zu allem möglichen unerwarteten/unerwünschten Verhalten führen. Was mMn. gut ginge wäre eine Log-Meldung schreiben und dann das Programm mit
abort()
abbrechen. Quasi ein "release assert". (Bzw. wenn man ein Programm hat, wo es wichtig ist solche Fälle zu erkennen, kann man sich auch überlegen ob man nicht überhaupt den Release Build ohneNDEBUG
machen sollte - dann kann man auch das normaleassert
verwenden.)
Den Fall "Zeiger NULL, Länge 0" allerdings kann man ohne weiteres als gültig ansehen. Und sollte man mMn. auch. Weil es in manchen Situationen halt wirklich praktisch ist. Nervt mich jedes mal bei
memcpy
wenn ich das blödeif
davorschreiben muss nur damit UBSAN nicht rumnörgelt.
-
@hustbaer sagte in Array Inhalte miteinander Multiplizieren.:
Davon kann man nur die Fälle "Zeiger ist null" und "Alignment passt nicht" erkennen. Bei den anderen ist das Ergebnis unweigerlich UB. (Wenn der Zeiger nicht auf gültigen Speicher zeigt ist übrigens schon das Lesen/Übergeben des Zeigerwertes selbst UB. IIRC auch bei unpassendem Alignment, wobei da bin ich nicht zu 100% sicher.)
Die Stelle in der Norm möchte ich sehen, wo das Übergeben des ungültigen Zeigers UB auslöst. Üblicherweise wird der Zeiger (bis dahin nur ein Zahlenwert unter vielen) in ein CPU-Register geschrieben, und erst beim Zugriff auf den Speicher knallt es, denn erst jetzt wird dieser Zahlenwert auch als Zeiger von der CPU interpretiert: beim Aligment direkt durch die CPU und bei Speicherfehlern durch das OS, weil die PMMU einen Zugriffsfehler meldet. Schlimmer sind die Fälle bei denen der Zeiger ins Nirvana zeigt, und dort zwar falscher Speicher ist, aber auf ihn zugegriffen werden kann ohne einen Fehler auszulösen.
NaN zurückgeben kann auch zu allem möglichen unerwarteten/unerwünschten Verhalten führen. Was mMn. gut ginge wäre eine Log-Meldung schreiben und dann das Programm mit
abort()
abbrechen.Es gibt zwei Arten von NaN – silent und signaling. Keine Ahnung was andere OS machen, UNIX/Linux schickt dem Progamm ein SIGFPE, wenn es ein signaling NaN ist. Das Problem ist hier, dass man weder in C noch C++ in irgend einer Form vernünftig mit der CPU oder FPU interagieren kann. Fortran hat ein eigenes IEEE754 Modul, mit dem man die Flags und FPU Exceptions behandeln kann bzw. auch auslösen kann. C erlaubt es nun nur ein silent NaN als Ergebnis zu übergeben. Die Frage an dieser Stelle ist dann: Weshalb gibt es weder in C noch C++ die Möglichkeit ein signaling NaN zu übergeben?
-
@DirkB sagte in Array Inhalte miteinander Multiplizieren.:
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
@wob sagte in Array Inhalte miteinander Multiplizieren.:
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
Also Länge größer 0 aber mit nullptr empfinde ich als ungültig.
Ok, das ist klar! Aber das sollte UB sein.
Also gerade das häufige UB macht C++ ja so unhandlichund unsicher. Ich weiß nicht ob man da selber noch mit dazu beitragen sollte.
Sorge beim Aufruf dafür, dass es kein UB gibt.
Evtl. mit einer eigenen Funktion product_s()Na genau daran scheitert es ja so häufig. Die Leute tun das nicht.
-
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
Sorge beim Aufruf dafür, dass es kein UB gibt.
Evtl. mit einer eigenen Funktion product_s()Na genau daran scheitert es ja so häufig. Die Leute tun das nicht.
Für mich wäre jetzt die Frage interessant was mit solchen Leuten zu besseren Ergebnissen führt. a) auf bestimmte falsche Eingaben zu prüfen und den Fehler irgendwie "graceful" zu behandeln oder b) genau das eben ganz absichtlich nicht zu tun, in der Hoffnung dass die Leute dadurch lernen besser aufzupassen.
Und mMn. ist für C bzw. C++ (b) nicht unbedingt verkehrt. Wobei man die Checks ruhig einbauen kann. Nur solche Fehlerfälle "graceful" zu behandeln finde ich verkehrt. Wenn dann das Programm abbrechen. Weil es einerseits die einzig "sichere" Option ist (kein Ergebnis ist meistens besser als ein falsches Ergebnis/unkontrollierter Crash/...), und andrerseits zwingt man die Benutzer der Funktion dadurch besser aufzupassen.
-
@hustbaer sagte in Array Inhalte miteinander Multiplizieren.:
@Tyrdal sagte in Array Inhalte miteinander Multiplizieren.:
Sorge beim Aufruf dafür, dass es kein UB gibt.
Evtl. mit einer eigenen Funktion product_s()Na genau daran scheitert es ja so häufig. Die Leute tun das nicht.
Für mich wäre jetzt die Frage interessant was mit solchen Leuten zu besseren Ergebnissen führt. a) auf bestimmte falsche Eingaben zu prüfen und den Fehler irgendwie "graceful" zu behandeln oder b) genau das eben ganz absichtlich nicht zu tun, in der Hoffnung dass die Leute dadurch lernen besser aufzupassen.
Und mMn. ist für C bzw. C++ (b) nicht unbedingt verkehrt. Wobei man die Checks ruhig einbauen kann. Nur solche Fehlerfälle "graceful" zu behandeln finde ich verkehrt. Wenn dann das Programm abbrechen. Weil es einerseits die einzig "sichere" Option ist (kein Ergebnis ist meistens besser als ein falsches Ergebnis/unkontrollierter Crash/...), und andrerseits zwingt man die Benutzer der Funktion dadurch besser aufzupassen.
Wenn ich mir die Sicherheitslücken anschaue ist Ansatz b) grandios gescheitert. Selbst talentierte Entwickler hauen da manchmal daneben. Die meisten legen aber eh keine besondere Sorgfalt an den Tag. Ist zumindest meine Erfahrung.
Und wegen dem "Crash" scglug ich ja asserts vor. Ist quasi der Mittelweg zwischen a und b.