welche Datentypen soll ich nehmen?

Gast #6734054
Lesenswert?

Hallo,

ich habe angefangen mit einem AVR (Atmega328 auf einem Arduino Board) zu 
programmieren.

ich habe mal eine generelle frage zu den Datentypen.

wenn ich stdint.h includiere, kann ich uint8_t, uint16_t usw. benutzen.
Ansonsten muss ich bei den default typen bleiben unsigned char, unsigned 
int usw.

Welche Datentypen sollte ich der richtigkeithalber nehmen?

Und wieso nimmt man obwohl man stdint.h includiert hat für strings 
trotzdem noch char?
#6734069
Lesenswert?

Hi
die Variable vom Typ "int" ist der Urgrossvater der variablen in der 
Sprache C. Als das entwickelt wurde haben sich die Väter der Sprache 
keine grossen Gedanken gemacht, was denn das jetzt ist. Denn es ist 
klar, dass int der Registergröße des Prozessors entspricht. Dummerweise 
kam nach den 8-Bit Prozessoren die 16-Bitter, dann die 32.Bitter, also 
was ist int? Um da jetzt Klarheit reinzubringen wurden diese Variablen 
wie uint8_t oder uint16_t definiert um ganz klar darustellen was das 
jetzt für ne Variable sein soll. Und das ist jetzt portabel, also 
unabhängig vom Prozessor.
Und string gibts eigentlich in C nicht. Ein string ist daher ein Array 
aus Charactern, also "char" oder char[]


Gruß
Gerhard
Gast #6734080
Lesenswert?

Felix schrieb:
> Welche Datentypen sollte ich der richtigkeithalber nehmen?

Die Datentypen welche sich bei deinem Problem sinnvoll einsetzen lassen!
Da das Problem geheim ist, ist mir leider keine konkretere Antwort 
möglich.

Felix schrieb:
> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?
Weil Strings eben aus Buchstaben bestehen....
Würde ich mal sagen....
Gast #6734092
Lesenswert?

Arduino Fanboy D. schrieb:
> Weil Strings eben aus Buchstaben bestehen....
> Würde ich mal sagen....

Das ja, aber wenn ich nach dieser Tabelle gehe 
https://de.wikibooks.org/wiki/C-Programmierung_mit_AVR-GCC/_Datentypen, 
dann ist char mit uint8_t ersetzt worden.

Das beides das selbe ist, ist mir bewusst. Aber es ist mir trotzdem 
schon öfters aufgefallen, dass für variablen uint8_t, uint16_t usw. 
genommen worden ist und für Arrays aus charachtern (danke Franko P.) 
eben char.

Franko P. schrieb:
> Um da jetzt Klarheit reinzubringen wurden diese Variablen
> wie uint8_t oder uint16_t definiert um ganz klar darustellen was das
> jetzt für ne Variable sein soll. Und das ist jetzt portabel, also
> unabhängig vom Prozessor.

Dann werde ich auch alles in uint8_t uint16_t usw. machen
#6734296
Lesenswert?

Felix schrieb:
> wenn ich stdint.h includiere, kann ich uint8_t, uint16_t usw. benutzen.
> Ansonsten muss ich bei den default typen bleiben unsigned char, unsigned
> int usw.

So weit, so offensichtlich.

> Welche Datentypen sollte ich der richtigkeithalber nehmen?

Den passenden! Und wenn das Programm nicht länger als nötig werden soll; 
den kürzesten passenden.

> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?

Wieso "trotzdem"? Wenn man ein char haben will, nimmt man ein char. 
Wieso sollte das davon abhängen, welche Typen es außerdem noch gibt?
#6734376
Lesenswert?

Erinnere dich dann auch daran, dass es noch fastest und least Typen 
gibt, die oft angebrachter sind.

uint8_least_t funktioniert auch, wenn CHARBITS größer als 8 ist.

Die int8_t Familie nimmt man zum rechnen, Sie sind ein Ersatz für signed 
char und unsigned char.
Für char ist vom Standard nicht vorgegeben, ob es signed oder unsigned 
ist.
Zudem sind alle Stringfunktionen der Standard Library auf char 
ausgelegt.
Gast #6734392
Lesenswert?

Franko P. schrieb:
> Dummerweise kam nach den 8-Bit Prozessoren die 16-Bitter, dann die
> 32.Bitter,
 8 Bitter kamen erst später für C. Die Väter haben sich große Gedanken 
gemacht, die vielen verschiedenen Architekturen zu unterstützen. Darum 
gab es 3 char, und 6 int.

Erst später haben sich Architekturen mit 8*2^n Bit durchgesetzt. Und nur 
deshalb konnte man konkret werden,

> wurden
> diese Variablen wie uint8_t oder uint16_t definiert um ganz klar
> darustellen was das jetzt für ne Variable sein soll


Verwendung:  char für Strings.

Ob Du nun unsigned char oder uint8_t für Zahlen verwendest, ist eher 
Geschmacksache. Der Vorteil von (u)int8_t: es knallt wenn der Prozessor 
keinen 8bit-typen hat. Bei unsigned char wirst Du höchstwahrscheinlich 
auf die Schnauze fallen, weil Du erwartest, dass nach 255 0 kommt.

Bei 16- oder 32 bit-Zahlen hilft ein uintXX_t nur bedingt. Spätestens 
bei Vergleichen, Rechnungen oder printf musst Du wissen, wie groß int 
ist oder größeren Aufwand treiben.

Beispiele: (keine Zeile funktioniert wie erwartet)

uint16_t u=10000*8/4;

uint16_t u2=100; int16 i=-1; if(i<u2) ...

printf("%u, %u", u, u2);
#6734411
Lesenswert?

Felix schrieb:
> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?

Weil char der Typ für Text ist.

Franko P. schrieb:
> Hi
> die Variable vom Typ "int" ist der Urgrossvater der variablen in der
> Sprache C. Als das entwickelt wurde haben sich die Väter der Sprache
> keine grossen Gedanken gemacht, was denn das jetzt ist. Denn es ist
> klar, dass int der Registergröße des Prozessors entspricht. Dummerweise
> kam nach den 8-Bit Prozessoren die 16-Bitter, dann die 32.Bitter, also
> was ist int? Um da jetzt Klarheit reinzubringen wurden diese Variablen
> wie uint8_t oder uint16_t definiert um ganz klar darustellen was das
> jetzt für ne Variable sein soll. Und das ist jetzt portabel, also
> unabhängig vom Prozessor.

Ziemlich genau falsch herum. Die ursprünglichen Typen wurden gerade aus 
Gründen der Portabilität nicht auf bestimmte Größen festgezurrt. Damit 
lässt sich ein effizienter C-Compiler für fast jede erdenkliche 
Plattform erzeugen, was auch geschehen ist. Da man manchmal aber feste 
Größen braucht (oft in dem Teil des Code, der prozessorspezifisch und 
damit sowieso unportabel ist), wurden entsprechende typedefs mit C99 
nachgerüstet.

MaWin schrieb:
> Ingo E. schrieb:
>> char hat minimal 8bit; ein int8_t hat genau 8bit ("exact width
>> type")
>
> außerdem ist char entweder signed oder unsigned, je nach
> Compiler(-optionen).
> int8_t hingegen ist immer signed.

char sollte man auch nie für was anderes als für Text einsetzen. Da man 
damit nicht rechnet, spielt auch die Signedness keine Rolle.

Kevin M. schrieb:
> Thomas Z. schrieb:
>> beim ARM ist das allerdings nicht mehr so. Ich habe aber gemerkt, dass
>> auch dort in vielen Fällen mit kurzen Variablen kompakterer Code
>> generiert wird.
>
> Zauberei?

Vermutlich. Ich hätte erwartet, dass der Code etwas größer wird.

A. S. schrieb:
> Spätestens bei Vergleichen, Rechnungen oder printf musst Du wissen, wie
> groß int ist oder größeren Aufwand treiben.

Du musst wissen, welchen Zahlenraum der Typ mindestens können muss, 
damit die Rechnung hinein passt.

A. S. schrieb:
> uint16_t u2=100; int16 i=-1; if(i<u2) ...

Signed und unsigned zu mischen, ist sowieso oft keine gute Idee.
Gast #6734414
Lesenswert?

Typischerweise faengt man mit int an (fuer zahlen, counter etc)
und erst wenn einem das Ram ausgeht, probiert man halt arrays etc auf 
int8_t
uint8_t etc umzustellen.

Fuer strings eigentlich immer char*. Da ist es ganz egal ob stdint 
inkludiert ist oder nicht. Solange du nicht herumkonvertierst isses auch 
egal ob der jetzt signed oder unsigned ist.

Das unsigned ist wohl wegen der funktionen im ctype.h
<https://stackoverflow.com/questions/17975913/does-ctype-h-still-require-unsigned-char>;

(ist halt die Frage ob du die verwenden willst.)
Gast #6734441
Lesenswert?

Felix schrieb:
> Welche Datentypen sollte ich der richtigkeithalber nehmen?

Das bleibt dir überlasssen. Ich verwende auf embedded Code fast 
ausschließlich die stdint Typen, da ich dadurch die Bit-Breite der 
Variablen absolut im Griff habe und nicht jedes mal nachdenken muss, wie 
groß ein "in"t oder ein "long int" etc. auf der aktuellen Architektur 
sind.

Man kann sich bspw nur auf gewisse Randbedingungen verlassen:
1) ein char ist immer "1" groß. => sizeof(char) ist immer 1!
2) ein Integer "int" ist mindestens 16 bit breit. (AVR: 16 bit, x86: 32 
bit)
3) etc...

Felix schrieb:
> Und wieso nimmt man obwohl man stdint.h includiert hat für strings
> trotzdem noch char?

Weil ein "char" der Korrekte Typ für einen Buchstaben (Text) ist. Das 
ist wie oben geschrieben auch safe, da ein char nach C-Standard immer 1 
groß ist.

Das Hauptproblem an der Sache ist, dass C nicht spezifiziert, wie groß 
die anderen Typen sind, bzw. eben nur in auslegbaren Grenzen.
#6734529
Lesenswert?

(prx) A. K. schrieb:
> Für Text im Speicher in struct oder array ist "char" sinnvoll. Für einen
> Buchstaben in einer lokalen Variablen gilt das nicht zwangsläufig auch.

Für ein Zeichen schon.

> Besonders nicht bei Nettigkeiten wie getchar():
>     char c;
>     while ((c = getchar()) != EOF)
>        ...
> geht garantiert irgendwie in die Hose.

Klar. Der Returntyp von getchar() ist ja auch nicht char.
Auch wenn der Name ("get char") das suggeriert.
#6734589
Lesenswert?

Dirk B. schrieb:
> Axel S. schrieb:
>> Klar. Der Returntyp von getchar() ist ja auch nicht char.
>> Auch wenn der Name ("get char") das suggeriert.
>
> Der Name passt schon, es wird ja ein char vom Stream gelesen.

Jein. Aus dem Stream wird schon ein Zeichen gelesen. Das wird 
allerdings nicht direkt zurück gegeben, weil man dann ja EOF oder einen 
Fehler nicht in den Returnwert tun könnte (inband signaling).

Also wird das gelesene unsigned(!) char in ein signed(!) int konvertiert 
und bei Bedarf eine magische Konstante (eben EOF) zurück gegeben. Ja, 
der eigentliche Fehler ist das inband signaling.
#6734593
Lesenswert?

A. S. schrieb:
> Rolf M. schrieb:
>> Du musst wissen, welchen Zahlenraum der Typ mindestens können muss,
>> damit die Rechnung hinein passt.
>
> Das ist ja durch intxx_t in den Beispielen klar.

Damit ist es aber meistens überspezifiziert. Wichtig ist, dass deine 
Werte rein passen, d.h. eine Mindestgröße reicht. Eine exakte Größe ist 
da nicht nötig. Für das beste Optimierungspotenzial ist es sinnvoll, 
möglichst genau zu nennen, was man braucht und nur das. Das lässt dem 
Compiler mehr Freiheiten.

> Trotzdem hängt das Verhalten von der Größe von int ab.

Klar, man muss die Mindestgrößen eben kennen und darf für portablen Code 
int nicht für Werte größer 32767 verwenden. Aber man hat ja alle 
Möglichkeiten, den Typ passend zu wählen. Entweder nimmt man int und 
akzeptiert das, weiß aber auch, dass es 32 oder gar 64 Bit groß sein 
kann. Oder man nimmt, wenn es auf Platzsparen kommt, dafür int_least16_t 
und bekommt das kleinstmögliche, was diesen Wertebereich kann, und das 
sogar auf Prozessoren, die gar keinen exakt 16 Bit großen Typen kennen. 
Oder man nimmt int_fast16_t, wenn es einem eher auf Geschwindigkeit 
ankommt. Dann kann der Compiler notfalls auch einen größeren Typen 
nehmen, wenn der auf diesem Prozessor effizienter ist.
Mit einem int16_t geht nichts davon, ohne dass man dafür irgendeinen 
echten Vorteil hätte. Die einzige Ausnahme sind die Stellen, für die die 
exakte Größe wirklich zwingend erforderlich ist, wie z.B. Zugriff auf 
Hardware-Register.

Axel S. schrieb:
> Klar. Der Returntyp von getchar() ist ja auch nicht char.
> Auch wenn der Name ("get char") das suggeriert.

Tatsächlich verwendet C für einzelne Zeichen durchgängig int als Typ. So 
wird z.B. auch bei Funktionen toupper() oder isalpha() immer int 
verwendet. Nur bei Arrays/Pointern darauf wird char verwendet.
#6734645
Lesenswert?

A. S. schrieb:
> Zeichen sind meist int in c. Nur in Strings oder %c sind es char.

Etwas genauer zu %c: da geht es nur bei den scanf-Funktionen um char 
bzw. char*.
Bei den printf-Funktionen wird tatsächlich eine int übergeben (von der 
dann aber nur das unterste char ausgegeben wird).

A. S. schrieb:
> Bei 16- oder 32 bit-Zahlen hilft ein uintXX_t nur bedingt. Spätestens
> bei Vergleichen, Rechnungen oder printf musst Du wissen, wie groß int
> ist oder größeren Aufwand treiben.
>
> Beispiele: (keine Zeile funktioniert wie erwartet)
>
> uint16_t u=10000*8/4;
>
> uint16_t u2=100; int16 i=-1; if(i<u2) ...
>
> printf("%u, %u", u, u2);

Kommt drauf an, woher deine Erwartungen kommen :-)

Wenn man die Formatkürzel aus inttypes.h nimmt für die (u)intxx_t 
anstatt der falschen Formatbeschreibungen wie in deinem Beispiel, 
entspricht es zumindest meinen Erwartungen.
Gast #6734745
Lesenswert?

Rolf M. schrieb:
>> Trotzdem hängt das Verhalten von der Größe von int ab.
>
> Klar, man muss die Mindestgrößen eben kennen und darf für portablen Code
> int nicht für Werte größer 32767 verwenden.

Du hast die Beispiele anscheinend nicht verstanden: Es geht nicht um den 
Wertebereich der Variablen (der ist fest), sondern dass (obwohl int 
nirgendwo auftaucht) das Verhalten auf Plattformen mit 16 Bit ein völlig 
anderes ist als auf Plattformen mit 32 Bit.
Gast #6734766
Lesenswert?

Klaus W. schrieb:
> Kommt drauf an, woher deine Erwartungen kommen :-)

Wie Du an anderen Kommentaren siehst, sind sich einige der 
unterschiedlichen  Verhalten nicht mal bewusst. Da ist ganz leichtfertig 
ein "immer uintxx_t" gefällt und plötzlich sieht man sich damit 
konfrontiert, sämtliche Formatspecifier umzubauen. Noch schlimmer, von 
reinen Strings im Quelltext auf weniger flüssige Konstrukte zu wechseln 
(die ggf. auch schwieriger zu finden sind)

> Wenn man die Formatkürzel aus inttypes.h nimmt für die (u)intxx_t
> anstatt der falschen Formatbeschreibungen wie in deinem Beispiel,
> entspricht es zumindest meinen Erwartungen.
Aber nur das printf. Die beiden anderen Zeilen hängen noch immer von der 
int-Größe ab.

Ja, es gibt für alles Workarounds, klar. Doch irgendwo in der Matrix 
[Warnungen/Casts/implizite Typumwandlungen/..] hakt es dann doch, wenn 
ich den gleichen Code auf 16- und 32-Bit-Plattformen laufen lassen will. 
Und das ist z.B. immer dort der Fall, wo der 16er-µC-Code auch am PC 
läuft.
Beitrag #6734804 wurde von einem Moderator gelöscht.
#6734973
Lesenswert?

A. S. schrieb:
> Rolf M. schrieb:
>>> Trotzdem hängt das Verhalten von der Größe von int ab.
>>
>> Klar, man muss die Mindestgrößen eben kennen und darf für portablen Code
>> int nicht für Werte größer 32767 verwenden.
>
> Du hast die Beispiele anscheinend nicht verstanden:

Ich glaube, ich habe missverstanden, was du damit sagen wolltest.

> Es geht nicht um den Wertebereich der Variablen (der ist fest), sondern
> dass (obwohl int nirgendwo auftaucht) das Verhalten auf Plattformen mit
> 16 Bit ein völlig anderes ist als auf Plattformen mit 32 Bit.

int taucht quasi implizit in den Integer-Literalen auf. Dazu kommt dann 
noch ggf. Integer-Promotion.
#6735049
Lesenswert?

A. S. schrieb:
> Rolf M. schrieb:
>> int taucht quasi implizit in den Integer-Literalen auf.
>
> OK, dann ausführlich:

Warum?

> A. S. schrieb:
>> uint16_t u=10000*8/4;
> Nur bei 32 Bit 40.000,

Ich hätte ja 20.000 erwartet…

> sonst etwa 3600

Ja natürlich, weil 10000, 8 und 4 vom Typ int sind. Wie ich sagte: int 
taucht in den Literals implizit auf.

>> uint16_t u2=100; int16 i=-1; if(i<u2) ...
> Bei 32 Bit erfüllt, bei 16 nicht.

Ja, auch wie ich sagte: Integer Promotion.

>> printf("%u, %u", u, u2);
> Bei beiden OK ;-)

Ja, ist in dem Fall ok, weil unsigned int nicht kleiner sein kann als 
uint16_t, und wenn es größer sein sollte, macht das auf Grund der 
Integer Promotion nichts. Es kann aber trotzdem zu einer 
Compiler-Warnung führen.
Gast #6735054
Lesenswert?

A. S. schrieb:
> int16
Interessanter Datentype!


Dann, selbst wenn man _t dran hängt meldet mein Kompiler:
1
E:\Programme\arduino\portable\sketchbook\sketch_jun22b\sketch_jun22b.ino: In function 'void setup()':
2
E:\Programme\arduino\portable\sketchbook\sketch_jun22b\sketch_jun22b.ino:11:7: warning: comparison of integer expressions of different signedness: 'int16_t' {aka 'int'} and 'uint16_t' {aka 'unsigned int'} [-Wsign-compare]
3
   11 |   if(i<u2)cout << F("och")<< endl;
4
      |      ~^~~
Wenigstens "Verdächtig" ist es also schon, was du da zutun versuchst.


Zum anderen Punkt
1
E:\Programme\arduino\portable\sketchbook\sketch_jun22b\sketch_jun22b.ino:13:19: warning: integer overflow in expression of type 'int' results in '14464' [-Woverflow]
2
   13 |   uint16_t u=10000*8/4;
3
      |              ~~~~~^~
Auch da kommt der "Rechenfehler" nicht ganz überraschend, würde ich mal 
sagen!

uint16_t u=10000UL*8/4;
Das UL kostet einen doch nichts.....
Und "Zack" läuft es auf jedem Kesselchen, wie man sich das wünscht.
(wenn denn uint16_t darauf funktioniert)
Gast #6735071
Lesenswert?

Arduino Fanboy D. schrieb:
> Auch da kommt der "Rechenfehler" nicht ganz überraschend, würde ich mal
> sagen!

Ja klar geht das alles. Hab ich ja auch geschrieben. Und auch auf jene 
eingegrenzt, die nur wenige Warnungen haben, z.B. -Wall.

Die Diskussionen sind nur dann immer die gleichen, wenn es nicht geht: 
"Ne, auf dem PC gehts ja, ... und der Wertebereich ist auch nicht 
überschritten mit 40.000, ... Der Compiler ist kaputt, der kann kein *".
Gast #6735087
Lesenswert?

A. S. schrieb:
> Die Diskussionen sind nur dann immer die gleichen, wenn es nicht geht:
> "Ne, auf dem PC gehts ja, ... und der Wertebereich ist auch nicht
> überschritten mit 40.000, ... Der Compiler ist kaputt, der kann kein *".

Naja...
Das ist ja auch kein Wunder....

Da muss(te) jeder mal durch!
Nicht einer für alle, sondern jeder einzeln für sich.

Beim ersten mal, wird einem danach klar, dass es doch Sinn macht, die 
Sprachreferenz mal durchzublättern.
Beim zweiten mal kann man sich in den Arsch beißen.
Und bei jedem weiteren mal, sieht man es schon beim schreiben.

Der ganz normale Weg...
Einige der "Erstlinge" tauchen hier auf, was dann scheinbar zu "immer 
die gleichen Diskussionen" führt. Sind es aber nicht, da sich die 
Individuen unterscheiden.
#6735115
Lesenswert?

Wobei man die Feinheit beachten sollte, dass char zwar entweder das 
Vorzeichenverhalten von signed char oder von unsigned char hat, aber
  char
  unsigned char
  signed char
im Typsystem trotzdem drei formal verschiedene Typen darstellen.

Da (u)int8_t in stdint.h üblicherweise auf (un)signed char abgebildet 
wird, ist char also mit beiden nicht identisch.

In C wird man oft erst dann damit konfrontiert, wenn man die Warnungen 
einschaltet und dann vielleicht darauf hingewiesen wird, dass die 
"signedness" sich von beiden Alternativen unterscheidet.

In C++ spielt das eine grössere Rolle.
#6735148
Lesenswert?

(prx) A. K. schrieb:
> In C++ spielt das eine grössere Rolle, weil man sonst bei Templates
> schnell gegen die Wand läuft.

Oder bei überladenen Funktionen. So bricht der Compiler hier z.B. mit 
Fehler ab, weil er nicht weiß, welche Überladung er nehmen soll:
1
void foo(signed char)
2
{
3
}
4

5
void foo(unsigned char)
6
{
7
}
8

9
int main()
10
{
11
    char c = 5;
12
    foo(c);
13
}

Andererseits erstaunt dann, dass bei einem:
1
    char c = 65;
2
    signed char d = 66;
3
    unsigned char e = 67;
4
    uint8_t f = 68;
5
    int8_t g = 69;
6
    
7
    std::cout << c << d << e << f << g << '\n';
sämtliche Variablen als Text ausgegeben werden. Man könnte eigentlich 
erwarten, dass die Überladungen des <<-Operators so definiert sind, dass 
nur die erste als Text ausgegeben wird.
#6735152
Lesenswert?

Arduino Fanboy D. schrieb:
>> In C++ spielt das eine grössere Rolle, weil man sonst bei Templates
>> schnell gegen die Wand läuft.
>
> Da gibts dann aber ein  is_signed<char>::value oder auch
> is_signed<char>() für.
> Und seine ganzen Brüder.

Es geht freilich nicht nur um das Vorzeichen. Sondern auch um Overloads 
und Templates, in denen das drei verschiedene Typen sind. Und man das 
nicht vergessen darf.

An dieser Stelle wird es bei C vs C++ noch etwas tricky. Denn in C ist 
in alter Tradition 'x' vom Typ int - nicht etwa char, wie man annehmen 
sollte. Hätte man das in C++ genauso durchgezogen, wären
   char c;
   f(c);    // f(char)
   f('x');  // C: f(int), C++: f(char)
verschiedene Fälle. Weshalb in C++ 'x' vom Typ char ist.
Gast #6735157
Lesenswert?

Rolf M. schrieb:
> Man könnte eigentlich
> erwarten, dass die Überladungen des <<-Operators so definiert sind, dass
> nur die erste als Text ausgegeben wird.

Dann macht Arduino ja doch mal was richtig..... ;-)
1
#include <Streaming.h> // die Lib findest du selber 
2
Stream &cout {Serial}; 
3

4
void setup() 
5
{
6
  Serial.begin(9600);
7
  cout << F("Start: ") << F(__FILE__) << endl;
8

9
  char c = 65;
10
  signed char d = 66;
11
  unsigned char e = 67;
12
  uint8_t f = 68;
13
  int8_t g = 69;
14
    
15
    cout << c << d << e << f << g << '\n';
16
}
17

18
void loop() 
19
{
20

21
}

Ausgabe:
1
Start: E:\Programme\arduino\portable\sketchbook\sketch_jun22c\sketch_jun22c.ino
2
A66676869
Gast #6735177
Lesenswert?

Thomas Z. schrieb:
> - vorzeichenlose Typen verwenden wo möglich

Das ist unnötig gefährlich.

Sehr viele Berechnungen kommen teilweise in's Negative. Der Klassiker 
sind rückwärts zählende Schleifen.

Vorzeichenlose Typen produzieren gerne schwer zu findende Bugs.


> Für 8 Bit CPUs gilt
> - so kurz wie möglich uint8_t ist efektiver als uint16_t

In den allermeisten Fällen ist das eine unnötige Einschränkung. Auf 
8-Bit-CPUs ist bei den meisten Operationen nur ein einfacher Takt und 
Opcode mehr notwendig, für eine 16-Bit-Operation.

Ein int16_t sollte auf 8-Bit-CPUs der "Standard-Datentyp" sein. Damit 
kann man in der Regel den kompletten RAM addresieren. Die Größe passt 
gut zu einem 8-Bit-System.
Persönliche Seite #6735193
Lesenswert?

Kevin M. schrieb:
> Thomas Z. schrieb:
>> beim ARM ist das allerdings nicht mehr so. Ich habe aber gemerkt, dass
>> auch dort in vielen Fällen mit kurzen Variablen kompakterer Code
>> generiert wird.
>
> Zauberei?

Vorab: genau wissen tu ich es nicht.

Aber: ich gehe davon aus, dass bei kleinerem Wertebereich der Compiler 
mehr Tricks anwenden kann.
D.h. selbst wenn der Compiler ein int16_t und ein int32_t auf ARM beide 
in 4 Byte speichert, weil es eine 32-bit-Architektur ist, so kann er 
später doch zusätzliche Optimierungen anwenden. Weil der Compiler genau 
weiß, dass dein Programm niemals etwas größeres als 32767 speichern 
wird. (Das wäre nämlich undefined behaviour und der Compiler wäre dann 
frei, irgendwas beliebiges zu machen)

Das klingt jetzt vielleicht unglaubwürdig, weil, welche Tricks sollen 
das sein, wenn mir keiner einfällt?

Hier empfehle ich folgendes Video:
https://www.youtube.com/watch?v=w0sz5WbS5AM

Das ist die Keynote der Konferenz C++ on Sea 2019:
What Everyone Should Know About How Amazing Compilers Are - Matt Godbolt
#6735205
Lesenswert?

Tilo R. schrieb:
> Aber: ich gehe davon aus, dass bei kleinerem Wertebereich der Compiler
> mehr Tricks anwenden kann.

Es gibt beide Effekte.

Der andere zeigt sich bei:
 uint16_t x;
 x++;
denn ARMs haben keine 16-Bit Register.

Fall 1: x besteht ausschliesslich aus den unteren 16 Bits, die oberen 
sind undefiniert. Dann kann man mit dem Register oft nicht rechnen.

Fall 2: x ist mit Nullen erweitert im Register gespeichert. Dann muss 
man den Überlauf aus den unteren 16 Bits berücksichtigen, der ggf den 
zulässigen Wertebereich verletzt. Also hinterher maskieren.

Auch aufgrund solcher Sauereien gibt es uint_fast16_t.

Und zur Optimierung solcher Sauereien ist Überlauf mit Vorzeichen 
undefiniert. Rechne damit, dass dann ein Wert in x stehen kann, der da 
eigentlich nicht reinpasst - statt dem intuitiv angenommenen 
Modulo-Verhalten. Das erspart die Reduktion auf 16-Bits, mit 
Nebeneffekt.
Gast #6735223
Lesenswert?

Experte schrieb:
> Sehr viele Berechnungen kommen teilweise in's Negative. Der Klassiker
> sind rückwärts zählende Schleifen.
>
> Vorzeichenlose Typen produzieren gerne schwer zu findende Bugs.

In der Regel ist es genau umgekehrt. Signed-Typen dürfen NICHT 
überlaufen. Man muss z.B. vor einem ++ abfragen, ob das passiert. Bei 
einer Rechnung sowieso.

Konstrukte wie "while(u-->=0)" sind keine Bugs sondern Arroganz des 
Programmierers der alle Warnungen in den Wind schlägt.

Ich habe nichts gegen Schleifenzähler int i. Aber meist reicht unsigned 
und ist auch einfach sauberer. Z.B. weil bei Arrays die Prüfung auf 
beide Grenzen wegfällt (i>=0 && i<N). Weil der %-Operator über den 
ganzen Wertebereich gleichmäßig funktioniert. Weil ein Überlauf 
definiertes Verhalten hat. Weil eine Division durch 2^n durch >> ersetzt 
werden kann, ...
Persönliche Seite #6735300
Lesenswert?

A. S. schrieb:
> In der Regel ist es genau umgekehrt. Signed-Typen dürfen NICHT
> überlaufen. Man muss z.B. vor einem ++ abfragen, ob das passiert. Bei
> einer Rechnung sowieso.
>
> Konstrukte wie "while(u-->=0)" sind keine Bugs sondern Arroganz des
> Programmierers der alle Warnungen in den Wind schlägt.

Vollkommen korrekt!
Mein Beispiel war mit Bedacht gewählt. Der Signed-Überlauf ist undefined 
behaviour (im Gegensatz zu unsigned, wo das definiert ist). Es ist pures 
Glück, dass es so oft funktioniert.

Und wenn man ganz großes Pech hat, ist jahrelang funktionierender Code 
plötzlich kaputt, weil der neue Compiler eine zulässige, 
standardkonforme Optimierung macht.
Gast #6736377
Lesenswert?

Es ist ziemlich praxisfern zu argumentieren, dass unbeabsichtigte 
Überläufe mit undefinierten Verhalten schlimmer wären als 
unbeabsichtigte Überläufe mit definierten Verhalten.

In den allermeisten Fällen ist ein Überlauf in einem Programm eine 
Katastrophe.

Und genau hier liegt der Knackpunkt bei unsigned: Man arbeitet ständig 
an der "Klippe", der Null rum. Wenn durch einen Rundungsfehler in 
Berechnungen eine Ergebnis "etwas" unter Null rutscht, ist bei unsigned 
die Katastrophe vorprogrammiert, und bei signed oft folgenlos.

A. S. schrieb:
> Konstrukte wie "while(u-->=0)" sind keine Bugs sondern Arroganz des
> Programmierers der alle Warnungen in den Wind schlägt.

Wenn 'u' ein vorzeichenbehafteter Datentyp ist, wirst Du niemals eine 
Compilerwarnung bekommen, weil einfach nichts passieren kann. Das ist 
deutlich robuster als einen Datentyp zu verwenden, der potentiell 
gefährlich ist.

Zusätzlich ist bei vorzeichenlosen Datentypen die mentale Belastung von 
Programmierern höher, da sie sicherstellen müssen, niemals in den 
negativen Bereich zu rutschen.

Jede "räumliche Nähe" von vorzeichenlosen Datentypen und Minuszeichen 
ist eine scharfe Tretmine, die nur darauf wartet, ausgelöst zu werden.

Fehler in Software haben einen "Hard-Fact" als letzten Auslöser, z.B. 
einen Unterlauf einer vorzeichenlose Variable, aber der Weg dorthin wird 
geebnet durch unzählige "Soft-Facts", und dazu gehört die Verwendung 
eben solcher Datentypen die solche Unterläufe (in der Nähe der Null) 
erst ermöglichen.
Gast #6736466
Lesenswert?

Experte schrieb:
> Es ist ziemlich praxisfern zu argumentieren, dass unbeabsichtigte
> Überläufe mit undefinierten Verhalten schlimmer wären als
> unbeabsichtigte Überläufe mit definierten Verhalten.

Ganz im Gegenteil!
Ein definiertes Verhalten ist eine absolute Notwendigkeit.
Das undefinierte Verhalten, ist da eher auf dem Niveau einer tödlichen 
Falle.
#6736554
Lesenswert?

Arduino Fanboy D. schrieb:
> Experte schrieb:
>> Es ist ziemlich praxisfern zu argumentieren, dass unbeabsichtigte
>> Überläufe mit undefinierten Verhalten schlimmer wären als
>> unbeabsichtigte Überläufe mit definierten Verhalten.
>
> Ganz im Gegenteil!
> Ein definiertes Verhalten ist eine absolute Notwendigkeit.

In beiden Fällen baut dein Programm Mist. Im zweiten ist es immerhin 
definierter Mist.
Gast #6736578
Lesenswert?

Experte schrieb:
> In den allermeisten Fällen ist das eine unnötige Einschränkung. Auf
> 8-Bit-CPUs ist bei den meisten Operationen nur ein einfacher Takt und
> Opcode mehr notwendig, für eine 16-Bit-Operation.
>
> Ein int16_t sollte auf 8-Bit-CPUs der "Standard-Datentyp" sein. Damit
> kann man in der Regel den kompletten RAM addresieren. Die Größe passt
> gut zu einem 8-Bit-System.

NACK.

Sorry, aber wer nicht im Griff hat, welche Werte seine Daten annehmen 
können, kann auch mit int16_t Probleme bekommen. Es ist nur weniger 
Wahrscheinlich.

Es gilt: Als Datentype bevorzugt die "Datengröße" des µC, uint8_t bei 
8bit, uint16_t bei 16bit etc. Datentype ansolsten so groß wie nötig, so 
klein wie möglich.

Gruß

Robert
#6736834
Lesenswert?

Oliver S. schrieb:
> Der Robs schrieb:
>> Datentype ansolsten so groß wie nötig, so klein wie möglich.
>
> Oder so schnell wie möglich, oder so aligned wie möglich, oder...

Und idealerweise sich automatisch an die konkrete Plattform anpassend, 
wo es keinen konkreten Grund gibt, warum das an der Stelle nicht geht.

> Pauschalaussagen sind da nutzlos.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren