Karl schrieb:> Der Compiler-Explorer meldet keinen Fehler, der bringt nur eine Warnung.
Der Code ist ja auch nicht illegal, sondern höchstens unsinnig.
> Und witzigerweise bringt der die Warnung nur bei char, uint8_t oder> int8_t. Und nur bei der Zuweisung, nicht beim Vergleich.
Der GCC meckert mit -Wextra auch den Vergleich an:
1
warning: comparison is always false due to limited range of data type [-Wtype-limits]
> int16_t c2; c2 = ~0x8000; wird klaglos akzeptiert, obwohl es genauso> einen Overflow erzeugen würde.
Auf dem PC liefert der GCC dabei die gleichen Warnungen wie oben. Auf
dem AVR natürlich nicht, weil dort int16_t = int ist und somit keine
Konvertierung erfolgt, die einen Überlauf auslösen könnte.
Yalu X. schrieb:> Auf dem PC liefert der GCC dabei die gleichen Warnungen wie oben. Auf> dem AVR natürlich nicht, weil dort int16_t = int ist und somit keine> Konvertierung erfolgt, die einen Überlauf auslösen könnte.
Ja, der gleiche Schmonz funktioniert aber auch auf 32bit CPUs mit int32
vs. int16, oder auf 64bit CPUs. Das ist kein AVR-spezifisches Problem.
Yalu X. schrieb:> Der Code ist ja auch nicht illegal, sondern höchstens unsinnig.
Warum ist es unsinnig, um beim AVR-Beispiel zu bleiben, Bits auf diese
Weise zu selektieren, oder auf ein Bitmuster zu prüfen?
Der Code ist hier vereinfacht, aber c1 könnte seine Daten aus einem
low-aktiven Tastenfeld haben und dann mit ~auf gedrückte Tasten prüfen.
Die Werte könnten auch per #define irgendwo anders festgelegt werden.
Karl schrieb:> Yalu X. schrieb:>> Der Code ist ja auch nicht illegal, sondern höchstens unsinnig.>> Warum ist es unsinnig, um beim AVR-Beispiel zu bleiben, Bits auf diese> Weise zu selektieren, oder auf ein Bitmuster zu prüfen?
Ich bezog mich auf das da:
Karl schrieb:> char c1;> c1 = ~0x80;> if (c1 == ~0x80)> // True oder False?> Der Code ist hier vereinfacht, aber c1 könnte seine Daten aus einem> low-aktiven Tastenfeld haben und dann mit ~auf gedrückte Tasten prüfen.
Ok, aber was erwartest du bei einem Vergleich zwischen zwei verschieden
breiten Integertypen?
Es gibt im Wesentlichen drei Möglichkeiten, mit diesem Fall umzugehen:
1. Einer oder beide der beiden Typen werden zu einem gemeinsamen Typ
erweitert, so dass sie vergleichbar werden. Im konkreten Fall wird
das char unter Beibehaltung des numerischen Werts zu einem int
erweitert. So wird das in C bei der impliziten Typkonvertierung
gehandhabt.
2. Einer oder beide der beiden Typen werden zu einem gemeinsamen Typ
beschnitten, so dass sie vergleichbar werden. So hättest du es in
deinem Beispiel wohl gerne. Im konkreten Fall c1==~0x80 würde ~0x80
in ein char konvertiert, wobei sich der Wert von -129 in +127 ändert
und der Vergleich deswegen wahr wird, da c1 ebenfalls gleich +127
ist. Implizites Beschneiden und damit u.U. verbundene Änderungen des
Werts ist aber in den allermeisten Fällen unerwünscht, da es eine
Fehlerquelle bei numerischen Berechnungen darstellt.
3. Der Vergleich wird wegen der verschiedenen Datentypen gar nicht erst
zugelassen. Im konkreten Fall würde der Compiler mit einem Fehler
abbrechen, weil char und int nicht vergleichbar sind. Das wäre die
sauberste Alternative, es gibt aber nur wenige Programmiersprachen,
deren Typprüfung so streng ist (z.B. Haskell).
Man muss sich in C (wie auch in neueren Pascals und vielen anderen
Sprachen) bewusst sein, dass implizite Typkonvertierungen manchmal zu
Problemen führen. Solche Probleme lassen sich im Zweifelsfall meist
durch explizite Typkonvertierungen umgehen. Schreibt man im konkreten
Fall c1==(char)~0x80 statt c1==~0x80, ist das Ergebnis das von dir
erwartete.
Yalu X. schrieb:> 3. Der Vergleich wird wegen der verschiedenen Datentypen gar nicht erst> zugelassen. Im konkreten Fall würde der Compiler mit einem Fehler> abbrechen, weil char und int nicht vergleichbar sind. Das wäre die> sauberste Alternative, es gibt aber nur wenige Programmiersprachen,> deren Typprüfung so streng ist (z.B. Haskell).
Und C++ seit der 2011er Version, wenn man die Initialisierung mit {}
nutzt.
Da sagt z.B. der gcc bei diesem Programm
1
intmain()
2
{
3
charc{~0x80};
4
}
folgendes:
1
0x80.cpp: In function ‘int main()’:
2
0x80.cpp:3:20: error: narrowing conversion of ‘-129’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]
Yalu X. schrieb:> Ok, aber was erwartest du bei einem Vergleich zwischen zwei verschieden> breiten Integertypen?
Sowas?
1
# [47] c1 := not($80);
2
ldi r18,127
3
# [48] if c1 = not($80) then begin
4
cpi r18,127
5
brne .Lj16
Die Typen sind nicht verschieden breit. Die sind genau ein Byte. Und ein
Not eines Bytes ist wiederum genau ein Byte. Ein Byte wird durch ein Not
nicht zu zwei Byte.
Das ist nur eine Eigenheit von C, welches erstmal versucht alles in 16
Bit zu quetschen.
Rolf M. schrieb:> Und C++ seit der 2011er Version, wenn man die Initialisierung mit {}> nutzt.> Da sagt z.B. der gcc bei diesem Programmint main()> {> char c { ~0x80 };> }> folgendes:0x80.cpp: In function ‘int main()’:> 0x80.cpp:3:20: error: narrowing conversion of ‘-129’ from ‘int’ to> ‘char’ inside { } [-Wnarrowing]
Ähnliches sagt ja auch der C-Compiler bei der Initialisierung, nur dass
der lediglich eine Warnung ausspricht.
Karl schrieb:> Yalu X. schrieb:>> Ok, aber was erwartest du bei einem Vergleich zwischen zwei verschieden>> breiten Integertypen?>> Sowas?# [47] c1 := not($80);> ldi r18,127> # [48] if c1 = not($80) then begin> cpi r18,127> brne .Lj16
Das sieht nach Pascal aus, aber welcher Dialekt bzw.Compiler? (W.S. hat
diese Frage weiter oben auch schon gestellt)
Da gibt es nämlich große Unterschiede. Mit deinem Compiler wird der
Ausdruck not($80) offensichtlich zu 127, mit Free Pascal (PC-Version)
aber zu -129 ausgewertet. Dort würde der Vergleich false liefern.
Karl schrieb:> Die Typen sind nicht verschieden breit. Die sind genau ein Byte. Und ein> Not eines Bytes ist wiederum genau ein Byte. Ein Byte wird durch ein Not> nicht zu zwei Byte.
wo steht das 0x80 ein Byte ist? Ist es nämlich nicht, es ist ein Integer
(und ein integer heißt nicht 16 bit)
aber selbst wenn du aus 0x80 ein Byte machst, ist das "not" davon wieder
ein Integer. Blöd, eh?
> Das ist nur eine Eigenheit von C, welches erstmal versucht alles in 16> Bit zu quetschen.
16 Bit ist falsch, Integer wäre richtig.
Für dich mag es eine Eigenheit sein, C-Programmierer haben das Kapitel
"Integral Promotion" in ihrem C-Buch gelesen ;-)
Michael R. schrieb:> Für dich mag es eine Eigenheit sein, C-Programmierer haben das Kapitel> "Integral Promotion" in ihrem C-Buch gelesen ;-)
Das nützt aber Nichts, wenn in dem "guten C-Buch" des Anderen wieder
etwas Anderes steht. Eine Sprache, bei der eine 50:50 Chance besteht,
daß das richtige Ergebnis einer Operation erzielt wird...
Ich weiß nicht so recht...
Bär Luskoni schrieb:> Ich weiß nicht so recht...
Aber ich!
Denn:
Den individuellen Irrtum eines Buchautors, einer Sprache anzulasten, ist
mindestens ungerecht.
Karl schrieb:> Yalu X. schrieb:>> Der Code ist ja auch nicht illegal, sondern höchstens unsinnig.>> Warum ist es unsinnig, um beim AVR-Beispiel zu bleiben, Bits auf diese> Weise zu selektieren, oder auf ein Bitmuster zu prüfen?
Zahlen vergleicht man mit == ,Bitmuster prüft man mit &.
if (! (c & 0x80))...
gibt immer das richtige Ergebnis,unabhängig von der Bitbreite der
Variablen und der Implementierung.
Das Bitmuster 0x81 ist also gleich dem Bitmuster 0x80?
Da muss man sich nicht wundern, dass Software in C so schlecht ist, wenn
C-Programmierer nicht mal die einfachsten Vergleiche hinbekommen.
Leute, ist es denn echt so schwer einfach zuzugeben: Ja, der C-Compiler
baut hier Scheisse, das ist leider so, weil das irgendwann mal so
angefangen wurde und wir das jetzt wegen der Abwärtskombatibilität so
beibehalten müssen.
Stattdessen Verrenkungen um Verrenkungen, um zu erklären, warum die
Scheisse in C jetzt doch die Sachertorte ist.
Karl schrieb:>> aber selbst wenn du aus 0x80 ein Byte machst, ist das "not" davon wieder>> ein Integer. Blöd, eh?>> Ach, wirklich? https://en.wikipedia.org/wiki/Bitwise_operation#NOT
Schön, dass du dir die Mühe gemacht hast, 'Integral Promotion' zu
recherchieren, und versucht hast, das auch nur ansatzweise zu verstehen
;-)
Karl schrieb:> Ja, der C-Compiler> baut hier Scheisse, das ist leider so, weil das irgendwann mal so> angefangen wurde und wir das jetzt wegen der Abwärtskombatibilität so> beibehalten müssen.
Wenn du die Promotion verstanden hättest, wüsstest du auch wo deren
Vorteile liegen, dass das weder Sch**sse noch Abwärtskompatibilität ist.
So aber wirst du wohl bei deiner $FavoriteProgrammingLanguage (welche
eigentlich?) bleiben müssen... das ist aber ok, damit hat niemand ein
Problem, eher im Gegenteil ^^
Karl schrieb:> Autsch!> c1 = 0x81;> if (c1 & 0x80) {> is_true();> } else {> is_false();> }> Das Bitmuster 0x81 ist also gleich dem Bitmuster 0x80?
if (c1 & 0x80) fragt ab, ob Bit7 gesetzt ist. Das ist bei c = 0x81 der
Fall. Also richtig.
Wenn du abfragen willst ob Bit7 und Bit0 gesetzt ist, geht das so:
if ((c1 & 0x81)==0x81)...
Deinen Mangel an Kenntnissen von Bitoperationen kannst du nicht C
anlasten.
Karl schrieb:> Scheisse wird nicht dadurch schmackhafter, dass man ihr einen tollen> Namen gibt.
Hmmm... ich finde Gabelstapler total Scheisse. Ich kann mit denen
überhaupt nicht umgehen...
Jobst Q. schrieb:> Deinen Mangel an Kenntnissen von Bitoperationen kannst du nicht C> anlasten.
Das gilt aber definitiv nicht für Gabelstapler ;-)
Jobst Q. schrieb:> if (c1 & 0x80) fragt ab, ob Bit7 gesetzt ist. Das ist bei c = 0x81 der> Fall. Also richtig.
Das ist nicht das, was Du oben behauptet hast.
Karl schrieb:> Jobst Q. schrieb:>> if (c1 & 0x80) fragt ab, ob Bit7 gesetzt ist. Das ist bei c = 0x81 der>> Fall. Also richtig.>> Das ist nicht das, was Du oben behauptet hast.
"Bitmuster prüft man mit &." hatte ich gesagt. Und 0x80 ist nunmal als
Bitmuster in 0x81 enthalten.
Karl schrieb:> Michael R. schrieb:>> aber selbst wenn du aus 0x80 ein Byte machst, ist das "not" davon wieder>> ein Integer. Blöd, eh?>> Ach, wirklich?
Ja, wirklich.
> https://en.wikipedia.org/wiki/Bitwise_operation#NOT
Schön. Du hast rausgefunden, wie eine boolesche Invertierung
funtkioniert. Was hat das jetzt damit zu tun, von welchem Datentyp die
Konstante 0x80 und damit auch ~0x80 in C ist?
> Da muss man sich nicht wundern, dass Software in C so schlecht ist, wenn> C-Programmierer nicht mal die einfachsten Vergleiche hinbekommen.
Schließe doch bitte nicht von dir auf andere.
Karl schrieb:> if (c1 == ~0x80)> // True oder False?
Weder noch, sondern einfach Unsinn. Die einzig sinnvolle Verwendung des
bitweisen NOT '~' ist das Zurücksetzen von Bits zusammen mit dem
bitweisen AND '&'.
flags &= (~0x80);
Dabei ist es nicht von Nachteil, wenn die vom Compiler gewählte Größe
der '~'-Konstruktion größer ist als die betroffene Variable. Alle
zusätzlichen Bits sind gesetzt, haben also mit dem '&' keine Wirkung.
Rolf M. schrieb:> Was hat das jetzt damit zu tun, von welchem Datentyp die> Konstante 0x80 und damit auch ~0x80 in C ist?
Die Konstante hat sinnvoller Weise die Länge, die angegeben ist. 0x80
sind ein Byte, 0x0080 sind 2 Byte. Gerade bei Hex ist das doch total
simple und seit Jahrhunderten bewährt.
Ich sag doch: Die Crux bei C ist, dass es erstmal alles auf seine
mindestens 16 Bit presst, auch wenn das für 8-Bit-Systeme nicht sinnvoll
ist.
Jobst Q. schrieb:> Die einzig sinnvolle Verwendung des> bitweisen NOT '~'...
... die Du Dir vorstellen kannst. Für Deinen beschränkten Horizont kann
ja sonst keiner was.
Karl schrieb:> Die Konstante hat sinnvoller Weise die Länge, die angegeben ist. 0x80> sind ein Byte, 0x0080 sind 2 Byte. Gerade bei Hex ist das doch total> simple und seit Jahrhunderten bewährt.
Ach ist das süß ;-) Hex ist also ein Sonderfall... wie macht man das
dezimal, oktal?
> [...] dass es erstmal alles auf seine mindestens 16 Bit presst [...]
ah, mindestens... er lernt^^
Karl schrieb:> Die Konstante hat sinnvoller Weise die Länge, die angegeben ist. 0x80> sind ein Byte, 0x0080 sind 2 Byte.
In C hängt die Länge vom Wert und von der Basis ab.
Michael R. schrieb:> Ach ist das süß ;-) Hex ist also ein Sonderfall... wie macht man das> dezimal, oktal?
Stimmt aber. Der Typ einer lexikalischen Konstanten hängt zwar vom Wert
ab, nicht von der Länge im Quelltext, aber das bestimmt sich oktal/hex
tatsächlich anders als dezimal. Bei dezimaler Angabe werden die
vorzeichenlosen Typen übergangen, weshalb 32768 in einer 16-Bit Umgebung
"long" ist, 0x8000 aber "unsigned".
Ach, wenn unser Karl sich wenigstens darüber beschweren würde, dass ein
Byte auch mal 9 Bit breit sein darf, dann hätte ich ja halbes
Verständnis.
Aber so....
Denn das Verhalten von C ist eigentlich recht klar definiert.
Sicher kann man diese Definitionen in Zweifel ziehen, oder sich fragen
ob sie geschickt definiert sind.
Es macht allerdings keinen Sinn darüber zu schimpfen, denn die
Definition kann man nicht ändern. Das ist, wie darüber jammern, dass man
nass wird, wenn es regnet.
Man/Karl könnte sich eine andere Sprache suchen, wenn man/Karl C nicht
mag. Aber deswegen anderen die Sprache madig machen...?
Neee...
Ich selber bin auch kein C Fan.
Habs eher mit OOP, also wenn, dann C++ auf meinen AVR Zwergen.
Michael R. schrieb:> Hex ist also ein Sonderfall... wie macht man das> dezimal, oktal?
Naja, oktal in C ist ja noch bescheuerter: 010 ist ein Oktalzahl. Was
haben die geraucht, um auf sowas zu kommen?
Michael R. schrieb:> ah, mindestens...
Falls Du damit wieder auf die vergötterte Integer-Promotion ansprichst:
Wenn ein NOT aus einem Byte zwei Byte macht, ist das keine
Integer-Promotion, sondern einfach nur bescheuert. Das Ergebnis von
einem NOT auf ein Byte ist wiederum nur ein Byte. Genauso wie das
Ergebnis ein Byte AND ein Byte wieder nur ein Byte ist. Bitweise
Operatoren. Wie soll sich da die Anzahl der Bits erhöhen?
Nein, was Du für Integer Promotion hältst ist einfach nur die Eigenheit,
erstmal alles in 16 Bit zu pressen, weil 16 Bit damals(TM) halt hipp
waren. Das ist schlichtweg ein Designfehler von C, den man immer weiter
mitschleifen muss.
Arduino F. schrieb:> Man/Karl könnte sich eine andere Sprache suchen, wenn man/Karl C nicht> mag. Aber deswegen anderen die Sprache madig machen...?
Done!
Allerdings ist es im Gegenteil eher so, dass die C-Fans anderen "ihre"
Sprachen madig machen wollen, weil C und dessen Derivate das einzig
Wahre sind und man nur in C richtig programmieren könne.
Witzig zu sehen ist allerdings, wie krampfhaft die "Eigenheiten" sprich
Designfehler von C verteidigt werden, anstatt einfach mal zuzugeben: Ja
ok, das war damals eine Fehlentscheidung, mit der wir heute leider leben
müssen.
Karl schrieb:> Nein, was Du für Integer Promotion hältst ist einfach nur die Eigenheit,> erstmal alles in 16 Bit zu pressen, weil 16 Bit damals(TM) halt hipp> waren. Das ist schlichtweg ein Designfehler von C, den man immer weiter> mitschleifen muss.
wir müssen damals im selben Kommunikationstraining gewesen sein...
"Blamiere dich täglich" war der Wahlspruch, aber du übertreibst es
etwas... dabei warst du schon beim "mindestens".
es wird mitnichten auf 16 bit, sondern auf die native Wortbreite (aber
mindestens 16 bit) erweitert. Und das nicht weil es hipp ist oder war,
sondern weil es in der Mehrzahl der Fälle sinnvoll ist. Deswegen ist es
auch kein Designfehler, sondern wurde absichtlich so definiert, weil es
eben Vorteile bietet. Das damit ein paar Fallstricke für Anfänger
verbunden sind, damit hast du recht. Aber C hatte (im Gegensatz zu zB
Pascal) niemals den Anspruch, eine Anfänger-Sprache zu sein.
Karl schrieb:> Naja, oktal in C ist ja noch bescheuerter: 010 ist ein Oktalzahl. Was> haben die geraucht, um auf sowas zu kommen?
Oktale Darstellung war in der Phase ziemlich gebräuchlich, in der C
erfunden wurde. Wenn man die binäre Codierung der Befehle der PDP11 und
anderer Maschinen dieser Ära ansieht, dann weiss man auch warum.
> Falls Du damit wieder auf die vergötterte Integer-Promotion ansprichst:> Wenn ein NOT aus einem Byte zwei Byte macht,
Tut es nicht. ~0x80 macht aus einer "int" eine "int", denn 0x80 ist
schon eine "int". ~ ändert also nicht den Typ. Auch in
uint8_t c = 0x80;
... ~c ...
erweitert nicht speziell der ~ Operator die variable "c" auf "int",
sondern der Umstand, dass "c" im Kontext einer Expression verwendet
wird. Aber auch da wird "c" vor der Berechung erweitert, also nicht
durch den Operator.
> Nein, was Du für Integer Promotion hältst ist einfach nur die Eigenheit,> erstmal alles in 16 Bit zu pressen,
Da wird nichts gepresst. Konstanten, die nicht in 16 Bits passen, werden
nicht reduziert. Der Typ passt sich an, mindestens aber "int", was per
Definition mindestens 16 Bits sein muss.
C ist definitiv nicht die bestmögliche aller Welten. Muss man nicht
mögen, ist aber so und wird so verwendet. Deutsch ist auch nicht die
einfachste aller Sprachen, wird aber von uns trotzdem verwendet.
Michael R. schrieb:> KaUnd das nicht weil es hipp ist oder war,> sondern weil es in der Mehrzahl der Fälle sinnvoll ist.
Es war damals nicht unüblich, Bytes seitens der Maschine automatisch zu
einem Wort zu erweitern, wenn sie aus dem Speicher geladen wurden - mal
vorausgesetzt, es gab überhaupt Byteadressierung. So auch die PDP11, die
in der Entstehungsphase eine nicht unmassgebliche Rolle spielte.
Karl schrieb:> Jobst Q. schrieb:>> Die einzig sinnvolle Verwendung des>> bitweisen NOT '~'...>> ... die Du Dir vorstellen kannst. Für Deinen beschränkten Horizont kann> ja sonst keiner was.
Natürlich kann ich mir mehr vorstellen, aber eben nichts sinnvolles.
Invertieren kann man auch mit XOR '^', sogar ganz beliebige Bits.
c^=0xFF;
Invertiert genau ein Byte, ohne dass die Typlänge eine Rolle spielt.
A. K. schrieb:> C ist definitiv nicht die bestmögliche aller Welten. Muss man nicht> mögen, ist aber so und wird so verwendet.
Ein wahres Wort!
Um nochmal bei Karl zu bleiben: Karl mag C nicht. Ich mag Python nicht.
Karl wird seine Gründe haben, zwischen mir und Python steht (vor allem)
die off-side rule (Einrückung). Damit kann ich nicht.
Ich würde aber nie so weit gehen, das als Design-Fehler zu bezeichnen.
Es ist nur ein (legitimes) Design welches mir nicht gefällt.
Weil ich Python nicht mag, verwende ich es nicht. Weil ich es nicht
verwende, weiß ich nichts (oder nur sehr wenig) drüber. Weil ich nichts
drüber weiß, schreibe ich nicht in Python-Threads mit (und schon gar
nicht mokiere ich mich dort über Design-Fehler).
Bei Karl scheint da irgendwas anders zu sein ;-)
Nun, ich mag Python auch nicht, genau aus genanntem Grund.
Im Gegensatz zum Einrücken bei Python, welches offensichtlich ist, ist
obiger Fehler in C eben nicht offensichtlich.
Der Compiler macht hier etwas, was zu einer der Intention - nicht zu
Verwechseln mit Indention - des Codes widersprechenden Aussage führt,
aufgrund einer Typwandlung, die im Verborgenen erfolgt.
Da weiter oben der FPC erwähnt wurde: Der FPC auf dem PC kompiliert das
gar nicht erst, man müsste ihn durch einen expliziten Typecast dazu
zwingen. Und der FPC für Embedded AVR kompiliert das plattformkonform
mit 8 Bit, und das Ergebnis ist korrekt.
Geht also durchaus.
Mutter Teresa sagte mal:
> Wenn du die Menschen verurteilst,> hast du keine Zeit, sie zu lieben.
Das gilt natürlich auch, im übertragenen sinne, für vieles andere.
z.B. für Computersprachen.
(auch wenn sie das nicht direkt damit gemeint haben sollte)
Michael R. schrieb:> Karl schrieb:>> Nein, was Du für Integer Promotion hältst ist einfach nur die Eigenheit,>> erstmal alles in 16 Bit zu pressen, ...>> ...> es wird mitnichten auf 16 bit, sondern auf die native Wortbreite (aber> mindestens 16 bit) erweitert.
Genau dieses "aber mindestens 16 bit" ist jedoch ein Punkt, wo ich Karl
teilweise recht gebe.
Der ursprüngliche Grund, die Größe von int und damit den defaultmäßigen
Wertebereich für Integer-Berechnungen an die Wortbreite des verwendeten
Prozessors anzugleichen, liegt schlicht im Wunsch nach maximaler
Performanz. Von diesem Standpunkt aus gesehen müsste bei einem
AVR-C-Compiler int 8 bit breit sein, denn ein AVR rechnet in 8-Bit-
Arithmetik deutlich schneller als in 16 Bit.
Der Grund, warum für int trotzdem mindestens 16 Bit vorgeschrieben sind,
liegt IMHO vor allem darin, dass einige als int deklarierte Funktionen
der Standardbibliothek (bspw. fgetc) einen Wertebereich von mehr als
0..255 voraussetzen. Die Signatur dieser Funktionen wurden bereits
festgelegt lange bevor der erste C-Compiler für einen 8-Bit-Prozessor
geschrieben wurde.
Dieses Problem wurde dadurch umgangen, dass 16 Bit als minimale Größe
von int festgesetzt wurde. Das führte bei den ersten C-Compilern für
8-Bit-Prozessoren tatsächlich zu Performanzeinschränkungen, die aber in
neueren, stark optimierenden Compilern weitgehend behoben sind.
Dennoch bleiben ein paar wenige Fälle, wo auf 8-Bit-Prozessoren die
Integer-Promotion lästig ist. Da diese Fälle selten sind und i.Allg.
durch Umschrieben des betreffenden Ausdrucks oder Einfügen von Casts
leicht abgehandelt werden können, besteht für mich keinerlei Grund, auf
8-Bit-Prozessoren in einer anderen Sprache als C (oder allenfalls noch
Assembler) zu programmieren.
An dem von Karl angeprangerten Fall
1
irgendwas1==~irgendwas2
störe ich mich überhaupt nicht, da ich einen solchen Ausdruck noch nie
gebraucht habe, obwohl schon seit vielen Jahren Mikrocontroller aus
vielen unterschiedlichen 8-, 16- und 32-Bit-Familien in C programmiere.
Übrigens kann man beim AVR-GCC die int-Größe mit -mint8 auf nicht 8 bit
einstellen. Dann verhält er sich wie von Karl gewünscht, allerdings ist
er damit nicht mehr ISO-konform, und die Standardbibliothek kann nicht
mehr verwendet werden bzw. müsste angepasst und neu gebaut werden.
@Karl:
Bisher hast du dich ja um die Beantwortung der bereits mehrfach
gestellten Frage nach dem von dir verwendeten Compiler erfolgreich
herumgedrückt:
W.S. schrieb:> Meinst du Mikroe oder was anderes?Yalu X. schrieb:> Das sieht nach Pascal aus, aber welcher Dialekt bzw.Compiler?Arduino F. schrieb:> Zeige mir bitte deinen eigenen "besseren" Kompiler.
Aber so langsam scheint Licht ins Dunkel zu kommen:
Karl schrieb:> Da weiter oben der FPC erwähnt wurde: Der FPC auf dem PC kompiliert das> gar nicht erst, man müsste ihn durch einen expliziten Typecast dazu> zwingen. Und der FPC für Embedded AVR kompiliert das plattformkonform> mit 8 Bit, und das Ergebnis ist korrekt.
Du arbeitest also mit dem AVR-FPC?
Michael R. schrieb:> Weil ich Python nicht mag, verwende ich es nicht. Weil> ich es nicht verwende, weiß ich nichts (oder nur sehr> wenig) drüber. Weil ich nichts drüber weiß, schreibe> ich nicht in Python-Threads mit (und schon gar nicht> mokiere ich mich dort über Design-Fehler).
Das ist menschlich gesehen zwar nobel, sachlich aber
nicht zielführend: Der Fortschritt wird durch die
Unzufriedenen erzwungen :)
> Der Fortschritt wird durch die Unzufriedenen erzwungen
Ich vertrete ja eher die Ansicht, dass Fortschritt meisten zufällig
passiert und der Versuch, ihn zu erzwingen, meistens scheitert.
Yalu X. schrieb:> Der ursprüngliche Grund, die Größe von int und damit den> defaultmäßigen Wertebereich für Integer-Berechnungen an> die Wortbreite des verwendeten Prozessors anzugleichen,> liegt schlicht im Wunsch nach maximaler Performanz.
Der Gedanke lässt sich weiterspinnen:
Wenn die Performanz allein das entscheidende Argument
(gewesen) wäre, hätte sich C nicht so weit verbreiten
dürfen -- dann hätte man nämlich bei Assembler bleiben
müssen.
Es ist daher nicht unsinnig zu vermuten, dass auch die
anderen Eigenschaften von C -- wie die im Vergleich zum
Assembler höhere Abstraktion und die bessere Portabili-
tät -- eine wichtige Rolle gespielt haben.
Spätestens hier sollte aber auffallen, dass logisch
widersprüchliche Ziele vorliegen: Man kann nicht
gleichzeitig maximale Performance (die optimale
Ausnutzung maschinenspezifischer Besonderheiten
erfordert) und maximale Portabilität (die maximale
Unabhängigkeit von maschinenspezifischen Besonder-
heiten notwendig macht) haben -- wenigstens nicht ohne
zusätzlichen Aufwand.
Stefan U. schrieb:>> Der Fortschritt wird durch die Unzufriedenen erzwungen>> Ich vertrete ja eher die Ansicht, dass Fortschritt> meisten zufällig passiert und der Versuch, ihn zu> erzwingen, meistens scheitert.
Unsere Aussagen sind vollständig kompatibel.
Von den 100 Unzufriedenen, die versuchen, den Fortschritt
zu erzwingen, scheitern 99, und nur einer hat Erfolg.
Trotzdem wird der Fortschritt von den Unzufriedenen
getragen -- die anderen haben nämlich kein Motiv, etwas
zu ändern :)
Yalu X. schrieb:> Der ursprüngliche Grund, die Größe von int und damit den defaultmäßigen> Wertebereich für Integer-Berechnungen an die Wortbreite des verwendeten> Prozessors anzugleichen, liegt schlicht im Wunsch nach maximaler> Performanz.
Nö.
Ich verbuche sowas unter Geburtsfehler, von denen C eine Menge hat.
Immerhin versemmelt man sich genau damit aus Sicht der zu lösenden
Probleme jegliche Sicherheit, Allgemeingültigkeit und Portierbarkeit.
Und um die "Performanz" sollte sich der Compiler bzw. dessen Architekt
kümmern.
Die Sicherheit, daß ein Datentyp eben genau definiert ist, sollte man
nicht unterschätzen. Das grundsätzliche Ziel einer Programmiersprache
oberhalb des Assemblers ist es ja gerade, dem Programmierer eine
Sicherhait für seine problemabhängigen Algorithmen zu geben und nicht
etwa, daß er sich mit maschinenabhängigen Besonderheiten herumschlagen
muß. Das ist Obliegenheit der Tools.
Die Sichtweise in C ist da mal wieder nicht die Sicht aus
Anwenderperspektive, sondern die Sicht des Compiler-Erfinders.
Nochwas: Die Maschinen, die ich damals programmiert hatte, konnten
überhaupt keinen byteweisen Zugriff. Der Speicher bestand kompletissimo
aus 16 Bit WORD's und wenn man Text (7 Bit) speichern wollte, dann mußte
man das selbst per Swap in diese 16 Bits einpassen.
So ging das damals - mit Kernspeicher-Maschinen.
W.S.
W.S. schrieb:> Yalu X. schrieb:>> Der ursprüngliche Grund, die Größe von int und damit den defaultmäßigen>> Wertebereich für Integer-Berechnungen an die Wortbreite des verwendeten>> Prozessors anzugleichen, liegt schlicht im Wunsch nach maximaler>> Performanz.>> Nö.
Gibt es deiner Ansicht nach denn einen anderen möglichen Grund für die
maschinenabhängige Größe von int? Oder haben dei C-Entwickler diese
Entscheidung etwa völlig grundlos getroffen?
> Ich verbuche sowas unter Geburtsfehler, von denen C eine Menge hat.
C hat zwar tatsächlich einige Geburtsfehler, aber die maschinenabhängige
int-Größe gehört definitiv nicht dazu.
> Immerhin versemmelt man sich genau damit aus Sicht der zu lösenden> Probleme jegliche Sicherheit, Allgemeingültigkeit und Portierbarkeit.
Sprachen mit guter Portabilität (bspw. Fortran) oder guter Performanz
(Assembler) gab es bereits. C sollte ein guter Kompromiss aus beiden
Kriterien werden. Das ist es IMHO auch geworden, aber es ist und bleibt
eben ein Kompromiss, der in keinem der Kriterien das absolute Optimum
erreicht.
Wem heute bei High-Level-Anwendungen Portabilität über alles geht,
programmiert nicht in C, sondern bspw. in Java. Maschinennahe Software,
hingegen (bspw. ein Betriebssystem) ist per se nur eingeschränkt
portabel, so dass andere Kriterien in den Vordergrund treten, in denen
Java wiederum ganz schlecht abschneidet.
Die Programmiersprache, die für alle Anwendungen vom PID-Regler auf
einem 8-Bit-Controller bis hin zum hochkomplexen KI-System gleichermaßen
optimal geeignet ist, gibt es leider noch nicht und wird es vermutlich
auch nie geben.
> Und um die "Performanz" sollte sich der Compiler bzw. dessen Architekt> kümmern.
Werden die Basisdatentypen erst einmal ungünstig festgelegt, kann das
auch der beste Optimierer nicht mehr richten. Man muss auch sehen, dass
in den 70ern die Compilertechnik noch nicht so weit fortgeschritten war
wie heute, und starke Optimierer, wie wir sie heute gewohnt sind, die
RAM-Kapazität der damaligen Rechner um Größenordnungen sprengen würden.
Yalu X. schrieb:> störe ich mich überhaupt nicht, da ich einen solchen Ausdruck noch nie> gebraucht habe
Ich auch nicht. Das ist ein Beispiel, an dem das Problem kurz und
prägnant deutlich wird.
Die "Zutaten" zu diesem Beispiel allerdings gibt es durchaus: Ein NOT,
um eingelesene low-aktive Pins zu wandeln, eine Konstante, die irgendwo
definiert eine Maske der verwendeten Pins enthält, Prüfen ob keiner der
Pins gesetzt ist, oder alle, oder...
Das ist auch nicht mein Beispiel, das findest Du auf diversen Seiten,
und das ist nicht 8-Bit spezifisch, das funktioniert unter anderem
Systemen ebenso.
Yalu X. schrieb:> Übrigens kann man beim AVR-GCC die int-Größe mit -mint8 auf nicht 8 bit> einstellen.
Wovon mir, als das Thema vor einigen Monaten mal aufkam, vehement
abgeraten wurde, es würde zu vielen Problemen führen.
Damals ging es darum, dass C viele Operationen unnötigerweise auf 16 Bit
aufbläht, wenn 8 Bit auch reichen würden.
Karl schrieb:> Damals ging es darum, dass C viele Operationen> unnötigerweise auf 16 Bit aufbläht, wenn 8 Bit> auch reichen würden.
Das Problem hat FreePascal eine Etage höher (32bit
vs. 64bit) aber auch.
Ich bin grundsätzlich ein Freund starker Typisierung,
aber das Problem ist mMn damit nicht lösbar.
Karl schrieb:> Damals ging es darum, dass C viele Operationen unnötigerweise auf 16 Bit> aufbläht, wenn 8 Bit auch reichen würden.
Als C definiert wurde kam niemand auf die Idee, es wäre etwas für 8 Bit
Mikroprozessoren. Es gab keine. Die kleinste Klasse von Rechnern, auf
denen ein C Compiler sinnvoll schien, waren 16-Bit Minicomputer. Es
dauerte Jahrzehnte, bis sich C als Crosscompiler-Sprache für 8-Bit
Mikrocomputer verbreitete.
A. K. schrieb:> Karl schrieb:>> Damals ging es darum, dass C viele Operationen>> unnötigerweise auf 16 Bit aufbläht, wenn 8 Bit>> auch reichen würden.>> Als C definiert wurde kam niemand auf die Idee,> es wäre etwas für 8 Bit Mikroprozessoren. Es gab> keine.
Kopf-an-Kopf-Rennen:
C - 1972 erschienen
i8008 - 1972 erschienen
i8080 - 1974 erschienen
Z80 - 1976 erschienen
> Die kleinste Klasse von Rechnern, auf denen ein C> Compiler sinnvoll schien, waren 16-Bit Minicomputer.
Ja - wegen der klassischen Bindung von C an Unix.
> Es dauerte Jahrzehnte, bis sich C als Crosscompiler-> Sprache für 8-Bit Mikrocomputer verbreitete.
... was man als Beweis dafür ansehen kann, dass frühe
Fehlentscheidungen NICHT ewigen Bestand haben müssen,
sondern auch korrigiert werden können.
Karl schrieb:> Yalu X. schrieb:>> Übrigens kann man beim AVR-GCC die int-Größe mit -mint8 auf nicht 8 bit>> einstellen.>> Wovon mir, als das Thema vor einigen Monaten mal aufkam, vehement> abgeraten wurde, es würde zu vielen Problemen führen.
Ich habe zwar nicht viel Erfahrung mit -mint8, aber solange du in deinen
Programmen keinen Fremdcode benutzt, der nicht für eine int-Größe von 8
bit geschrieben ist (also auch nicht die AVR Libc), dürften IMHO keine
größeren Probleme entstehen.
Hier ist übrigens noch ein kleines Beispiel für den AVR-FPC, dessen
Verhalten auch nicht immer ganz der menschlichen Intuition entspricht:
1
procedure test;
2
var
3
w: word;
4
b: byte;
5
6
begin
7
8
{ Wegen $e0 + $40 > $ff wird die not-Operation 16-bit-breit ausgeführt }
9
w := not ($e0 + $40); { -> $fedf }
10
do_something_with(w);
11
12
{ Wird $e0 durch eine Byte-Variable mit gleichem Inhalt ersetzt, wird
13
die not-Operation nur noch 8-bit-breit ausgeführt }
14
b := $e0;
15
w := not ( b + $40); { -> $00df }
16
do_something_with(w);
17
18
end;
Vom FPC generierter Assemblercode (R1 hat den Inhalt 0):
1
PsTEST_ss_TEST:
2
ldi r24,-33
3
ldi r26,-2
4
mov r25,r26
5
call PsTEST_ss_DO_SOMETHING_WITHsWORD
6
ldi r18,-32
7
ldi r24,-33
8
mov r25,r1
9
jmp PsTEST_ss_DO_SOMETHING_WITHsWORD
Leider konnte ich in der Dokumentation des FPC keine Erklärung für
dieses Verhalten finden.
In C liefern beide Ausdrücke das gleiche Ergebnis:
Possetitjel schrieb:> Das Problem hat FreePascal eine Etage höher (32bit> vs. 64bit) aber auch.
Auf dem PC sehe ich ja ein, dass es sinnvoller ist die Bitbreite der
Matheunit voll auszunutzen, da geht eine Division mit 32 oder 64 Bit
auch schnell.
Allerdings ist Pascal hier wenigstens konsequent und sagt: Wenn wir auf
64 Bit Breite rechnen, dann alles, und wenn Du das anders willst musst
Du das explizit casten. C wandelt halt einfach nach Belieben.
Auf dem AVR hab ich mir meine eigenen Matheroutinen geschrieben. Da
Pascal Multiplikation oder Division immer auf gleicher Bitbreite macht,
wird unnötig viel Overhead erzeugt, genau wie bei C. Wenn ich aber nur
16bitx16bit=>32bit oder 32bit/8bit=>32bit brauche, spare ich enorm. Eine
Division in 5usec statt in 200usec ist schon ein Nummer.
Aber wenigstens versucht Pascal nicht meinem 8-Bitter unbedingt 16 Bit
aufzunötigen.
Was mir unter Pascal deutlich besser gefällt ist die Matheunterstützung.
Letztens hab ich einen - aktuellen - Beitrag über C gelesen, wo als Tipp
kam: Nehmen Sie mathematische Funktionen möglichst auseinander. Nur eine
Operation pro Zeile. Da dachte ich so: Echt jetzt? Euer Ernst? Leider
finde ich die Seite nicht mehr, vielleicht wurde sie aus Scham gelöscht.
War das bei heise...?
Karl schrieb:> Letztens hab ich einen - aktuellen - Beitrag über C gelesen, wo als Tipp> kam: Nehmen Sie mathematische Funktionen möglichst auseinander. Nur eine> Operation pro Zeile. Da dachte ich so: Echt jetzt? Euer Ernst?
Das denke ich auch ;-)
Bist du sicher, dass es um C und nicht um Bascom ging?
Bei letzterem muss man sogar für jede Rechenoperation eine eigene
Anweisung schreiben.
> Leider> finde ich die Seite nicht mehr, vielleicht wurde sie aus Scham gelöscht.
So wird es wohl sein ;-)
Yalu X. schrieb:> Hier ist übrigens noch ein kleines Beispiel für den AVR-FPC, dessen> Verhalten auch nicht immer ganz der menschlichen Intuition entspricht:
not($E0 + $40) gibt bei mir einen fetten range check error. Ich muss das
explizit auf uint16 casten. Und dann isses klar, einmal wird das Not auf
ein Word angewendet, einmal auf ein Byte, und dann erst auf ein Word
erweitert.
Karl schrieb:> Yalu X. schrieb:>> Hier ist übrigens noch ein kleines Beispiel für den AVR-FPC, dessen>> Verhalten auch nicht immer ganz der menschlichen Intuition entspricht:>> not($E0 + $40) gibt bei mir einen fetten range check error.
Stimmt, auch wenn "fett" etwas übertrieben ist, denn es erscheint
lediglich eine Warnung, die den Kompiliervorgang nicht abbricht.
Sie kommt daher, dass der Compiler das Ergebnis $fedf als negativ
ansieht, so dass es außerhalb des Wertebereichs von word liegt.
> Ich muss das explizit auf uint16 casten.
Die Warnung verschwindet auch ohne Cast, wenn man für w einen
passenderen Datentyp wählt. Ersetze also
1
w: word;
durch
1
w: smallint;
so dass w auch negative Werte annehmen kann. Die Ergebnisse sind aber
immer noch verschieden. Es ändert sich nicht einmal der generierte
Assemblercode.
Was nun?
Yalu X. schrieb:> Die Ergebnisse sind aber immer noch verschieden. Es> ändert sich nicht einmal der generierte Assemblercode.>> Was nun?
Naja, schätzungsweise ist "not" einfach überladen.
Anders ausgedrückt: "$E0 + $40" liefert dieser
Vermutung nach im ersten Beispiel nicht deswegen ein
word, weil es größer als 255 ist, sondern weil als
Ergebnis ein Word erwartet wird. (Die Konstanten
selbst haben ja in Pascal, wenn ich mich recht
entsinne, keinen Typ.)
Das kannst Du ja leicht prüfen: Sieh' einfach, was
bei "$30 + $50" passiert, und berichte.
Analog im zweiten Beispiel: Byte plus Konstante gibt
erstmal ein Byte; der unäre Operator "not" macht ein
Byte aus dem Byte, und die Zuweisung expandiert auf
das Word. Genau das kommt heraus.
Ich habe da jetzt nicht tiefer drübernachgedacht,
aber ich finde das auch völlig logisch so.
Possetitjel schrieb:> Analog im zweiten Beispiel: Byte plus Konstante gibt> erstmal ein Byte; der unäre Operator "not" macht ein> Byte aus dem Byte, und die Zuweisung expandiert auf> das Word. Genau das kommt heraus.
Ich bekomme hier noch eine Meise. Warum liefert...
1
program multest;
2
3
var b1, b2, b3 : byte;
4
w : word;
5
begin
6
b1:=100;
7
b2:=200;
8
b3:=1;
9
w:=not((b1*b2)+b3);
10
writeln('w=',w);
11
end.
... als Ergebnis "w=45534" (=0xB1DE)?
Meiner Theorie nach hätte Byte mal Byte plus Byte
wiederum Byte ergeben sollen, so dass ich ein
Ergebnis der Art "0x00??" erwartet hätte.
> Ich habe da jetzt nicht tiefer drübernachgedacht,> aber ich finde das auch völlig logisch so.
Ich nehme das frustriert zurück.
Karl schrieb:> Auf dem PC sehe ich ja ein, dass es sinnvoller ist die Bitbreite der> Matheunit voll auszunutzen, da geht eine Division mit 32 oder 64 Bit> auch schnell.
Dabei geht es nicht nur um (mathematische) Berechnungen; mWn profitieren
alle Register- und Speicheroperationen von der nativen Wortbreite.
> C wandelt halt einfach nach Belieben.
"nach Belieben" wäre mir noch nicht aufgefallen.
> Auf dem AVR hab ich mir meine eigenen Matheroutinen geschrieben.
Das kann aber nicht Sinn der Übung sein, oder?
> Was mir unter Pascal deutlich besser gefällt ist die Matheunterstützung.
Inwiefern? Grad vorher schreibst du, du hättest das alles selbst
geschrieben?
> Damals ging es darum, dass C viele Operationen unnötigerweise auf 16 Bit> aufbläht, wenn 8 Bit auch reichen würden.
ich mach jetzt doch schon einige Jahre in C, etwas kürzer auf dem AVR,
aber ich kann mich nicht erinnnern da auf signifikante Probleme gestoßen
zu sein (was nicht heißt dass solche nicht existieren).
Hast du mal ein wirklich konkretes Beispiel? Also nicht so ein
konstruiertes wie
1
irgendwas1==~irgendwas2
von dem du ja selbst schreibst dass du es noch nie gebraucht hast.
Karl schrieb:> Allerdings ist Pascal hier wenigstens konsequent und sagt: Wenn wir auf> 64 Bit Breite rechnen, dann alles, und wenn Du das anders willst musst> Du das explizit casten. C wandelt halt einfach nach Belieben.
Nein, C macht das gleiche. Gerade aus diesem Grund gibt es die
Integer-Promotion, die dich so stört, doch. Berechnungen werden immer in
int durchgeführt, es sei denn, die Eingangsdatentypen sind größer.
Gleiches gilt entsprechend für den Datentyp von Integerkonstanten.
Deshalb sollte int möglichst die native Breite der CPU sein. Es gibt
allerdings eben die Einschränkung, dass es mindestens 16 Bit breit sein
muss, was für den Spezialfall einer 8-Bit-CPU nicht ideal ist. Die
Compiler-Optimierungen sorgen aber dafür, dass der Impact auf Laufzeit
und Codegröße minimal ist.
Rolf M. schrieb:> Es gibt allerdings eben die Einschränkung, dass es mindestens 16 Bit> breit sein muss, was für den Spezialfall einer 8-Bit-CPU nicht ideal> ist.
...und deshalb dort meist abgestellt werden kann. Für optimierten
8-bit-code.
Wer portable(r) sein will, lässt es.
Possetitjel schrieb:> Kopf-an-Kopf-Rennen:> C - 1972 erschienen> i8008 - 1972 erschienen
1972 erschien das Buch, Sprache und Compiler entstanden logischerweise
vorher. Dass der Dennis Ritchie mit Intel unter einer Decke steckte darf
man wohl ausschliessen.
Aber ein C Compiler, der auf einem 8008 läuft, das wär schon eine
Vorstellung für 1972 ;-). Immerhin entstand C als Sprache eines
laufenden Systems, nicht als Crosscompiler-Sprache für Drittsysteme, wie
es heute bei Mikrocontrollern üblich ist.
Possetitjel schrieb:> Ich bekomme hier noch eine Meise. Warum liefert...> program multest;>> var b1, b2, b3 : byte;> w : word;> begin> b1:=100;> b2:=200;> b3:=1;> w:=not((b1*b2)+b3);> writeln('w=',w);> end.>> ... als Ergebnis "w=45534" (=0xB1DE)?>> Meiner Theorie nach hätte Byte mal Byte plus Byte> wiederum Byte ergeben sollen, so dass ich ein> Ergebnis der Art "0x00??" erwartet hätte.
Das zeigt doch schön, das dass grundsätzliche Problem weder bei C noch
bei Pascal liegt, sondern beim unären Operator 'not' bzw '~'. Sein
Verhalten ist naturgemäß typgebunden.
Wie ich schon weiter oben geschrieben habe, ist seine einzig sinnvolle
Anwendung das Zurücksetzen von Bits zusammen mit 'and' bzw '&', wodurch
die Typgebundenheit aufgehoben wird.
Wenn es um Invertierung geht, ist der binäre Operator 'xor' bzw '^'
immer die bessere und sicherere Wahl.
Possetitjel schrieb:>> Die kleinste Klasse von Rechnern, auf denen ein C>> Compiler sinnvoll schien, waren 16-Bit Minicomputer.>> Ja - wegen der klassischen Bindung von C an Unix.
Weniger. Sondern weil man oft nur den einen Rechner hatte, nämlich den
Zielrechner. Auf dem sollte der Compiler laufen, Unix oder nicht. So
wars bei mir Ende der 70er auch, Compiler (PL/65) und Assembler liefen
auf dem 6502 Rechner selbst. Einen anderen hatte ich nicht.
Mikrocontroller mit Entwicklungssystem auf einem PC gab es sinngemäss in
den 70ern zwar auch. Minicomputer mit Assembler und ggf. Compiler als
Entwicklungssystem für Mikrocomputer und Mikrocontroller. Aber in dieser
Szene war C völlig unbekannt.
Jobst Q. schrieb:> Das zeigt doch schön, das dass grundsätzliche Problem weder bei C noch> bei Pascal liegt, sondern beim unären Operator 'not' bzw '~'. Sein> Verhalten ist naturgemäß typgebunden.
Hier noch ein Beispiel ohne Bitoperationen, sondern nur mit gewöhnlichen
Additionen:
1
procedure test;
2
const
3
b1 = 100;
4
5
var
6
b2: byte;
7
i: smallint;
8
9
begin
10
i := b1 + 200; { -> 300 }
11
do_something_with(i);
12
13
b2 := 100;
14
i := b2 + 200; { -> 44 }
15
do_something_with(i);
16
end;
Wie es scheint, liegt dieses (zumindest für C-Programmierer seltsam
anmutende) Verhalten an der Auswertung konstanter Ausdrücke:
Solche Ausdrücke werden durch den Compiler ausgewertet, wobei ein
Wertebereich von -2⁶³..+2⁶⁴-1 (das ist die Vereinigung von int64 und
uint64) abgedeckt wird. Wird dieser Wertebereich überschritten, meldet
der Compiler einen Overflow-Error, ansonsten ist das Ergebnis immer
mathematisch korrekt. Anders als in C wird das Ergebnis also nicht auf
eine bestimmte Bitbreite beschnitten.
Beispiel:
1
Ausdruck: 1000 * 1000
2
AVR-FPC: -> 1000000
3
AVR-GCC: -> 16960 (= 1000000 mod 2¹⁶)
Erst nach der Auswertung des konstanten Ausdrucks wird sein Typ
bestimmt, und zwar abhängig vom berechneten Wert.
Beispiel:
1
Ausdruck Wert Typ
2
——————————————————————————————————————————————
3
10 + 10 20 uint8 bzw. byte
4
10 - 30 -20 int8
5
1000 * 1000 1000000 uint32 bzw. longword
6
usw.
7
——————————————————————————————————————————————
In einem Ausdruck, der auch Variablen enthält, werden zuerst die
konstanten Teilausdrücke ausgewertet und typisiert, dann kommen die in
der FPC-Dokumentation beschriebenen impliziten Typkonvertierungen zur
Anwendung (auf der Seite nach "type conversion" suchen):
https://www.freepascal.org/docs-html/current/ref/refsu4.html#x26-250003.1.1
In C wird für jede Teiloperation erst der Typ des oder der Operanden
bestimmt und danach mit einer von den Operandentypen abhängigen
Bitbreite das Ergebnis berechnet. Der Ablauf ist also genau andersherum
als in Free Pascal. Dieselbe Methode wird auch für variable Ausdrücke
angewandt, die erst zur Laufzeit berechnet werden.
Das führt letztendlich zu den folgenden Eigenschaften der beiden
Methoden:
Free-Pascal:
- Der Wert konstanter Ausdrücke ist immer richtig (oder der Compiler
bricht mit einer Fehlermeldung ab, wenn der Wert nicht mit 64 Bit
darstellbar ist, was aber selten passieren dürfte).
- Ausdrücke mit Variablen können zu abweichenden Ergebnissen führen, da
dort andere Auswertungsregeln gelten.
C:
- Der Wert wird bei Überläufen beschnitten und ist deswegen nicht immer
mathematisch korrekt.
- Das Ergebnis eines Ausdrucks hängt nur von dessen Operandenwerten und
-typen ab, ist aber unabhängig davon, ob es sich bei den Operanden um
Konstanten oder Variablen handelt.
Beide Sprachen haben also ein paar Überraschungen parat. Wenn man die
genannten Regeln genau kennt, kann man mit beiden Methoden gut leben.
Deswegen sollte man sich das Regelwerk einmal genau durchlesen, die
Frage ist nur, wo:
- Bei Free Pascal habe ich in der Dokumentation immer noch keine
Beschreibung der Auswertung und Typisierung konstanter Ausdrücke
gefunden, aber vielleicht habe ich auch einfach nur Tomaten auf den
Augen :)
Die ISO-Normen für Pascal und Extended Pascal stellen leider
keine Hilfe bei Fragen zu aktuellen Pascal-Implementationen dar.
- Für C gibt es viel mehr Informationsquellen (sowohl auf Papier als
auch im Netz). Im Zweifelsfall kann man die ISO-Norm zu Rate ziehen,
die solche (und andere) Dinge naturgemäß mit absoluter Präzision
beschreibt.
Jobst Q. schrieb:>> Meiner Theorie nach hätte Byte mal Byte plus Byte>> wiederum Byte ergeben sollen, so dass ich ein>> Ergebnis der Art "0x00??" erwartet hätte.>> Das zeigt doch schön, das dass grundsätzliche Problem> weder bei C noch bei Pascal liegt, sondern beim unären> Operator 'not' bzw '~'.
Hmm. Weiss nicht. Vielleicht ist das Problem nur mein
schlechtes Gedächtnis. Ich war GANZ sicher, das TurboPascal
bei
1
2
b1:=100;
3
b2:=200;
4
w:=b1*b2;
als Ergebnis "32" geliefert hätte. FreePascal liefert jedoch
20'000.
> Sein Verhalten ist naturgemäß typgebunden.
Sicher -- aber lt. Online-Hilfe ist "not" nur für Bytes (!)
definiert.
Im dargestellten Beispiel
1
2
b1:=100;
3
b2:=200;
4
b3:=1;
5
w:=not((b1*b2)+b3);
kommt lt. Test "w=45534" heraus, was das Negat von 20001 ist.
Wie kann es sein, dass in einem Ausdruck, der angeblich nur
aus Byte-Operanden und Byte-Operatoren besteht, bereits die
einzelnen Faktoren der Multiplikation (!!) auf 16bit erweitert
werden, obwohl in dem arithmetischen Ausdruck nichts steht,
das auch nur entfernt nach 16Bit aussieht?
Wer die integer promotion von C nicht mag - ist der Umgang von Free
Pascal mit ganzzahligen Rechnungen tatsächlich einfacher?
https://www.freepascal.org/docs-html/current/ref/refsu4.html#x26-260003.1.1Possetitjel schrieb:> Wie kann es sein, dass in einem Ausdruck, der angeblich nur> aus Byte-Operanden und Byte-Operatoren besteht, bereits die> einzelnen Faktoren der Multiplikation (!!) auf 16bit erweitert> werden, obwohl in dem arithmetischen Ausdruck nichts steht,> das auch nur entfernt nach 16Bit aussieht?
Das geschieht gemäss obiger Referenz, wenn die "native integer size" 16
Bits beträgt:
`Every platform has a ”native” integer size, depending on whether the
platform is 8-bit, 16-bit, 32-bit or 64-bit. e.g. On AVR this is 8-bit.
Every integer smaller than the ”native” size is promoted to a signed
version of the ”native” size. Integers equal to the ”native” size keep
their signedness.´
Wer das alles zu unintuitiv ist, der kann sich bei PL/I umsehen. Da legt
man bei der Deklaration von Variablen die Anzahl Bits vor und nach dem
Komma fest, "fixed binary (15,0)" ist eine 16 Bit Integer in binärer
Darstellung - das Vorzeichen geht extra, 15 Bits insgesamt, davon 0 nach
dem Komma.
Bei den Operatoren darf man dann kopfrechnen. Wenn man fixed(P,Q) und
fixed(R,S) addiert oder subtrahiert, dann kommt ein
fixed(MIN(N,1+MAX(P-Q,R-S)+MAX(Q,S)),MAX(Q,S)) raus, mit N als
implementiertem Maximum. Multiplikation ist natürlich einfacher:
fixed(MIN(N,P+R+1),Q+S). Also solange es in die Grenzen der
Implementierung passt gibts keinen Überlauf.
http://documentation.microfocus.com/help/topic/com.microfocus.eclipse.infocenter.studee60ux/BKPFPFEXPRMATHOPS.html
Bei Bitoperation war man ebenfalls sehr korrekt. Das macht man nicht mit
Zahlen, sondern mit Bitstrings deklarierter Länge. Und weil das eben
Strings sind, erweitern Operatoren auf Operanden unterschiedlicher Länge
den kürzeren String linksbündig. '10000'B | '01'B ergibt '11000'B
http://documentation.microfocus.com/help/topic/com.microfocus.eclipse.infocenter.studee60ux/BKPFPFEXPRS011.html
A. K. schrieb:> Das geschieht gemäss obiger Referenz,
Vielen Dank für den Link.
> wenn die "native integer size" 16 Bits beträgt:>> `Every platform has a ”native” integer size, [...]
Oh Gott. Nicht auch Pascal... !
Possetitjel schrieb:>> `Every platform has a ”native” integer size, [...]>> Oh Gott. Nicht auch Pascal... !
;-)
Nur dass Free Pascal auch 8 Bits als native integer zulässt.
Aber es ist schon ein wenig anders als in C. Komplizierter nämlich.
Beispielsweise weil es Ausnahmen gibt, etwa indem die Differenz zweier
vorzeichenloser Typen ein Vorzeichen hat. Und wenn man Typen mit und
ohne Vorzeichen mischt, kann es je nach Grösse auf ein Ergebnis
rauslaufen, das doppelt so gross ist. Beides ist nicht unlogisch, aber
kompliziert. Und natürlich ist das abhängig vom konkreten Pascal, Delphi
anders als Turbo Pascal.
Jobst Q. schrieb:> Wie ich schon weiter oben geschrieben habe, ist seine einzig sinnvolle> Anwendung das Zurücksetzen von Bits zusammen mit 'and' bzw '&', wodurch> die Typgebundenheit aufgehoben wird.
Ja klar, alle blöd ausser Du...
Die wohl berühmteste Routine des Forums:
https://www.mikrocontroller.net/articles/Entprellung#Timer-Verfahren_.28nach_Peter_Dannegger.29
Siehe unten bei C-Code.
Jobst Q. schrieb:> Wenn es um Invertierung geht, ist der binäre Operator 'xor' bzw '^'> immer die bessere und sicherere Wahl.
Schon deswegen nicht, weil xor immer eine Hilfsvariable braucht.
Michael R. schrieb:> Hast du mal ein wirklich konkretes Beispiel? Also nicht so ein> konstruiertes wie
Ich weiss ja nicht, ob Dir das konstruiert ist:
[code]void test(void) {
char a;
a = a * 4;
a = a << 2;
}
ldd r24,Y+1
lsl r24
lsl r24
std Y+1,r24
ldd r24,Y+1
clr r25
sbrc r24,7
com r25
lsl r24
rol r25
lsl r24
rol r25
std Y+1,r24[code]
Ich hab das damals mit uint8_t gemacht, aber das nimmt der GCC im
Codeexplorer nicht an, und AvrStudio 7 habe ich wegen exzessiver
Platzverschwendung von der Platte gehauen.
Yalu X. schrieb:> Erst nach der Auswertung des konstanten Ausdrucks wird sein Typ> bestimmt, und zwar abhängig vom berechneten Wert.
Und das ist gut so:
1
const
2
Cfcpu = 16000000; // 16MHz Quarz
3
...
4
const
5
fmul = 1 * Cfcpu div 1000000;
Bin ich ganz froh, das fmul hier nur ein Byte ist und nicht 4 Bytes.
A. K. schrieb:> Wer die integer promotion von C nicht mag - ist der Umgang von Free> Pascal mit ganzzahligen Rechnungen tatsächlich einfacher?
Ähm nein, da ist auch historisch viel Mist gewachsen. Deswegen verwende
ich für den AVR prinzipiell nur uint8, uint16, int8, int16. Auf dem PC
allerdings habe ich mir angewöhnt, dass ein Zähler von 1 bis 10 durchaus
integer sein darf, und damit je nach Maschine 32 oder 64 Bits, weil es
eh keinen Unterschied macht.
A. K. schrieb:> Da legt> man bei der Deklaration von Variablen die Anzahl Bits vor und nach dem> Komma fest, "fixed binary (15,0)" ist eine 16 Bit Integer in binärer> Darstellung - das Vorzeichen geht extra, 15 Bits insgesamt, davon 0 nach> dem Komma.
Ja und, Festkommazahl, geht bei Ada auch und für FreePascal gibts glaub
ich eine Lib für. Der AVR kann das ja sogar bedingt, allerdings scheint
es so ein Hirnkrampf für den Programmierer zu sein, dass es kaum jemand
nimmt.
Karl schrieb:> const> fmul = 1 * Cfcpu div 1000000;>> Bin ich ganz froh, das fmul hier nur ein Byte ist und> nicht 4 Bytes.
Verstehe ich nicht. Würde Dir die Hand abfaulen, wenn Du
Karl schrieb:> Ja und, Festkommazahl, geht bei Ada auch und für FreePascal gibts glaub> ich eine Lib für.
Es ging mir dabei weniger um die Nachkommastellen. Sondern darum, dass
das Ergebnis einer Addition von zwei 16-Bit Operanden ein Typ mit 17
Bits war. Das war die normale Integer-Rechnung, eine andere gab es
nicht.
Karl schrieb:> Und das ist gut so:> const> Cfcpu = 16000000; // 16MHz Quarz> ...> const> fmul = 1 * Cfcpu div 1000000;> Bin ich ganz froh, das fmul hier nur ein Byte ist und nicht 4 Bytes.
fmul ist eine Compilezeitkonstante. Die belegt auf dem Zielsystem kein
einziges Byte.
Michael R. schrieb:> Inwiefern? Grad vorher schreibst du, du hättest das alles selbst> geschrieben?
Beides.
Ich habe ein Projekt mit mehreren umfangreichen Berechnungen von C auf
Pascal übertragen und meiner Beobachtung nach produziert hier Pascal
besseren Code bzw. nutzt Register besser aus für die gleichen
Berechnungen.
Allerdings setzt Pascal genau wie C einige Berechnungen zu aufwändig um.
Für a32 = b16 x c16 muss zwingend b und c auf 32 Bit erweitert werden
und eine 32 Bit Multiplikation ausgeführt werden. Mit einer 16 Bit
Multiplikation ist a auch nur 16 Bit.
In Assembler kann ich aber b16 und c16 problemlos zu a mit 32 Bit
multiplizieren.
Nun müssen bei 32x32 Bit natürlich selbst unter Verwendung des
Hardware-Multiplizierers deutlich mehr Register rumgeschubst werden als
bei 16 x 16 Bit, einschließlich des pushens und poppens dieser Register,
der längeren Laufzeit und des größeren Speicherbedarfs. Da dauert die
Berechnung durchaus 20mal so lange. Bei Division ist es noch extremer,
da alles in Software.
Und nun kommts drauf an: Muss ich nur ab und zu einen Wert berechnen,
nehm ich die fertigen Routinen. Muss ich was in Echtzeit berechnen, über
viele Werte, sprich es muss einfach schnell sein, nehm ich die
optimierten Assembler-Routinen.
Karl schrieb:> Jobst Q. schrieb:>> Wenn es um Invertierung geht, ist der binäre Operator 'xor' bzw '^'>> immer die bessere und sicherere Wahl.>> Schon deswegen nicht, weil xor immer eine Hilfsvariable braucht.
Meistens keine Variable, sondern eine Konstante. Und mit dieser
Konstante kann man genau bestimmen, wieviele und welche Bits invertiert
werden. Unabhängig vom Compiler, vom Zielprozessor und von irgendwo
festgelegten Typen. Also nur Vorteile.
Jobst Q. schrieb:> Meistens keine Variable, sondern eine Konstante.
Es gibt kein xor mit Konstante auf dem AVR. Du musst eine Konstante
immer erst in ein Register laden und dann kannst Du das Register mit
Deinem Wert ver-xor-en.
Karl schrieb:> Allerdings setzt Pascal genau wie C einige Berechnungen zu aufwändig um.> Für a32 = b16 x c16 muss zwingend b und c auf 32 Bit erweitert werden> und eine 32 Bit Multiplikation ausgeführt werden.
Der AVR-GCC macht das schon seit geraumer Zeit besser, zumindest bei
Controllern mit Hardwaremultiplizierer. Johann, der auch hier im Forum
aktiv ist, hat's gerichtet:
https://gcc.gnu.org/ml/gcc-patches/2011-06/msg02114.html
Die selbstgeschriebene 16-Bit-Multiplikation mit 32-Bit-Ergebnis
benötigst du nur noch für Pascalprogramme.
Jobst Q. schrieb:> Und mit dieser Konstante kann man genau bestimmen,> wieviele und welche Bits invertiert werden.> Unabhängig vom Compiler, vom Zielprozessor und von> irgendwo festgelegten Typen. Also nur Vorteile.
Mit Verlaub -- aber da widerspreche ich.
Von einer Hochsprache, die den Namen verdient, erwarte
ich schon, dass ich die üblichen booleschen Operationen
einfach im Klartext (egal, ob Schlüsselwort oder
Sonderzeichen-Operator) hinschreiben kann -- und nicht
erst den Karnaugh-Plan im Kopf aufstellen muss. Als
"üblich" würde ich mal NOT, AND, OR, XOR auffassen.
(Auf der Siemens-S7-SPS geht das in AWL nicht so einfach;
es ist ein ziemlicher Krampf, dort einen Flankendetektor
zu formulieren, denn (NOT(alt) AND neu) funktioniert nicht.)
Vielleicht übersehe ich ja etwas, aber mir erschließt
sich das fundamentale Problem mit "NOT" nicht. Das ist
ein unärer Operator, der Datentyp des Ergebnisses kann
(=sollte) also derselbe wie der des Operanden sein.
A. K. schrieb:> Possetitjel schrieb:>>> `Every platform has a ”native” integer size, [...]>>>> Oh Gott. Nicht auch Pascal... !>> ;-)>> Nur dass Free Pascal auch 8 Bits als native integer> zulässt.
Naja, ich halte, drastisch formuliert, native integers
generell für Schwachsinn und einen Irrweg.
Hochsprachen sollten die Portabilität fördern -- und
nicht noch dazu einladen, die Plattformabhängigkeiten
gleichmäßig über den Quelltext zu verteilen.
> Beides ist nicht unlogisch, aber kompliziert. Und natürlich> ist das abhängig vom konkreten Pascal, Delphi anders als> Turbo Pascal.
Furchtbar.
Aber an die heilige Kuh, die seit Jahrzehnten tradierte
implizite Modulo-Arithmetik, hat sich wieder einmal niemand
herangetraut. Typisch.
Possetitjel schrieb:> Naja, ich halte, drastisch formuliert, native integers> generell für Schwachsinn und einen Irrweg.
Viele Prozessoren favorisieren bestimmte Datentypen. 32-Bit RISCs tun
sich bei Rechnungen mit 32-Bit Integers leichter als mit kleineren
Typen. Wenn eine Sprache also der Implementierung die Möglichkeit gibt,
in der favorisierten Breite zu rechnen ohne bei jedem Schritt auf
Einhaltung einer kleineren Breite zu bestehen, dann ist das schlicht ein
Tribut an die Effizienz.
A. K. schrieb:> Viele Prozessoren favorisieren bestimmte Datentypen. 32-Bit> RISCs tun sich bei Rechnungen mit 32-Bit Integers leichter> als mit kleineren Typen.
Ja, ich weiss. Das ist mir klar.
> Wenn eine Sprache also der Implementierung die Möglichkeit> gibt, in der favorisierten Breite zu rechnen ohne bei jedem> Schritt auf Einhaltung einer kleineren Breite zu bestehen,> dann ist das schlicht ein Tribut an die Effizienz.
Sicher -- aber dazu ist kein "native integer" notwendig.
Es wäre völlig ausreichend, wenn man beim Definieren
der Variablen keinen "Typ", sondern außer dem Hinweis
"integer" noch den notwendigen Wertebereich angeben könnte:
1
2
var
3
menuepunkt : integer range(1..20);
Das würde genau denselben Effekt erzielen; der Compiler
wäre völlig frei in seiner Entscheidung, auf welchen
plattform-eigenen Typ er diese Variable abbildet -- aber
es wäre dennoch vollständig portabel.
Possetitjel schrieb:> Hochsprachen sollten die Portabilität fördern -- und> nicht noch dazu einladen, die Plattformabhängigkeiten> gleichmäßig über den Quelltext zu verteilen.
in Ergänzung zu A.K.s richtigem Kommentar meine ich, dass "native
integers" genau das tun - Portabilität fördern.
Wenn ich schreibe
1
for(inti=42;i>0;i--){
2
magic_smoke(i)
3
}
dann möchte ich dass auf einer 64-bit-CPU i 64 bit hat, detto mit 32, 16
(mal abgesehen davon dass der hochoptimierende Compiler noch eingreift)
Possetitjel schrieb:> Von einer Hochsprache, die den Namen verdient, erwarte> ich schon, dass ich die üblichen booleschen Operationen> einfach im Klartext (egal, ob Schlüsselwort oder> Sonderzeichen-Operator) hinschreiben kann -- und nicht> erst den Karnaugh-Plan im Kopf aufstellen muss. Als> "üblich" würde ich mal NOT, AND, OR, XOR auffassen.
Die Probleme treten ja nicht bei den boolschen (logischen) Operatoren
auf, sondern bei den Bitoperatoren. C unterscheidet das aus gutem Grund
mit unterschiedlichen Zeichen. ! && || für die Logik und ~ & | ^ für die
Bitmanipulationen.
Bei den Bitoperatoren ist das NOT ~ als unärer ein typabhängiger
Sonderfall. Es hängt vom Typ ab, wieviele Bits betroffen sind. Bei den
binären Operatoren AND, OR und XOR wird das von den Operanden bestimmt.
Deshalb würde ich das ~ vermeiden, so gut es geht, aus Gründen der
Vorhersehbarkeit und der Portabilität, die du ja auch sehr schätzt.
Possetitjel schrieb:> (Auf der Siemens-S7-SPS geht das in AWL nicht so einfach;> es ist ein ziemlicher Krampf, dort einen Flankendetektor> zu formulieren, denn (NOT(alt) AND neu) funktioniert nicht.)
Wieso?
U Neu; UN Alt; = PosFlanke;
UN Neu; U Alt; = NegFlanke;
U Neu; X Alt; = Aenderung;
...
U Neu; = Alt;
Michael R. schrieb:> Possetitjel schrieb:>> Hochsprachen sollten die Portabilität fördern -- und>> nicht noch dazu einladen, die Plattformabhängigkeiten>> gleichmäßig über den Quelltext zu verteilen.>> in Ergänzung zu A.K.s richtigem Kommentar meine ich,> dass "native integers" genau das tun - Portabilität> fördern.
Nur in speziellen Fällen.
> Wenn ich schreibe> for (int i = 42; i> 0; i--) {> magic_smoke(i)> }
Das darf man m.W. in Pascal nicht schreiben; diese
"inline-Deklaration" war zumindest bisher nicht erlaubt.
> dann möchte ich dass auf einer 64-bit-CPU i 64 bit hat,> detto mit 32, 16 (mal abgesehen davon dass der> hochoptimierende Compiler noch eingreift)
Bei allem gebotenen Respekt glaube ich, dass Du EIGENTLICH
etwas anderes willst: Den Integer-Datentyp, der
1. maximale Rechengeschwindigkeit ermöglicht und
2. den notwendigen Zahlbereich abdeckt.
Oder möchtest Du im (leicht modifizierten) Beispiel:
1
2
for (int i = 300; i> 0; i--) {
3
magic_smoke(i)
4
}
auf einer 8-bit-Maschine einen Compilerfehler bekommen?
Ich würde das nicht wollen.
Possetitjel schrieb:> var> menuepunkt : integer range(1..20);> Das würde genau denselben Effekt erzielen; der Compiler> wäre völlig frei in seiner Entscheidung, auf welchen> plattform-eigenen Typ er diese Variable abbildet -- aber> es wäre dennoch vollständig portabel.
Eine solche Spezifikation geht am Problem vorbei. Denn das Problem
besteht nicht in der Breite der Speicherung von Variablen. Sondern in
der Breite der Berechnung von Ausdrücken, die Variablen verwenden. Wenn
du dieses Regelwerk nur von solchen Deklarationen abhängig machst, dann
landest du ungefähr beim erwähnten PL/I, also mit individuellen
Rechenbreiten für jeden Rechenschritt. Nur eben in Wertebereichen, statt
wie in PL/I in Bits oder Digits.
Die Multiplikation zweier [1..100] Variablen ergäbe also ein
Zwischenergebnis [1..10000] und müsste somit mit mindestens 16 Bits
stattfinden. Denn du wirst mit Variablen, die als [1..100] deklariert
sind, wohl kaum meinen, dass dies auch auf das Produkt zutreffen sollte.
Weitere Rechenschritte führen dann zu weiteren möglichen Änderungen in
der Rechenbreite. Wird dabei von der Implementierung vorgegebene
Maximalbreite überschritten - und das ist bei Multiplikationen schnell
der Fall - dann dürfte nicht implizit abgeschnitten werden, denn das
wäre ja nicht portabel. Es liefe also darauf hinaus, bei vielen
Rechenausdrücken für einzelne Zwischenschritte jeweils explizit die
Rechenbreite im Programm anzugeben. Also sowas wie
x *[0..50000] y
statt
x * y
um dem Compiler mitzuteilen, dass er nur mit diesem Wertebereich rechnen
muss, auch wenn die Teilrechnungen hinter x und y eigentlich eher eine
nicht vorhandene 256-Bit Multiplikation erzwängen.
Bei Operationen auf Bits gibts das ähnlich, nämlich bei Linksshifts,
wenn die rechte Seite nicht konstant ist. Da lässt sich die
erforderliche Rechenbreite im Programm nur schlecht statisch aus den
Typen der Teilausdrücke ableiten, so dass auch hier der Programmierer
bei jeder einzelnen solchen Operation explizit abgeben müsste, wie breit
sie durchgeführt werden muss.
Alternativ wären ausschliesslich Berechnungen der Form
a = b <op> c
und
a = <op> c
mit Variablen a,b,c zulässig, weil sich dann aus dem Typ von a,b,c die
Breite der Berechnung ableiten liesse. Komplexere Rechenausdrücke
müssten entsprechend zerlegt werden. Also nix mit a = b * c - d.
Viel Spass.
Possetitjel schrieb:> Oder möchtest Du im (leicht modifizierten) Beispiel:> for (int i = 300; i> 0; i--) {> magic_smoke(i)> }> auf einer 8-bit-Maschine einen Compilerfehler bekommen?
In einer Sprache, in der sich der Typ von "i" aus dem Typ der
Initialisierung und der Verwendung ergibt, liesse sich das in diesem
Beispiel vermeiden. Allerdings müsste dann die interne Darstellung von
"i" in
for (integer i = 100; i > x; i--)
magic_smoke(i)
eigentlich vom Typ von "x" (kann -1000000 sein) und vielleicht auch von
der Parameterdeklaration von magic_smoke abhängen. In komplexeren
Beispielen blickt dann aber schnell niemand mehr durch.
In einem C Statement
for (int i = 300000; i > 0; i--)
würde ich es allerdings schon vorziehen, gewarnt zu werden, wenn int nur
16 Bits haben sollte. Du wirklich nicht?
A. K. schrieb:> Possetitjel schrieb:>> var>> menuepunkt : integer range(1..20);>>> Das würde genau denselben Effekt erzielen; der Compiler>> wäre völlig frei in seiner Entscheidung, auf welchen>> plattform-eigenen Typ er diese Variable abbildet -- aber>> es wäre dennoch vollständig portabel.>> Eine solche Spezifikation geht am Problem vorbei. Denn> das Problem besteht nicht in der Breite der Speicherung> von Variablen. Sondern in der Breite der Berechnung von> Ausdrücken, die Variablen verwenden.
Nee, Moment.
Ich kann Dir folgen -- aber das sind unterschiedliche
Teilprobleme.
Wir waren zwischendurch auf die "native Integers" abge-
schweift, darauf, dass ich sie für einen Irrweg halte, und
Deine Entgegnung, dass man sie aus Performancegründen
dennoch haben will.
Meine Erwiderung DARAUF ist: Die Typsysteme gängiger
Programmiersprachen spezifizieren z.T. das Falsche. Für
eine Laufvariable ist u.U. völlig wurscht, wievel Speicher
sie benötigt -- man will einfach, dass die Kiste schnell
und richtig rechnet. Man sollte daher eine Möglichkeit
schaffen, solche Variablen nach WERTEBEREICH und nicht
nach SPEICHERBEDARF zu deklarieren.
Das bringt nämlich die gegensätzlichen Forderungen nach
Performance und Portabilität unter einen Hut: Wenn den
Programmierer der Speicherbedarf nicht interessiert,
sondern nur ein bestimmter Wertebereich gefordert wird,
dann soll er gefälligst auch den Wertebereich angeben --
und nicht die Speichergröße!
Der Unterschied meines Vorschlages zum "native integer"
ist: Der Compiler kann prüfen, ob der Zahlbereich
ausreicht, und bei Portierung auf eine andere Plattform
mit anderer native-integer-Größe einen geeigenten
non-native-integer wählen!
Das hat aber mit der hauptsächlich diskutierten Frage
nach "integer promotion", impliziter Typkonvertierung
usw. nicht direkt zu tun, das war ein Seitenast.
Thomas H. schrieb:> Zitat von einem unserer Informatiker: "Code soll für> einen selbst übersichtlich und lesbar sein. Optimierung macht der> Compiler".
Sollte über (und unter) jedem Tutorial stehen.
A. K. schrieb:> In einer Sprache, in der sich der Typ von "i" aus> dem Typ der Initialisierung und der Verwendung ergibt,> liesse sich das in diesem Beispiel vermeiden.
Ich möchte nicht missverstanden werden: Es ging mir
nicht darum, die üblichen Typen generell abzuschaffen
und durch die Wertebereichs-Spezifikation oder irgend
eine Art von Heuristik zu ersetzen.
Der Vorschlag war nur, ZUSÄTZLICH eine Typspezifikation
über den Wertebereich zu haben, damit der Programmierer
die (plattformunabhängige) Kontrolle über den Wertebereich
behält, die Auswahl der internen Zahldarstellung aber dem
Compiler überlassen kann.
> Allerdings müsste dann die interne Darstellung von> "i" in> for (integer i = 100; i > x; i--)> magic_smoke(i)> eigentlich vom Typ von "x" (kann -1000000 sein) und> vielleicht auch von der Parameterdeklaration von> magic_smoke abhängen.
Naja, ich hatte eigentlich Pascal vor Augen, wo diese
on-the-fly-Deklarationen m.W. nicht erlaubt sind.
> In komplexeren Beispielen blickt dann aber schnell> niemand mehr durch.
Klar -- deswegen ja mein primitiver Vorschlag, den
gewünschten Wertebereich explizit durch Konstanten
anzugeben.
> In einem C Statement> for (int i = 300000; i > 0; i--)> würde ich es allerdings schon vorziehen, gewarnt zu> werden, wenn int nur 16 Bits haben sollte. Du wirklich> nicht?
Naja, in meinem Universum würde es das Problem nicht
geben, weil in
1
2
var
3
pixelcount : integer range(0..300000);
das "integer" nicht für einen spezifischen Datentyp
stehen sollte, sondern als Aufforderung an den Compiler,
aus den auf der jeweiligen Plattform zur Verfügung
stehenden Datentypen einen auszuwählen, der den geforderten
Zahlbereich -- im Beispiel also 1-300'000 -- abbilden kann.
Auf einer 32bit-Maschine und Optimierung auf Geschwindigkeit
wäre das ganz sicher ein 32bit-native-integer, auf einer
16-bit-Maschine sicherlich irgend etwas anderes.
Programme profitieren auf diese Art bei Portierung
"automatisch" von einer größeren Wortbreite, ohne dass bei
kleineren Prozessoren die Gefahr von unerkannten Überläufen
besteht.
Jobst Q. schrieb:> Possetitjel schrieb:>> (Auf der Siemens-S7-SPS geht das in AWL nicht so einfach;>> es ist ein ziemlicher Krampf, dort einen Flankendetektor>> zu formulieren, denn (NOT(alt) AND neu) funktioniert nicht.)>> Wieso?
Das weiss ich leider nicht mehr genau. Kann sein, dass es
nicht auf der S7, sondern der S5 war; bin nicht sicher.
Der Kernpunkt war jedenfalls, das "not(x)" durch "x xor 1"
nachgebildet werden musste. Natürlich geht das -- aber es
ist doch irgendwie Krampf. Muss das im dritten Jahrtausend
WIRKLICH noch sein?
Yalu X. schrieb:> Der AVR-GCC macht das schon seit geraumer Zeit besser, zumindest bei> Controllern mit Hardwaremultiplizierer. Johann, der auch hier im Forum> aktiv ist, hat's gerichtet:
Anscheinend 2011, der GCC 4.6.4 scheints nicht zu können, der ist von
2013.
Possetitjel schrieb:> Man sollte daher eine Möglichkeit> schaffen, solche Variablen nach WERTEBEREICH und nicht> nach SPEICHERBEDARF zu deklarieren.
Ist das (speziell bei Integer) nicht das Selbe? Zumindest bei mir im
Kopf ist das so... Die Wertebereiche von 8- und 16 bit signed/unsigned
hab ich eingebrannt, bei 32 Bit wirds schon eng ("irgendwas mit 4
vorne") und in 99.999% der Fälle muss ich nicht nachdenken ob ich 0..255
oder 0..65535 oder mehr brauche.
Aber wie oben schon richtig festgestellt wurde, Variablen sind nicht so
schwierig, komplexer sind Ausdrücke. Aber auch hier habe zumindest ich
keine Probleme mit der Art wie C das angeht. Die Fälle wo ich hier
Fehler suchen und durch einen cast beheben musste, kann ich vermutlich
an einer Hand abzählen.
Possetitjel schrieb:> Aber an die heilige Kuh, die seit Jahrzehnten tradierte> implizite Modulo-Arithmetik, hat sich wieder einmal niemand> herangetraut. Typisch.
Erklärst du mir was du hier meinst? Bezieht sich das auf pascal? (dann
muss ichs nicht verstehen, siehe ich & Python)
Karl schrieb:>> Hast du mal ein wirklich konkretes Beispiel? Also nicht so ein>> konstruiertes wie>> Ich weiss ja nicht, ob Dir das konstruiert ist:>>
1
>voidtest(void){
2
>chara;
3
>a=a*4;
4
>a=a<<2;
5
>}
6
>
>>
1
> ldd r24,Y+1
2
> lsl r24
3
> lsl r24
4
> std Y+1,r24
5
> ldd r24,Y+1
6
> clr r25
7
> sbrc r24,7
8
> com r25
9
> lsl r24
10
> rol r25
11
> lsl r24
12
> rol r25
13
> std Y+1,r24[code]
14
>
Wenn ich dein Beispiel so kompiliere, kommt nix raus, a wird nicht
verwendet und deshalb komplett rausoptimiert.
ich habs aber mal leicht modifiziert:
1
charKarl(chara)
2
{
3
a=a*4;
4
a=a<<2;
5
returna;
6
}
Daraus wird bei mir:
1
Karl:
2
swap r24
3
andi r24,lo8(-16)
4
ret
5
.size Karl, .-Karl
keine 16 bit, und optimaler code (ich denke das lässt sich auch von Hand
nicht mehr verbessern)
Das von dir beschriebene Ergebnis kriege ich, wenn ich mit -O0 (disable
all optimizations) kompiliere. Warum machst du das?
Karl schrieb:>> Der AVR-GCC macht das schon seit geraumer Zeit besser, zumindest bei>> Controllern mit Hardwaremultiplizierer. Johann, der auch hier im Forum>> aktiv ist, hat's gerichtet:>> Anscheinend 2011, der GCC 4.6.4 scheints nicht zu können, der ist von> 2013.
Auch -O0 ? Damit löst mein avr-gcc 4.8.1 (auch 2013) das auch noch nach
__mulhi3 auch, mit -Os (was eigentlich Standard auf AVR sein sollte)
aber sehr wohl nach __umulhisi3 (der 32=16*16 widening multiplication)
Possetitjel schrieb:> Der Vorschlag war nur, ZUSÄTZLICH eine Typspezifikation> über den Wertebereich zu haben, damit der Programmierer> die (plattformunabhängige) Kontrolle über den Wertebereich> behält, die Auswahl der internen Zahldarstellung aber dem> Compiler überlassen kann.
Das hatte ich schon verstanden. Aber du stelltest diese Datentypen
direkt in den Kontext deiner Ablehnung von native integers und eine
solche (sinnvolle) Typdeklaration ändert nichts daran, dass man Regeln
benötigt, in welcher Breite sowohl damit als auch mit normalen Integers
gerechnet werden soll.
Pascals native Integers sind kein Aspekt der Speicherung von Daten,
sondern betreffen nur die Breite der Berechnung in Ausdrücken. Mit einer
möglichen Wertebereichsdefinition von Variablen wirst du sie nicht los.
> Naja, ich hatte eigentlich Pascal vor Augen, wo diese> on-the-fly-Deklarationen m.W. nicht erlaubt sind.
Solche Details sind für Grundfragen zur Typisierung von Integers
irrelevant. In C geht das auch erst seit C99.
> stehenden Datentypen einen auszuwählen, der den geforderten> Zahlbereich -- im Beispiel also 1-300'000 -- abbilden kann.
Damit beschreibst du wieder die Festlegung von Variablen ...
> Auf einer 32bit-Maschine und Optimierung auf Geschwindigkeit> wäre das ganz sicher ein 32bit-native-integer, auf einer> 16-bit-Maschine sicherlich irgend etwas anderes.
... um sofort zur Breite der Berechnung von Ausdrücken umzuschwenken.
Denn eine 32-Bit Maschine kann (heute) Daten im RAM stets auch kleiner
speichern, tut sich aber im Umgang mit diesen Daten evtl leichter, wenn
sie dafür auf native integer erweitert werden.
Die sinnvolle Möglichkeit, [1..100] im Array als Byte zu speichern, in
einer explizit definierten lokalen Variable, die im Register liegt, aber
in voller Wortbreite, erspart nicht die Festlegung, in welche Breite man
mit diesen Daten umgehen sollte. Und da kommen die native integers in
Spiel.
A. K. schrieb:> PS: Da war doch was... Deine Wertebereichstypen gibts in Pascal doch> sowieso schon seit Anbeginn der Zeit.
Genau.
Und auch in C gibt es mit [u]int_fast<n>_t und [u]int_least<n>_t
flexible Datentypen, mit denen auf portable Weise Wertebereiche für
Variablen festgelegt werden können, zwar nicht so feingranular wie in
Pascal, dafür kann man mit "fast" oder "least" angeben, ob man lieber
Rechenzeit oder Speicherplatz sparen möchte.
Possetitjel schrieb:> A. K. schrieb:>> Eine solche Spezifikation geht am Problem vorbei. Denn>> das Problem besteht nicht in der Breite der Speicherung>> von Variablen. Sondern in der Breite der Berechnung von>> Ausdrücken, die Variablen verwenden.>> Nee, Moment.>> Ich kann Dir folgen -- aber das sind unterschiedliche> Teilprobleme.
Das erste Teilproblem, um das es dir schwerpunktmäßig geht, sehe ich in
Pascal und C als weitgehend gelöst an.
Das zweite, viel schwierigere Teilproblem hat A. K. diesem Beitrag sehr
gut umrissen:
A. K. schrieb:> Denn das Problem besteht nicht in der Breite der Speicherung von> Variablen. Sondern in der Breite der Berechnung von Ausdrücken, die> Variablen verwenden.> ...
Ich kenne keine (auch keine exotische) Programmiersprache, in der dieses
Problem zufriedenstellen gelöst wäre.
Interessant ist, dass selbst eine extrem "maschinenferne" Sprache wie
Haskell neben Integer-Typen mit fester und dynamischer Bitbreite einen
plattformspezifischen Integer-Typ (Int) hat. Im Gegensatz zu C wird Int
für Rechenoperationen aber nur dann verwendet, wenn auch die Operanden
von diesem Typ sind.
Yalu X. schrieb:> Ich kenne keine (auch keine exotische) Programmiersprache, in der dieses> Problem zufriedenstellen gelöst wäre.
ich denke das liegt auch daran, dass dieses Problem von Der Sprache bzw.
dem Compiler gar nicht gelöst werden kann; hier sehe ich den
Programmierer in der Pflicht. Nur er (oder sie) kann die Wertebereiche
abschätzen, und entsprechend reagieren.
Das zeigt sich schon bei einer einfachen Multiplikation: Die Bitbreite
des Ergebnisses ergibt sich aus der Summe der Bitbreiten der Operanden.
Wenn ich also zwei 16-bit-Werte multipliziere, müsste ich die Operation
in 32 bit ausführen, um sicher keinen Überlauf zu erhalten.
Wenn ich aber weiß (woher auch immer), dass das Ergebnis immer in 16
bit Platz finden wird, kann ich die Multiplikation in 16 bit ausführen,
was (zB am AVR) schneller und kürzer ist.
Dieses Wissen kann mir aber keine Sprache und kein Compiler abnehmen.
Possetitjel schrieb:> Jobst Q. schrieb:>>> Possetitjel schrieb:>>> (Auf der Siemens-S7-SPS geht das in AWL nicht so einfach;>>> es ist ein ziemlicher Krampf, dort einen Flankendetektor>>> zu formulieren, denn (NOT(alt) AND neu) funktioniert nicht.)>>>> Wieso?>> Das weiss ich leider nicht mehr genau. Kann sein, dass es> nicht auf der S7, sondern der S5 war; bin nicht sicher.>> Der Kernpunkt war jedenfalls, das "not(x)" durch "x xor 1"> nachgebildet werden musste. Natürlich geht das -- aber es> ist doch irgendwie Krampf. Muss das im dritten Jahrtausend> WIRKLICH noch sein?
Die AWL-Anweisungen sind nicht für alle CPUs gleich, nichtmal für die
Serien. Im Laufe der Zeit sind wohl einige dazugekommen.
Habe gerade gelesen, dass es zum Invertieren die Anweisungen INVI (16
Bit) und INVD (32 Bit) gibt, aber evtl erst ab S7-1500.
XOW und XOD gibt es aber wohl schon länger.
Michael R. schrieb:> ich denke das liegt auch daran, dass dieses Problem von Der Sprache bzw.> dem Compiler gar nicht gelöst werden kann;
Der aus dem Unix-Universum stammende "bc" kommt dem recht nahe. ;-)
Geht halt als Interpreter einfacher.
Michael R. schrieb:> Yalu X. schrieb:>> Ich kenne keine (auch keine exotische) Programmiersprache, in der dieses>> Problem zufriedenstellen gelöst wäre.>> ich denke das liegt auch daran, dass dieses Problem von Der Sprache bzw.> dem Compiler gar nicht gelöst werden kann; hier sehe ich den> Programmierer in der Pflicht.
Richtig. Man müsste dann für jede einzelne Rechenoperation den zu
erwartenden Wertebereich angeben, bspw. so:
A. K. schrieb:> x *[0..50000] y> statt> x * y
Aber das will man ja auch nicht wirklich.
A. K. schrieb:> Der aus dem Unix-Universum stammende "bc" kommt dem recht nahe. ;-)> Geht halt als Interpreter einfacher.
Das geht auch problemlos in kompilierten Sprachen (wird ja teilweise
tatsächlich auch gemacht), ist aber nicht unbedingt effizienzfördernd.
Yalu X. schrieb:> Richtig. Man müsste dann für jede einzelne Rechenoperation den zu> erwartenden Wertebereich angeben, bspw. so:>> A. K. schrieb:>> x *[0..50000] y>> statt>> x * y>> Aber das will man ja auch nicht wirklich.
Das reicht ja nicht mal!
ich konstruiere mal ein Beispiel: Länge eines Vektors der sich im
Inneren des Einheitskreises bewegt, bei auf 255 skaliertem
Einheitskreis. Basierend auf den Wertebereichen könnte der Vektor 360
lang werden, praktisch ist er aber nie größer als 255.
Oder anders gesagt: Immer wenn die Variablen nicht unabhängig sind,
können sich geringere Wertebereiche ergeben.
Yalu X. schrieb:> Das geht auch problemlos in kompilierten Sprachen (wird ja teilweise> tatsächlich auch gemacht),
Nur mit dynamischen Datentypen, deren Breite sich also zur Laufzeit
entwickelt. Klar, kann man als C++ Klasse machen und tut man sicherlich
auch. Angewandt auf alle normalen Integers der Sprache wäre das aber,
verglichen mit dem was man hier im Forum üblicherweise als Compiler
versteht, eher ein als Compiler getarnter Interpreter.
Michael R. schrieb:> Das reicht ja nicht mal!> ...> Oder anders gesagt: Immer wenn die Variablen nicht unabhängig sind,> können sich geringere Wertebereiche ergeben.
Genau deswegen braucht man ja die Wertebereichsangaben für die einzelnen
Rechenoperationen, für die A. K. die Syntax
<op>[<lo>..<hi>]
vorgeschlagen hat. Für dein Beispiel würde das so aussehen:
1
x, y, length: 0..255;
2
3
length := isqrt(sqr(x) +[0..65025] sqr(y))
Damit weiß der Compiler, dass die Addition nur 16- und nicht etwa
17-bit-breit ausgeführt werden muss, wie es bei unabhängigen x und y der
Fall wäre.
Entsprechendes gilt auch für Funktionsaufrufe. Wenn der Compiler nicht
von sich aus erkennt, dass der Aufruf von sqr([0..255]) einen
Wertebereich von [0..65025] hat, müsste der obige Ausdruck
folgendermaßen ergänzt werden:
Yalu X. schrieb:> <op>[<lo>..<hi>]
ok, das hatte ich falsch verstanden.
Yalu X. schrieb:> Ja, irgendwann ist ein Ausdruck dann so sehr mit Wertebereichshinweisen> gespickt, dass überhaupt keiner mehr durchblickt ;-)
Richtig, und diese (fiktive) Programmiersprache würde ich dann nicht
sooo gerne verwenden ;-)
Übrigens: Respekt, du hast mein (krudes) Beispiel besser verstanden als
ich selbst... der Trick liegt in der Addition, für die 16 Bit
ausreichend sind.
Michael R. schrieb:> Possetitjel schrieb:>> Man sollte daher eine Möglichkeit schaffen, solche>> Variablen nach WERTEBEREICH und nicht nach SPEICHERBEDARF>> zu deklarieren.>> Ist das (speziell bei Integer) nicht das Selbe?
Für den Menschen: Fast.
Für den Compiler: Nein.
> Zumindest bei mir im Kopf ist das so...
Ich weiss... anerkannte Berufskrankheit bei Programmierern :)
> Die Wertebereiche von 8- und 16 bit signed/unsigned> hab ich eingebrannt, bei 32 Bit wirds schon eng [...]
Nein, ich meine das anders: Wenn Du auf einer 8-bit-Maschine
einen kleinen endlichen Automaten mit - was weiss ich - 12
Zuständen programmierst, dann wirst Du für die Zustandsvariable
ein Byte wählen.
Wenn Du jetzt diesen Automaten auf einem ARM verwenden willst,
weil er super funktioniert, zwingst Du den Compiler, auch auf
dem ARM mit einem Byte zu operieren, obwohl ein 32bit-Wort viel
sinnvoller wäre.
Es gibt aber keine Möglichkeit, dem Compiler zu sagen: "Nimm
einen Dir passend scheinenden Integer-Typ, der (mindestens)
die Zahlen 1 bis 12 kennt".
Der Witz dieser Idee zeigt sich erst, wenn man sich einen
Zahlbereich 1..1000 und die Portierung in die umgekehrte
Richtung (vom ARM auf 8 bit) vorstellt: Theoretisch wäre
ein ja word (16bit) ausreichend.
Wenn es meine wertebereichsgesteuerte Typauswahl gäbe, würde
der Compiler auf dem ARM schätzungsweise ein longint wählen --
auf einer 8-bit-Maschine aber tatsächlich ein word, weil das
der kleinste Integer ist, der den Zahlbereich abdeckt.
Die Bereichsangabe 1..1000 ist vollständig portabel; dennoch
könnte vom Compiler immer der optimale Datentyp gewählt werden.
> Aber wie oben schon richtig festgestellt wurde, Variablen> sind nicht so schwierig, komplexer sind Ausdrücke.
Kommt darauf an. Fließkomma kann außer Betracht bleiben, weil
dort die niederwertigsten Bits (und nicht die höchstwertigen)
wegfallen. Das fällt in die Zuständigkeit der Numerik.
Bitoperationen spielen auch keine Rolle, weil die keinen
Überlauf erzeugen, und wenn sie vernünftig implementiert bzw.
definiert sind, auch keine versteckte Typabhängigkeit haben.
Als Problemfälle bleiben nur die Ganzzahl-Ausdrücke.
> Aber auch hier habe zumindest ich keine Probleme mit der> Art wie C das angeht. Die Fälle wo ich hier Fehler suchen> und durch einen cast beheben musste, kann ich vermutlich> an einer Hand abzählen.
Sicher eine Sache der Gewohnheit.
Ich bin der Meinung, dass
1. die Regeln EINFACH sein und
2. sich nicht dauernd ändern sollten.
Der Rest ist ziemlich wahlfrei.
Michael R. schrieb:> Possetitjel schrieb:>> Aber an die heilige Kuh, die seit Jahrzehnten tradierte>> implizite Modulo-Arithmetik, hat sich wieder einmal niemand>> herangetraut. Typisch.>> Erklärst du mir was du hier meinst?
Klar.
> Bezieht sich das auf pascal?
Nee.
Jeder hält es für normal, dass bei...
1
2
var b : byte;
3
...
4
b:=255;
5
incr(b);
... für b Null herauskommt.
Nicht nur, dass das mathematisch falsch und unter dem
Gesichtspunkt der Anwendungslogik häufig unsinnig ist, es
ist auch deshalb ärgerlich, weil der Prozessor intern den
Überlauf sehr wohl registriert, das Flag aber nicht an die
Hochsprache durchreicht.
Dass es auch anders geht, zeigen die diversen SIMD-Einheiten,
die (auch) eine Sättigungsarithmetik haben.
A. K. schrieb:> Das hatte ich schon verstanden. Aber du stelltest diese> Datentypen direkt in den Kontext deiner Ablehnung von> native integers und eine solche (sinnvolle) Typdeklaration> ändert nichts daran, dass man Regeln benötigt, in welcher> Breite sowohl damit als auch mit normalen Integers gerechnet> werden soll.>> Pascals native Integers sind kein Aspekt der Speicherung von> Daten, sondern betreffen nur die Breite der Berechnung in> Ausdrücken. Mit einer möglichen Wertebereichsdefinition von> Variablen wirst du sie nicht los.
Du hast Recht, ich zwei verschiedene Fragen vermischt.
Entschuldigung.
Mein zügelloses Wettern gegen "native integers" bezog sich
auf den "int"-Datentyp, wie er in C existiert. Während man
z.B. bei "uint8_t" gleichzeitig SOWOHL Speicherbedarf ALS
AUCH Wertebereich festlegt, legt man bei "int" außer der
Tatsache, dass es ganze Zahlen sein sollen, ÜBERHAUPT NICHTS
fest -- also Übertreibung in die andere Richtung.
Die Sachlage in Pascal ist anders; da geht es, wie Du richtig
bemerkst, um die Auswertung von Ausdrücken. Ich muss jetzt,
beim erneuten ruhigen Durchdenken, auch zugeben, dass die
implementierte Lösung einen gewissen Charme hat -- aber
ärgerlich ist sie andererseits doch, denn es entsteht eine
verdeckte Plattformabhängigkeit.
A. K. schrieb:> PS: Da war doch was... Deine Wertebereichstypen gibts> in Pascal doch sowieso schon seit Anbeginn der Zeit.
Die haben natürlich bei meinem Vorschlag Pate gestanden.
Ich weiss aber nicht, ob sie genau das leisten, was ich
haben will: Ich will ja keinen NEUEN Datentyp erzeugen, der
dann Pascal-typisch wieder nur zu sich selbst zuweisungs-
kompatibel ist -- ich will, dass der Compiler aus den
EXISTIERENDEN Integertypen einen passenden auswählt.
Aus meinen antiken Pascal-Büchern kann ich nicht heraus-
lesen, wie die Teilbereichstypen im Detail funktionieren.
Yalu X. schrieb:> Und auch in C gibt es mit [u]int_fast<n>_t und> [u]int_least<n>_t flexible Datentypen, mit denen> auf portable Weise Wertebereiche für Variablen> festgelegt werden können, zwar nicht so feingranular> wie in Pascal, dafür kann man mit "fast" oder "least"> angeben, ob man lieber Rechenzeit oder Speicherplatz> sparen möchte.
Ahh... richtig, da war was. Danke für die Erinnerung;
das war mir komplett entfallen.
> A. K. schrieb:>> Denn das Problem besteht nicht in der Breite der>> Speicherung von Variablen. Sondern in der Breite>> der Berechnung von Ausdrücken, die Variablen verwenden.>> ...>> Ich kenne keine (auch keine exotische) Programmiersprache,> in der dieses Problem zufriedenstellen gelöst wäre.
Ich weiss ja nicht, was für Dich "zufriedenstellend" ist.
Da Tcl überhaupt kein für den Programmierer zugängliches
Typkonzept kennt, ist in Tcl natürlich auch die Unterscheidung
verschieden langer Integers hinfällig.
Yalu X. schrieb:>> Oder anders gesagt: Immer wenn die Variablen nicht>> unabhängig sind, können sich geringere Wertebereiche>> ergeben.>> Genau deswegen braucht man ja die Wertebereichsangaben> für die einzelnen Rechenoperationen, [...]
Nee... die braucht man nur für die µC.net-typische
Übertreibung... :)
Es gibt doch erstmal drei separate Probleme:
- Portabilität,
- Auswertungsregeln, die Überlauf verhindern,
- Effizienz.
Mein Ausgangspunkt war gar nicht, dass ich immer und
überall den Überlauf zuverlässig verhindern will --
es ging mir nur darum, dass nicht plattformabhängig
mal ein Überlauf entsteht und mal nicht!
Anders ausgedrückt: Ich wollte keine plattformabhängigen
Auswertungsregeln.
Sicher gibt es keine ideale Sprache, die diesem Problem in Gänze gerecht
wird. Allerdings ist für mich C++ nah dran. Jeder C++-Programmierer
lernt / sollte lernen ziemlich am Anfang, dass eine der Ideen der
Sprache die (fast) Gleichbehandlung der primitive DT und UDT sind, mit
dem Hintergrund, sich ein domänenspezifisches Typsystem zu erzeugen
(nicht alles ist ein String oder ein int, es gibt Bytes, Meter, Volt,
etc. und entspr. Operationen).
Insofern adressiere ich das Problem mit Typen wie
[c]
uint_ranged<23, 57> x;
uint_ranged_NaN<0, 19999> y;
uint_circular<0, 15> z;
[\c]
Die notwendigen unterliegenden primitiven DT werden hier bspw. aus dem
Wertebereich bestimmt. Die Plattformabhängigkeit löst der Standard durch
uint_fast8_t, etc.
Statische Überläufe erkennt der Compiler, Laufzeitüberläufe als
Verlassen des Wertebereiches prüfen natürlich Assertionen (sind ja auch
abschaltbar).
Natürlich kann man sich mehr wünschen, aber so bin ich bzgl. Sicherheit
und Expressivität schon ein ziemliches Stück weiter als der "alles ist
ein unsigned char" Ansatz ...
Wilhelm M. schrieb:> Allerdings ist für mich C++ nah dran.
Endlich! Ich kaufe gleich Popcorn ein, für heute Abend und die nächsten
Wochen.
Wenn C++ ins Spiel kommt, wird es lustig.
F. F. schrieb:> Wilhelm M. schrieb:>> Allerdings ist für mich C++ nah dran.>> Endlich! Ich kaufe gleich Popcorn ein, für heute Abend und die nächsten> Wochen.
Ja, mich hat es auch schon gewundert, dass das noch keiner ins Spiel
gebracht hatte ... nun, ich muss ja meinem Ruf gerecht werden ;-)
Possetitjel schrieb:> Nein, ich meine das anders: Wenn Du auf einer 8-bit-Maschine> einen kleinen endlichen Automaten mit - was weiss ich - 12> Zuständen programmierst, dann wirst Du für die Zustandsvariable> ein Byte wählen.
Nein. Für die Zustandsvariable wähle ich eine Enumeration. Die kann man
auf einem AVR (allerdings per Compilerschalter) auch auf 8 Bit
eindampfen und wird je nach Wertebereich automatisch vergrößert.
> Es gibt aber keine Möglichkeit, dem Compiler zu sagen: "Nimm> einen Dir passend scheinenden Integer-Typ, der (mindestens)> die Zahlen 1 bis 12 kennt".
Doch, und in C nennt der sich "enum" (mit benannten Werten).
Außerdem kennt C solche Datentypen wie uint_fast8_t und uint_least8_t,
mit denen ich genau solch ein Verhalten ausdrücken kann.
> Die Bereichsangabe 1..1000 ist vollständig portabel; dennoch> könnte vom Compiler immer der optimale Datentyp gewählt werden.
CPUs arbeiten auf Vielfachen von Bytes, und außerdem durchgängig im
Binärsystem. Wenn ich einen Wertebereich von 1..1000 angebe, dann
erwarte ich auch, dass dieser immer und ausnahmslos eingehalten wird.
Das kostet entweder Performance zur Laufzeit oder ich kann mit Über-
oder Unterläufen ungültige Werte produzieren. Möchte ich nicht.
> Als Problemfälle bleiben nur die Ganzzahl-Ausdrücke.
Also genau das, wofür Computer gebaut werden - Rechenoperationen. :-)
Possetitjel schrieb:> [Modulo-Arithmetik]> Dass es auch anders geht, zeigen die diversen SIMD-Einheiten,> die (auch) eine Sättigungsarithmetik haben.
Man kann dank der Modulo-Arithmetik bestimmte Dinge sehr effizient
umsetzen, was mit sättigender Arithmetik nicht ginge. Zumal CPUs bei
normaler Integer-Arithmetik nicht sättigen können.
Alles, was du möchtest, gibt es in höheren Sprachen, bei denen die
Laufzeitkosten kein Problem darstellt. Im Extremfall steht dann sowas
wie coq, bei dem jede Operation passend bewiesen werden muss (und wo die
Laufzeitkosten wieder wegfallen).
Possetitjel schrieb:> Mein zügelloses Wettern gegen "native integers" bezog sich> auf den "int"-Datentyp, wie er in C existiert.
Wie es ihn auch in Pascal gibt, siehe oben.
Wie es ihn in so ziemlich jeder Programmiersprache gibt, denn wenn man
darauf verzichtet, muss man mit arbitrary precision logic arbeiten und
das kostet Performance und Speicher.
> Während man> z.B. bei "uint8_t" gleichzeitig SOWOHL Speicherbedarf ALS> AUCH Wertebereich festlegt, legt man bei "int" außer der> Tatsache, dass es ganze Zahlen sein sollen, ÜBERHAUPT NICHTS> fest -- also Übertreibung in die andere Richtung.
Das ist falsch. Ein "int" ist mindestens 16 Bit lang und auf der CPU
effizient. Das sind grundsätzlich sinnvolle Randbedingungen und etwas
völlig anderes als "nichts".
Und wie gesagt, du darfst auch mit den fastN- und leastN-Typen
hantieren. Oder dich bei Java umschauen, wo Integer ein Objekt ist (dort
gibt es "int" nur auf Performance-Gründen, nur signed und m.W. mit
relativ undefinierter Breite).
Was du möchtest, ist an sich nicht verkehrt, aber es ist für
maschinennahe/effiziente Programmierung (und für nichts anderes ist C
entwickelt worden) aus den genannten Gründen nicht optimal. Da sind
andere Kompromisse wichtiger.
Wilhelm M. schrieb:> Sicher gibt es keine ideale Sprache, die diesem> Problem in Gänze gerecht wird. Allerdings ist für> mich C++ nah dran.
Nun ja, ich bin dabei, C zu lernen. Zielpunkt sind
Mikrocontroller.
Wäre es tatsächlich hilfreich, statt C eine noch
wesentlich mächtigere und komplexere Sprache zu
wählen? Ich habe da Zweifel...
Possetitjel schrieb:> Wilhelm M. schrieb:>>> Sicher gibt es keine ideale Sprache, die diesem>> Problem in Gänze gerecht wird. Allerdings ist für>> mich C++ nah dran.>> Nun ja, ich bin dabei, C zu lernen. Zielpunkt sind> Mikrocontroller.>> Wäre es tatsächlich hilfreich, statt C eine noch> wesentlich mächtigere und komplexere Sprache zu> wählen? Ich habe da Zweifel...
C++ ist schon klasse und ich wollte das nicht verunglimpfen. Es gab
einmal einen Thread hier, da wurde ein Problem so über Wochen (glaube
das ging eine Weile) zerrissen und es kamen quasi täglich neue "Götter
der C++ Kunst" hinzu und der Thread wurde eher philosophisch (vielleicht
übertreibe ich ein bisschen, aber deshalb das Popcorn) und war nicht
mehr zu lesen.
Possetitjel schrieb:> Wilhelm M. schrieb:>>> Sicher gibt es keine ideale Sprache, die diesem>> Problem in Gänze gerecht wird. Allerdings ist für>> mich C++ nah dran.>> Nun ja, ich bin dabei, C zu lernen. Zielpunkt sind> Mikrocontroller.>> Wäre es tatsächlich hilfreich, statt C eine noch> wesentlich mächtigere und komplexere Sprache zu> wählen? Ich habe da Zweifel...
Die Sprache C++ ist zwar komplexer und wesentlich mächtiger als C, aber
nicht notwendigerweise schwerer zu lernen. Das wird leider oft in einen
Topf geworfen. Ja, C++ ist eine Multiparadigmensprache: imperativ /
prozedural, objektorientiert, generisch / meta-programmatisch,
funktional. Doch man muss nicht alles auf einmal benutzen, sondern man
kann sich je nach Einsatzzweck das Richtige heraussuchen.
Sich auf C als Sprache oder auf den C-Anteil in C++ allein zu
beschränken, macht m.E. keinen Sinn. In einer general-purpose Umgebung
wie *nix/Win$$ greift man gerne auf OOP zurück, in einer eingeschränkten
Umgebung wie bare-metal µC findet man schnell eine Kombination aus
prozedural und meta-programmatisch hilfreich, ggf. mit einer Prise OOP.
Ich finde die Sichtweise "alles ist ein Integer oder ein hoffentlich
null-terminiertes char-Array" sehr einengend. In meinen Augen sind
solche SW-Konstrukte extrem schwer zu lesen, zu warten oder weiter zu
entwickeln. Hingegen fördern die richtigen Abstraktionen die Lesbarkeit
bzw. Expressivität und verhindern zur Compile-Zeit viele Fehler, die man
sonst zu Laufzeit suchen muss. Zudem hilft ein reiches Typsystem dem
Compiler bei der Optimierungsarbeit.
Wilhelm M. schrieb:> Insofern adressiere ich das Problem mit Typen wie
Wobei Possetitjel damit auch den Platzbedarf optimieren wollte. Wie
macht man das in C++? Also dass der Programmierer nicht doch wieder
explizit den Grundtyp "uint" in "uint_ranged<23, 57>" angeben muss, egal
ob wie hier hardcoded, oder als Parameter vom Template. Sondern der
Compiler sich das aus dem angegeben Bereich selbst ableiten kann. Für
die frühen C++ Versionen fällt mir da nichts sinnvolles ein.
A. K. schrieb:> Wilhelm M. schrieb:>> Insofern adressiere ich das Problem mit Typen wie>> Wobei Possetitjel damit auch den Platzbedarf optimieren wollte. Wie> macht man das in C++? Also dass der Programmierer nicht doch wieder> explizit den Grundtyp "uint" in "uint_ranged<23, 57>" angeben muss, egal> ob wie hier hardcoded, oder als Parameter vom Template. Sondern der> Compiler sich das aus dem angegeben Bereich selbst ableiten kann. Für> die frühen C++ Versionen fällt mir da nichts sinnvolles ein.
Mit einer Meta-Funktion, etwa so: