Forum: Compiler & IDEs Sind ALLE Rechenoperationen automatisch 16-bit breit?


von Phil (Gast)


Lesenswert?

Hallo Freunde,

mein C-Buch hilft mir hier nicht weiter:

1
uint8_t cnt = 0;
2
3
if (cnt+10 < 255) cnt++;

die Operation "cnt+10" ist ja ein 8-bit Wert plus 10. Was passiert also 
bei bspw. cnt = 250? Kommt es schon in dem Vergleich zu einem überlauf 
oder wird JEDE mathematische Opaeration im GCC auf uint16_t gecastet?

Wird auch auf signed int16_t gecastet? Sprich bei dem Fall:

1
uint8_t cnt = 255;
2
3
if (cnt-10 > 5) cnt--;

Vielleicht wisst ihr ja ne Lösung.

Danke

von (prx) A. K. (prx)


Lesenswert?

Das Ergebnis von ganzzahligen Rechnungen ist per C-Definition so, als 
wäre es mindestens mit "int" gerechnet worden. In diesen Beispielen wird 
mit "int" gerechnet.

Achtung: GCC ist korrekt, aber nicht alle Compiler für 8-Bit 
Mikrocontroller halten sich an diese Regel. Manche muss man explizit 
davon überzeugen, sich nach dem Standard zu richten.

von Phil (Gast)


Lesenswert?

Also egal an welcher Stelle auch immer ich eine Rechenoperation 
durchführe castet der Compiler auf 16 bit?

Ist er signed oder unsigned? (Logischer wäre sicherlich signed)

von (prx) A. K. (prx)


Lesenswert?

Phil schrieb:

> Also egal an welcher Stelle auch immer ich eine Rechenoperation
> durchführe castet der Compiler auf 16 bit?

Er konvertiert alle char/short Werte, die in "int" passen, nach "int", 
sonst nach "unsigned".

> Ist er signed oder unsigned? (Logischer wäre sicherlich signed)

Da sich alle Werte von uint8_t vollständig in "int" wiederfinden, wird 
in "int" gerechnet, also mit Vorzeichen.

von Phil (Gast)


Lesenswert?

Dann geh ich mal eine Stufe weiter.

Ich rufe eine Funktion count auf:
1
void count(uint8_t val){
2
   if(val > 255) x = 255;
3
   else x++;
4
}
5
//global
6
uint8_t x = 250
7
8
main(){
9
count(x + 25);
10
}
was passiert hierbei? Wenn das Argument der Funktion übergeben wird, 
wird dann erstmal in 16bit gerechnet. Der Wert dann in eine 8bit 
variable gepresst und dann erst der Vergleich geführt?

Wenn ihr das Problem versteht. Wie kann ich den Überlauf umgehen? "val" 
als int16_t zu definieren fällt flach.

von Oliver (Gast)


Lesenswert?

Phil schrieb:
> Wenn ihr das Problem versteht.

Nicht ganz.

1
void count(uint8_t val){
2
   if(val > 255) x = 255;
3
   else x++;

val hat einen Wertebereich von 0..255. Der Vergleich ist damit sinnlos, 
und er Optimizer wird den rauswerfen. Wenn du nicht möchtest, das val 
"größer als 255" wird, d.h. nicht überläuft, brauchst du sowas hier:
1
void count(uint8_t val){
2
   if(val < 255) x++;

Oliver

von Sven P. (Gast)


Lesenswert?

Phil schrieb:
> Dann geh ich mal eine Stufe weiter.
>
> Ich rufe eine Funktion count auf:
>
>
1
> void count(uint8_t val){
2
>    if(val > 255) x = 255;
3
>    else x++;
4
> }
5
> //global
6
> uint8_t x = 250
7
> 
8
> main(){
9
> count(x + 25);
10
> }
11
> 
12
>
> was passiert hierbei? Wenn das Argument der Funktion übergeben wird,
> wird dann erstmal in 16bit gerechnet.
Es wird integerbreit x+25 gerechnet und das Ergebnis für den 
Funktionsaufruf mundgerecht auf 8 Bit gestutzt. Der Compiler wird den 
oberen Zweig der Bedingung wegoptimieren und count wird immer x++ 
ausführen.

> Wenn ihr das Problem versteht. Wie kann ich den Überlauf umgehen? "val"
> als int16_t zu definieren fällt flach.
Vorher schauen, obs überlaufen wird.
Warum fällt int16_t flach?

von (prx) A. K. (prx)


Lesenswert?

Phil schrieb:

>    if(val > 255) x = 255;

Sinnlos, da val nie > 255 werden kann.

>    else x++;

Kann überlaufen und dann steht 0 in x.

> count(x + 25);

Da der Parameter als uint8_t deklariert ist, wird (uint8_t)((int)x + 25) 
übergeben, also 19.

Merke: Nur Zwischenrechnungen werden expandiert. Wird das Ergebnis einer 
solchen Rechung einer Variable zugewiesen, dann enthält diese Variable 
nur das, was dort reinpasst.

> Wenn ihr das Problem versteht. Wie kann ich den Überlauf umgehen? "val"
> als int16_t zu definieren fällt flach.

Du hast ein paar abstrakte Beispiele genannt, nicht aber das eigentliche 
Problem.

von Karl H. (kbuchegg)


Lesenswert?

Phil schrieb:

> was passiert hierbei?

Gehs der Reihe nach durch

Du hast zunächst mal 2 Phasen

Vor dem Aufruf
Nach dem Aufruf

Vor dem Aufruf muss passieren

   Argumente auswerten
   An die tatsächlich von der Funktion gewünschten Datentypen
   anpassen


Du hast

  count(x + 25);

x ist ein uint8_t mit dem Wert 250

Um die Addition zu machen wird aus dem uint8_t ein int
   250 + 25  macht 275

Damit sind die Argumente evaluiert.
Der eigentliche Aufruf:

Du möchtest den Wert 270 an eine Funktion übergeben, die einen uint8_t 
nimmt. Das geht erst mal so nicht. Von dem int wird das obere Byte 
gestrippt, aus 270 wird so 19

Damit bist du innerhalb der Funktion:

   if(val > 255) x = 255;

das ist natürlich selten dämlich. val ist ein uint8_t. Den uint8_t 
möchte ich sehen, der Werte größer als 255 annehmen kann :-)

Aber seis drum.
Die Funktion bekommt 19 als Wert für val. Und damit wird 
weitergearbeitet.

> Wie kann ich den Überlauf umgehen? "val"
> als int16_t zu definieren fällt flach.

Welchen Überlauf. Innerhalb der Funktion findet kein Überlauf statt. 
Dein 'Problem' welches es auch immer konkret ist, ist nicht innerhalb 
der Funktion zu lösen.

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Phil schrieb:
> Wenn das Argument der Funktion übergeben wird,
> wird dann erstmal in 16bit gerechnet.

Nein, nicht unbedingt. Wie schon andere Diskussionsteilnehmer 
geschrieben haben, erfolgt die Berechnung mit einem internen Datentyp, 
dessen Breite einem "int" oder "unsigned int" entspricht.

Da Du es offenbar nicht nötig hast, hier auch nur ansatzweise 
Andeutungen über das Zielsystem zu machen, kann keiner von uns wissen, 
wie breit dort ein "int" ist.

In den meisten Fällen entspricht ein "int" der natürlichen 
Datenwortlänge des Prozessors. Bei x86-Prozessoren gilt dies aber häufig 
nicht, z.B. bei Anwendungen, die im DOS-Kompatibilitätsmodus für Windows 
übersetzt werden und daher 16Bit-Integer haben.

Aber auch bei 64Bit-Systemen erlebt man die folgende Überraschung. Ich 
empfehle, das folgende Progrämmchen jeweils mittels

gcc -m32

und

gcc -m64

zu übersetzen und auszuführen.
1
#include <stdio.h>
2
#include <values.h>
3
4
int main(int argc, void **argv){
5
  printf("char: %d\n", CHARBITS);
6
  printf("short: %d\n", SHORTBITS);
7
  printf("int: %d\n", INTBITS);
8
  printf("long: %d\n", LONGBITS);
9
  return 0;
10
}

von Sven P. (Gast)


Lesenswert?

Bei 64-Bit kann man dazu munkeln:

Wenn man den int auf 64 Bit aufgeblasen hätte, eben so, wie man es auch 
eigentlich erwartet hätte, da ja jetzt 64 Bit die natürliche Wortbreite 
der Maschine ist, so würden unzählige Programme auf einmal doppelt so 
viel Arbeitsspeicher verbrauchen.
Nämlich immer dann, wenn im Programm ein int verbaut ist...

Aber so viel konsequentes 64 Bit wollen die Privatkonsumenten dann doch 
nicht. Lieber 64 Bit können, aber dann wieder auf 32 Bit kastrieren.

von (prx) A. K. (prx)


Lesenswert?

Andreas Schweigstill schrieb:

> Aber auch bei 64Bit-Systemen erlebt man die folgende Überraschung. Ich
> empfehle, das folgende Progrämmchen jeweils mittels

Insbesondere unter Windows wird die Überraschung gross ausfallen. Unter 
Linux/Unix ist's nicht ganz so arg.

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Sven P. schrieb:
> Aber so viel konsequentes 64 Bit wollen die Privatkonsumenten dann doch
> nicht. Lieber 64 Bit können, aber dann wieder auf 32 Bit kastrieren.

Das hat nichts mit "Privatkonsumenten" zu tun. Der wahre Grund dürfte 
eher darin liegen, dass Unmengen an Quelltexten auf Seiteneffekte hin 
überprüft werden müssten.

Werden Dateien von "Altsystemen" mit 32Bit übernommen, müssen diese 
natürlich lesbar bleiben. Ebenso gibt es Unmengen an 
Netzwerkprotokollen, die implizit auf 32Bit-Datentypen basieren und 
damit sehr genau kontrolliert werden müssten.

Die Beibehaltung der 32Bit-Integer hat also eher etwas mit 
Bestandsschutz zu tun, und das völlig zu Recht!

von (prx) A. K. (prx)


Lesenswert?

Dummerweise handelt man sich dadurch wieder jene Probleme ein, mit denen 
man schon anno 16-Bit x86 zu kämpfen hatte. Nämlich der leider immer 
noch gelegentlich anzutreffenden Annahme, dass ein "int" oder "unsigned" 
oder - bei vorsichtigeren Gemütern - zumindest ein "long" eine 
Speicheradresse gefahrlos transportieren kann.

Dazu kommt ein weiteres und neues Problem: Der Datentyp für 
Array-Indizierung lässt sich in C nicht deklarieren, nicht zuletzt 
aufgrund der definierten Äquivalenz von Pointerrechung und 
Array-Indizierung. Wie der Compiler damit nun umgehen soll ist aus 
Quelltextsicht somit eher schwarze Magie als klare Sache, bzw. vom 
Datentyp der Indexoperanden abhängig.

Ich wäre also nicht überrascht, wenn mit der Verbreitung von Dataspaces 
grösser als 4GB ein Index-Modulo-Problem eine Rolle ähnlich dem 
Buffer-Overflow bekommt.

von Rolf Magnus (Gast)


Lesenswert?

Andreas Schweigstill schrieb:
> Nein, nicht unbedingt. Wie schon andere Diskussionsteilnehmer
> geschrieben haben, erfolgt die Berechnung mit einem internen Datentyp,
> dessen Breite einem "int" oder "unsigned int" entspricht.

Das ist etwas seltsam formuliert. Die Integer-Promotion konvertiert den 
Wert zunächst nach "int" oder "unsigned int" und rechnet dann mit dem 
Ergebnis dieser Konvertierung. Die Berechnung erfolgt also formal mit 
exakt diesem Typ und nicht nur mit irgendeinem internen Typ derselben 
Größe.

> Bei x86-Prozessoren gilt dies aber häufig nicht, z.B. bei Anwendungen,
> die im DOS-Kompatibilitätsmodus für Windows übersetzt werden und daher
> 16Bit-Integer haben.

Naja, diese Programme laufen aber dann auch im 16-Bit-Modus, so daß man 
im Prinzip schon sagen kann, daß die natürliche Breite in dem Fall 16 
Bit ist. Sonst müßte konsequenterweise auf einem Core2-Prozessor ein int 
64 Bit groß sein, auch wenn darauf gerade ein 32-Bit-Betriebssystem 
läuft.

> Aber auch bei 64Bit-Systemen erlebt man die folgende Überraschung.

Überraschend ist es eigentlich nicht. Das Problem ist, daß einem hier 
einfach die Typen ausgehen. Wäre int 64 Bit breit, wären nicht mehr 
genug Standard-Typen für 8, 16 und 32 Bit übrig, weil es nur zwei gibt, 
die kleiner als int sein dürfen.

von (prx) A. K. (prx)


Lesenswert?

Rolf Magnus schrieb:

> Überraschend ist es eigentlich nicht. Das Problem ist, daß einem hier
> einfach die Typen ausgehen.

Andererseits gehen einem dank Microsofts weisem Ratschluss nun am 
anderen Ende die Typen aus. Denn in ANSI-C ('89/'90) ist es auf einem 
64-Bit Windows unmöglich, mit 64 Bit Integers zu arbeiten. "long" steht 
für 32 Bits und "long long" gibt es offiziell erst in C99.

von Sven P. (Gast)


Lesenswert?

Andreas Schweigstill schrieb:
> Die Beibehaltung der 32Bit-Integer hat also eher etwas mit
> Bestandsschutz zu tun, und das völlig zu Recht!

Meine Meinung:
Nein, das ist totaler Blödsinn! :-)

Dort, wo explizite Wortlängen gefordert sind, sollen sie auch explizit 
vereinbart werden. Nicht nur deshalb hat man vor Äonen mal sowas wie 
stdint.h eingeführt.
Quelltext, der von der Breite von int & Konsorten abhängt, ist einfach 
für die Tonne.
Solcherlei Quelltexte sind MURKS von Anfang an, seit es z.B. stdint.h 
gibt. Sowas gehört bedingungslos in ein ordentliches Review, denn früher 
oder später ist ein int 64 Bit breit.

Immerhin hätte ich (aber auf mich hört ja niemand G) wenigstens beim 
Sprung auf 64 Bit mal eine ganze Reihe von Altleichen beseitigt. Die 
meisten Betriebssysteme (vermutlich abgesehen von Windows) wären nicht 
dran krepiert, dort muss seit eh und je schon mehrgleisig (x86, ARM, 
...) gefahren werden. Aber dafür wären solche Leichen wie die Timer, der 
verkappte PIC, MMX/SSE/Trallalla etc. endlich mal aus dem Keller.
Die Anwendungen werden sowieso für jede Distribution neu übersetzt.


Aber das genau ist ja so ein Kernproblem. Jede Klitsche ist geil auf 
ihre supergeheimen Quelltexte. Ist die Klitsche mal pleite, steht wieder 
jemand mehr auf dem Schlauch, weil die Software auf irgendwelche total 
veralteten Dinge besteht.
Normalerweise würde man jetzt einfach das Programm entsprechend anpassen 
(lassen) neu auf der Zielarchitektur übersetzen und gut wärs. Aber nein, 
da wird z.B. ein Counterstrike-Server ausgeliefert, der unbedingt SSE2 
benutzen möchte und kein Fallback hat. Is natürlich für ältere AMDs 
optimal, illegal instruction und Ende.

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Sven P. schrieb:
> Meine Meinung:
> Nein, das ist totaler Blödsinn! :-)

Nein, das ist einfach Realität.

> Dort, wo explizite Wortlängen gefordert sind, sollen sie auch explizit
> vereinbart werden. Nicht nur deshalb hat man vor Äonen mal sowas wie
> stdint.h eingeführt.

Das heißt aber noch nicht, dass dies konsequent umgesetzt wurde. 
Außerdem fällt dies ja frühestens dann erst auf, wenn ein Wechsel auf 
eine neuere Generation von Prozessoren/Betriebssystem ansteht. Und dann 
ist es zu spät, die alten Fehler zu korrigieren.

> Quelltext, der von der Breite von int & Konsorten abhängt, ist einfach
> für die Tonne.

Das mag ja sein, aber trotzdem ist man auf solche Software auch auf dem 
neuen System angewiesen.

> Solcherlei Quelltexte sind MURKS von Anfang an, seit es z.B. stdint.h
> gibt. Sowas gehört bedingungslos in ein ordentliches Review, denn früher
> oder später ist ein int 64 Bit breit.

Es gibt keinerlei Verpflichtung, solch ein Review durchzuführen. Wer 
soll das denn auch bezahlen? Wenn der Kunde eine Software haben will, 
die auf Betriebsystem A Version B läuft, dann bekommt er diese. Sofern 
nichts anderes vereinbart ist, bezahlt er auch nur diese Version.

> Immerhin hätte ich (aber auf mich hört ja niemand G) wenigstens beim
> Sprung auf 64 Bit mal eine ganze Reihe von Altleichen beseitigt.

Die Welt ist voll von solchen realitätsfremden Weltverbesserern.

> Die Anwendungen werden sowieso für jede Distribution neu übersetzt.

Nein. Das mag zwar auf viele Open-Source-Projekte zutreffen, aber für 
den Großteil kommerzieller Software gilt dies nicht. Kompatibilität wird 
meist nur für eine bestimmte Distribution zugesagt, im Linux-Bereich 
häufig irgendeine leicht angegraute Redhat-Version.

> Aber das genau ist ja so ein Kernproblem. Jede Klitsche ist geil auf
> ihre supergeheimen Quelltexte.

Diese "supergeheimen Quelltexte" sind die Geschäftsgrundlage der meisten 
Klitschen.

Hast Du schon einmal versucht, selbst Geld zu verdienen? Ich gehe nicht 
davon aus.

> Ist die Klitsche mal pleite, steht wieder
> jemand mehr auf dem Schlauch, weil die Software auf irgendwelche total
> veralteten Dinge besteht.

Zu dem Zeitpunkt, als die Software auf den Markt kam, waren die 
Voraussetzungen aber vermutlich nicht veraltet. Und der Kunde hat die 
Software ja auch nur für den damaligen Anwendungszweck gekauft. Ggf. hat 
er einen Wartungsvertrag für die Lieferung von Software-Updates erworben 
und damit vielleicht auch Software für neuere Betriebssystemversionen 
erhalten.

> Normalerweise würde man jetzt einfach das Programm entsprechend anpassen
> (lassen) neu auf der Zielarchitektur übersetzen und gut wärs.

Nicht normalerweise, sondern idealerweise. Die Realität definiert, was 
normal ist und nicht was idealerweise gelten sollte.

von Sven P. (Gast)


Lesenswert?

Andreas Schweigstill schrieb:
> [...]
> Hast Du schon einmal versucht, selbst Geld zu verdienen? Ich gehe nicht
> davon aus.
Ja, hat auch funktioniert.

Im Ernst, ich stimme dir ohne Vorbehalte zu; meine Aussagen waren schon 
bewusst etwas überspitzt, ganz so naiv bin ich dann doch nicht.

Dass das praktisch nicht bzw. nicht mehr umzusetzen ist, weiß ich auch. 
Aber man sieht, wie m.M.n. closed-source das Krebsgeschwür der 
Computerwelt ist, um es mal mit nicht ganz eigenen Worten zu sagen. Es 
hemmt den Fortschritt ganz gewaltig, da andauernd irgendwelche Altlasten 
mitgeschleppt werden.

Irgendwann kommt der Sprung weg von diesen Leichen im Keller, ob man es 
nun für wahr haben möchte oder nicht. Also besser jetzt Review machen 
und sauber arbeiten, als später wieder mal aufs Maul zu fallen, oder?

Hier (Linux) hat man das z.B. erkannt, und htons() und Konsorten auf 
explizite Breiten umgestellt. Windows benutzt in den Winsock-Headern 
bislang u_short und u_long, wobei sich die MSDN darüber ausschweigt, ob 
ein u_short dasselbe ist, wie ein ushort, welches nämlich ein unsigned 
short ist. Oder ob u_short ur u_short heißt, aber in Wahrheit ein 
uint16_t ist.
Sehr durchdacht :-)

Es würde vermutlich auch niemand etwas sagen, wenn es das erste Mal 
wäre, dass sowas passiert. Aber man ist ja in der nahen Vergangenheit 
schon mehrmals über denselben Mist gefallen -- OS-Hersteller wie 
Klitschen.
Wer da nun nicht schlau draus geworden ist, der ist wohl selbst schuld. 
Dumm ist nur, dass wieder tausend andere darunter leiden müssen, weil 
z.B. irgendeine Software nicht mehr wartbar ist und man deshalb an Win95 
klebt. -- produktiv geht irgendwie anders.

von Andreas F. (aferber)


Lesenswert?

Sven P. schrieb:
> Dort, wo explizite Wortlängen gefordert sind, sollen sie auch explizit
> vereinbart werden. Nicht nur deshalb hat man vor Äonen mal sowas wie
> stdint.h eingeführt.

Als die ersten 64bit-Architekturen eingeführt wurden, war es noch ein 
weiter Weg bis C99.

Unter anderem auf den damals gewonnenen Erfahrungen beruht es wohl, dass 
überhaupt die Notwendigkeit für eine "stdint.h" erkannt wurde.

> Quelltext, der von der Breite von int & Konsorten abhängt, ist einfach
> für die Tonne.
> Solcherlei Quelltexte sind MURKS von Anfang an, seit es z.B. stdint.h
> gibt.

Es gibt Tonnen von bis heute verwendetem Code, der erheblich älter als 
C99 ist. Soviel zum Thema "MURKS von Anfang an". Ausserdem verschwindet 
Code nicht, bloss weil er vielleicht Murks (oder eher: nicht mehr 
zeitgemäss) ist.

Wenn der jahrzentelang funktionierende Code auf einmal auf einer neuen 
Maschine nicht mehr funktioniert, dann ist für den Kunden die neue 
Maschine schuld, nicht der Code. Wie es vielleicht technisch oder 
akademisch aussieht, interessiert ihn da einen Scheissdreck.

> Sowas gehört bedingungslos in ein ordentliches Review, denn früher
> oder später ist ein int 64 Bit breit.

Korrekt wäre "früher war". Die allerersten 64bit-Systeme haben int auf 
64bit aufgedreht (ILP64-Modell). Später hat man erkannt, dass das nicht 
wirklich günstig ist, und ist zum LP64-Modell gewechselt (und MS jetzt 
zu LLP64). Der Erhalt eines "natürlichen" C-Typs mit 32bit hat dabei 
aber nur eine kleine Rolle gespielt, es gibt noch diverse andere Gründe.

http://www.unix.org/version2/whatsnew/lp64_wp.html

> Immerhin hätte ich (aber auf mich hört ja niemand G) wenigstens beim
> Sprung auf 64 Bit mal eine ganze Reihe von Altleichen beseitigt.

Schau dir mal die Geburtswehen von DEC Alpha als erste 64bit-Architektur 
im Opensource-Bereich an. Es hat Jahre gedauert, bis die DEC 
Alpha-Versionen der Linux-Distributionen den gleichen Umfang erreicht 
hatten wie die x86-Versionen. Eines der Hauptprobleme hierbei war, dass 
auf einmal long und int nicht mehr die gleiche Größe hatten.

Natürlich war der betroffene Code unsauber geschrieben, das Problem ist 
aber, dass Fehler ohne jede Auswirkung auf die Programmlogik (und um 
einen solchen handelt es sich, wenn bei 32bit-int und -long nicht sauber 
zwischen beiden unterschieden wird) bei den üblichen 
Entwicklungsprozessen nicht entdeckt werden.

Ich vermute, dass eben diese Geburtswehen auch der Grund sind, warum 
Microsoft bei 64bit-Windows long bei 32bit belassen hat. Die Probleme 
bei der Umstellung sind schon groß genug, da wollten sie dieses Fass 
nicht auch noch aufmachen. Allerdings bricht das dann mit einer anderen 
häufig zu findenden Annahme, nämlich dass ein long gross genug ist, um 
einen Pointer aufzunehmen.

Andreas

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Sven P. schrieb:
> Also besser jetzt Review machen
> und sauber arbeiten, als später wieder mal aufs Maul zu fallen, oder?

Das kann man nicht pauschal beantworten.

Das Review müsste für jede einzelne Anwendung geschehen, wohingegen 
beim Bereitstellen einer Ausführungsumgebung für Altanwendungen das 
Problem gleich für sehr viele Anwendungen gelöst wird.

Und selbst wenn der Quellcode vorliegt, kann man nicht "einfach 'mal so" 
neu kompilieren und dabei alle Probleme beheben. Wir sehen es ja im 
Linux-Bereich, wo zwar für den meisten Kram der Quellcode vorliegt, aber 
sowohl das Neukompilieren von Altanwendungen auf aktuellen 
Distributionen als auch das Kompilieren neuer Anwendungen auf alten 
Distributionen ist ja oft mit erheblichem Aufwand verbunden.

Ich hatte gerade vor ein paar Wochen das Problem, eine neue Version von 
Amanda auf einem openSUSE 10.2 zum Laufen zu bringen. Es ist mir bisher 
nicht gelungen, da eine wesentliche Bibliotheksabhängigkeit nicht 
aufgelöst werden kann. Die Bibliothek ebenfalls upzudaten würde einen 
unglaublichen Rattenschwanz an Updates nach sich ziehen.

Obwohl ich Linux-Aktivist der ersten Stunde und auch Kernelentwickler 
bin, habe ich von der o.a. Aktion Abstand genommen.

Gerade die Open-Source-Thematik führt meines Erachtens genau zu dem 
Effekt, dass sich Entwickler zu wenig Gedanken um Altsysteme machen, 
sondern sich darauf verlassen, dass der Anwender den Kram auf dem 
Altsystem neu kompilieren kann/muss. Damit ist das System ziemlich ad 
absurdum geführt.

Da die Zahl von Softwarepaketen für Linux immer weiter ansteigt, sind 
auch die Distributionsanbieter nicht mehr in der Lage, all die Pakete 
neu zu kompilieren und einer Qualitätskontrolle zu unterziehen.

Somit sind wir ein sehr, sehr großes Stück davon entfernt, den 
gewünschten Idealzustand zu erreichen.

Auch darf man nicht vergessen, dass die unter Linux am meisten genutzten 
APIs auch schon zwanzig und mehr Jahre alt sind. Dateizugriffe basieren 
in den allermeisten Fällen auch nur auf dem tradierten 
User/Group/Other-Konzept und nicht auf ACLs und ähnlichen Mechanismen. 
Der Open-Source-Charakter hat da keineswegs zu einer durchgängigen 
Modernisierung geführt, eben weil die Umstellungen von Anwendungen sehr 
aufwändig und mit unüberschaubaren Seiteneffekten verbunden sein können.

von Haku-aus-einem-unsicheren-Netz (Gast)


Lesenswert?

Aber weshalb z.B. hält sich besagtes Problem in den Winsock-Routinen 
dann so hartnäckig? Ebendort wäre es doch problemlos möglich, statt 
ushort einen uint16_t zu verbauen, ohne auch nur irgendetwas zu 
verändern.

Dennoch findet man dort heute noch diesen Murks. Das hätte man doch z.B. 
in einem Zug mit der Umstellung auf IPv6 erledigen können?

von Sven P. (Gast)


Lesenswert?

Und danke für die vielen Erläuterungen, da waren (wie erwartet) jede 
Menge Aspekte dabei, die für mich als Hobbyist eher nebensächlich waren 
oder von denen ich eigentlich nicht erwartet hätte, dass sie derart 
sperrig sind.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sven P. schrieb:
> Und danke für die vielen Erläuterungen, da waren (wie erwartet) jede
> Menge Aspekte dabei, die für mich als Hobbyist eher nebensächlich waren
> oder von denen ich eigentlich nicht erwartet hätte, dass sie derart
> sperrig sind.

Du bist ja gerade dabei, einen 80-Bit Taschenrechner zu stricken. 
Versuch doch einfach mal, den Code so zu schreiben, daß er auf 8-, 16- 
und 32-Bit Systemen sowohl lauffähig als auch nicht unnötig ineffizient 
ist. Du wirst feststellen, daß dein Code nicht gerade hübscher wird.

Als erstet fliegen die uint8_t für Schleifen rausraus, weil der auf 
32-Bit Systemen sehr ungünstig sind. Ok, dafür gibt es den uint_fast8_t, 
den du stattdessen verwenden solltest. Wie machst du es mit dem 
Speicher? Hier sind 32-Bit Werte besser als 8-Bit Werte. Also möchtest 
du auch da vermutlich den uint_fast8_t verwenden. Ok. Wieviel 
Arrayelemente brauchst Du? Dafür bemühen wir sizeof -- doch stop, 
verschissen. 80 geht nicht durch 32. Also nehmen wir ein uint16_t, der 
schon die ersten Abstriche in der Performance bring, etwa bei 
Multiplikation. Das Ergebnis ist ein uint32_t -- oder doch lieber ein 
uint_fast32_t was evtl ein 64-Bit Wert ist...?

Nächste Hürde sind die impliziten Casts in C. Die C Spez bezieht sich 
idR auf int bzw. unsigned int. Um unangenehme Nebeneffekte 
auszuschliessen beginnst Du bei jedem Ausdruck ne Cast-Orgie.

Nächstes Thema: Endianess...

Nächstes Thema: Alignment in Structs/Unions, Alignments von 
Speicherzugriggen (auf AVR kein Thema) ...

Nächstes Thema: Pointer-Casts

etc, etc.

Klar, dein Projekt machst du nur für AVR, und auch nur für eine 
bestimmte Sorte oder sogar nur für einen bestimmten µC. Aber du siehst, 
wie schnell das Kreise zieht und Aufwand erzeugt.

Vor allem auch für Plattformen, auf denen dein Code eh nie laufen wird 
-- oder doch?

von Sven P. (Gast)


Lesenswert?

Hm, das wird offenbar furchtbar kompliziert, zweifellos.

Wie sieht es denn aber aus, wenn es darum geht, Standards umzusetzen, 
wie es bei dem htons() & Co. der Fall ist?
In solchen Fällen dürfte die Lage doch eindeutig sein, oder?


Zu den Cast-Orgien: Die sind wirklich eine Qual. Auch da: Strenggenommen 
sind die doch Pflicht -- alles andere ist wieder Murks und Kaffeesatz?!


Immerhin weiß ich immer mehr darum, PCs nicht zu mögen :-)

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Als erstet fliegen die uint8_t für Schleifen rausraus, weil der auf
> 32-Bit Systemen sehr ungünstig sind.

Wobei GCC sich einen Dreck drum schert, denn dessen Erkennung solcher 
Schleifenmuster ersetzt den Zähler sowieso durch int, in der sicheren 
Gewissheit, das wäre der effizienteste Datentyp. Weshalb AVRs gekniffen 
sind, denn so zählt er leider in 16 Bits statt 8 Bits obwohl 
ausdrücklich ein 8 Bit Typ verwendet wurde.

von Sven P. (Gast)


Lesenswert?

A. K. schrieb:
> Wobei GCC sich einen Dreck drum schert, denn dessen Erkennung solcher
> Schleifenmuster ersetzt den Zähler sowieso durch int, in der sicheren
> Gewissheit, das wäre der effizienteste Datentyp. Weshalb AVRs gekniffen
> sind, denn so zählt er leider in 16 Bits statt 8 Bits obwohl
> ausdrücklich ein 8 Bit Typ verwendet wurde.

Kann ich aber nicht bestätigen. Ich benutze avr-gcc 4.3.5 und habe 
einige Schleifen mit int8_t-Zählvariablen, die er auch allesamt brav in 
ein einzelnes 8-Bit-Register packt.

An anderer Stelle verkorkst er dafür aber. Statt einfach am 
Schleifenende (Schleife zählt rückwärts) ein 'dec' + 'brne' zu setzen, 
dekrementiert er den Zähler zu Beginn, rechnet den Schleifenkörper, 
prüft dann mit 'cpi' den Zähler und springt dann erst :-/

von (prx) A. K. (prx)


Lesenswert?

Sven P. schrieb:

> Kann ich aber nicht bestätigen.

War ich grad eben drüber gestolpert, als ich den Vektorcode aus deinem 
anderen Thread durch GCC jagte. Kam
      cp r30,r22
      cpc r31,r23
      brne .L2
bei raus, trotz uint8_t als Zähler. avr-gcc 4.3.4, -O1.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Johann L. schrieb:
>
>> Als erstet fliegen die uint8_t für Schleifen rausraus, weil der auf
>> 32-Bit Systemen sehr ungünstig sind.
>
> Wobei GCC sich einen Dreck drum schert, denn dessen Erkennung solcher
> Schleifenmuster ersetzt den Zähler sowieso durch int, in der sicheren
> Gewissheit, das wäre der effizienteste Datentyp.

Da ist bestenfalls für einfache Schleifen der Fall. Bei komplizierterem 
Code wäre es viel zu aufwändig, die möglichen Werte der Variablen zu 
bestimmen bzw. ist nicht mehr möglich. Value range propagation et al. 
haben eben auch ihre Grenzen, und hier ist es woe so oft: Wenn der 
Programmierer mehr wissen hat als der Compiler hat (oder in der Lage ist 
herauszufinden), ist das Ergebnis suboptimal.

GCC schert sich nicht "einen Dreck" um solche Datentypen. Er beachtet 
deren Semantik genau und setzt das Programm danach um in IL. Die 
Optimierungen kommen erst danach, und müssen (oder sollen) alles 
rausfischen, was ineffizient ist, was eben noch nicht immer perfekt 
gelingt.

von Simon K. (simon) Benutzerseite


Lesenswert?

Kann natürlich gut sein, wenn du mit O1 (also auf Speed) optimierst. Da 
der Compiler ja denkt, dass es "schneller" geht. Üblicherweise 
kompiliert man ja mit -Os, wo er wahrscheinlich dann die Erweiterung auf 
16 Bit sein lässt, da das mehr Platz braucht als ein 8Bit  Zähler (der 
ja au ausreicht).

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> GCC schert sich nicht "einen Dreck" um solche Datentypen. Er beachtet
> deren Semantik genau und setzt das Programm danach um in IL.

Schon klar, keep cool ;-).

Nur entbehrt es nicht einer gewissen Ironie, wenn der für AVR optimierte 
und auf 32-Bittern suboptimale aussehende Quelltext ausgerechet auf den 
16/32-Bittern effizient und auf dem AVR leicht ineffizient ist.

von (prx) A. K. (prx)


Lesenswert?

Simon K. schrieb:

> Kann natürlich gut sein, wenn du mit O1 (also auf Speed) optimierst. Da
> der Compiler ja denkt, dass es "schneller" geht. Üblicherweise

-Os und -O2 liefern zwar einen anderen Code, der ist aber ebenso 
umständlich.

Korrektur: Der Zähler fliegt ganz raus, also nicht "int" wie ich oben 
schrieb, aber es wird einer der laufenden Pointer verglichen:
        ldi r24,hi8(vector0+10)
        cpi r30,lo8(vector0+10)
        cpc r31,r24
        brne .L2

Dummerweise ist das schlechter als ein separater 8-Bit Zähler, erst 
recht wenn dekrementierend.

von Rolf Nagnus (Gast)


Lesenswert?

Man muß hier aber auch bedenken, daß gcc eigentlich gar nicht für 
8-Bit-Prozessoren gemacht wurde und daß avr ein verhältnismäßig selten 
verwendetes Target ist. Es gibt nicht viele avr-Programmierer, die Lust 
haben und sich zutrauen, am gcc mitzuentwicklen und dort Optimierungen 
zu implementieren. Entsprechend bekommt der avr-Zweig im Vergleich zu 
Architekturen wie ARM oder x86 recht wenig Aufmerksamkeit.

von Rolf Magnus (Gast)


Lesenswert?

Man muß hier aber auch bedenken, daß gcc eigentlich gar nicht für 
8-Bit-Prozessoren gemacht wurde und daß avr ein verhältnismäßig selten 
verwendetes Target ist. Es gibt nicht viele avr-Programmierer, die Lust 
haben und sich zutrauen, am gcc mitzuentwicklen und dort Optimierungen 
zu implementieren. Entsprechend bekommt der avr-Zweig im Vergleich zu 
Architekturen wie ARM oder x86 recht wenig Aufmerksamkeit.

von (prx) A. K. (prx)


Lesenswert?

Klar, aber mir erscheint fraglich, ob man da viel dran drehen kann ohne 
wesentlich beim maschinenübergreifenden Teil mitzumischen, der für 
solche Optimierungen verantwortlich zeichnet. Der geht von bestimmten 
Grundannahmen aus, und eine davon ist die Wortverarbeitung als 
kostengünstigste Maschinenoperation. C ist schon als Sprache so 
konzipiert, und es stimmt ja auch fast immer - aber 8-Bitter sind dabei 
eben gekniffen.

Man muss also bei entsprechenden Anpassungen genau da reingreifen, wo 
sich auch alle anderen GCC Maintainer angesprochen fühlen können, nicht 
nur die für's AVR Target.

von Andreas F. (aferber)


Lesenswert?

Soweit ich erkennen kann, scheint die Erweiterung des Schleifenzählers 
auf 16bit (bzw. die Nutzung eines Pointers als Zähler) immer dann zu 
passieren, wenn die Schleifenvariable als Index in ein Array benutzt 
wird.

Vermeiden lässt sich das, indem stattdessen zusätzlich ein Pointer 
mitgeführt und inkrementiert wird.

Statt
1
uint8_t foo(const uint8_t data[10])
2
{
3
        uint8_t x = 0;
4
5
        for (uint8_t i = 0; i < 10; i++) {
6
                x |= data[i];
7
        }
8
9
        return x;
10
}

also besser
1
uint8_t foo(const uint8_t data[10])
2
{
3
        uint8_t x = 0;
4
5
        const uint8_t *p = &data[0];
6
        for (uint8_t i = 10; i > 0; i--) {
7
                x |= *p++;
8
        }
9
10
        return x;
11
}

Die erste Variante ergibt kompiliert mit -O2 bei mir 32 Byte Code, die 
zweite nur 18 Byte.

Da die Schleifenvariable jetzt nicht mehr als Index benötigt wird, kann 
ausserdem rückwärts gezählt werden, was (wenn die Schleife bei 0 endet) 
auch noch den Vergleich am Schleifenende einspart (es können direkt die 
Flags vom Dekrementieren der Variable mittels SUBI ausgewertet werden).

Interessanterweise stellt das zumindest bei dem konkreten Beispiel oben 
übrigens noch nichtmal unbedingt eine Verschlechterung auf anderen 
Architekturen dar, wie ich erwartet hätte. Erstaunlich, aber der 
generierte Code auf x86 ist in beiden Fällen komplett identisch.

Andreas

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Andreas Ferber schrieb:
> Soweit ich erkennen kann, scheint die Erweiterung des Schleifenzählers
> auf 16bit (bzw. die Nutzung eines Pointers als Zähler) immer dann zu
> passieren, wenn die Schleifenvariable als Index in ein Array benutzt
> wird.

Ich bin Deiner Angabe nachgegangen und habe in meinem AVR-Source solche 
Stellen mal rausgesucht. Die Ersetzung des Array-Zugriffs auf 
Pointer-Zugriff hat durchgehend eine Ersparnis von ca. 20 Bytes pro 
Schleife gebracht.

Bisher dachte ich, der gcc wirds schon richten, da kommt derselbe Code 
raus - egal ob ich einen Array- oder Pointerzugriff mache. Das ist auch 
größtenteils auf 32-Bit-Plattformen so. Aber niemals hätte ich gedacht, 
dass der gcc eine 16-Bit-Indexvariable "einschleust", wenn ich diese 
doch explizit als uint8_t definiere.

Gruß,

Frank

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.