Hi. Ich habe hier gerade ein Problem mit der Funktion pec15. Die
berechnet mir den PEC Code für meine SPI Kommunikation.
Dazu Übergebe ich die Adresse von einem 8 Bit Array der meine Nachricht
enthält die ich senden möchte.
Hierbei handelt es sich ja um einen "call-by-reference" Aufruf richtig?
Ich verstehe nicht warum ich den Array zwingend als char übergeben muss.
Momentan muss ich das dann komplett hässlich und unnötig in einen char
array zwischenspeichern. Das ist ja irgendwie blöd.
Aber auch wenn ich bei der pec15 Funtkion den Datentyp von *data auf int
oder uint8_t ändere und den Array "Nachricht" dann auf den gleichen Typ
anpasse bekomme ich
"conflicting types for 'pec15'"
1
constuint16_tpec15Table[256]=
2
{
3
0x0000,
4
0xc599,
5
.....
6
0x4e3e,
7
0x450c,
8
0x8095
9
};
10
11
uint16_tpec15(char*data,intlen)
12
{
13
int16_tremainder;
14
int16_taddress;
15
16
remainder=16;
17
for(inti=0;i<len;i++)
18
{
19
address=((remainder>>7)^data[i])&0xff;
20
remainder=(remainder<<8)^pec15Table[address];
21
}
22
return(remainder*2);
23
}
24
25
voidSPI_Befehl_Senden(uint16_tBefehl)
26
{
27
charNachricht[2];
28
uint8_tSpiBufTx[4];
29
30
Nachricht[0]=SHORT_H_BYTE(Befehl);
31
Nachricht[1]=SHORT_L_BYTE(Befehl);
32
33
SpiBufTx[0]=Nachricht[0];
34
SpiBufTx[1]=Nachricht[1];
35
SpiBufTx[2]=SHORT_H_BYTE(pec15(Nachricht,2));
36
SpiBufTx[3]=SHORT_L_BYTE(pec15(Nachricht,2));
37
38
HAL_SPI_Transmit(&hspi1,SpiBufTx,4,1);//Warten bis gesndet wurde
39
}
Wenn ich einfach einen uint8_t Array an pec15 übergebe obwohl ich *data
weiterhin als char deklariert habe bekomme ich "nur" eine Warnung
pointer targets in passing argument 1 of 'pec15' differ in signedness
Kann mir da jemand helfen?
Gibt es überhaupt einen unterschied zwischen uint8_t und unsigned char?
Vielen vielen vielen Dank!
Mirco
Mirco G. schrieb:> Ich verstehe nicht warum ich den Array zwingend als char übergeben muss.
Weil dein Parameter als "char" definiert ist. Warum?
Mirco G. schrieb:> Momentan muss ich das dann komplett hässlich und unnötig in einen char> array zwischenspeichern.
Wie würdest du es lieber machen?
Mirco G. schrieb:> "conflicting types for 'pec15'"
Das klingt als hättest du vergessen, die Deklaration anzupassen. An sich
ist das eine gute Idee, denn:
Mirco G. schrieb:> address = ((remainder >> 7) ^ data[i]) & 0xff;
"remainder" ist "signed". Ein Rechts-Shift von "signed" Typen ist in C
und C++ plattformabhängig; es ist nicht definiert, ob das Sign-Bit mit
geshiftet wird. Definiere "remainder" lieber als "uint16_t". Wie sich
das xor "^" auf einem vorzeichenbehafteten "char" verhält weiß ich
gerade nicht. Es ist wahrscheinlich sinnvoll, wenn data[i] auch unsigned
wäre!
Also: Alles unsigned machen, du brauchst ja gar keine negativen Zahlen.
Mirco G. schrieb:> Gibt es überhaupt einen unterschied zwischen uint8_t und unsigned char?
uint8_t ist entweder:
- Ein Alias auf "char", falls "char" vorzeichenlos ist
- Ein Alias auf "unsigned char"
- Nichtexistent, z.B. wenn CHAR_BIT != 8
Mirco G. schrieb:> Nachricht[0]=SHORT_H_BYTE(Befehl);> Nachricht[1]=SHORT_L_BYTE(Befehl);
Wie sind diese Makros definiert?
Das ist nicht zufällig für den LTC6804... :)
Mirco G. schrieb:> Ich verstehe nicht warum ich den Array zwingend als char übergeben muss.
Weil deine "Nachricht" vom Typ "char []" ist.
Ein Zeiger darauf ist folglich "char *".
Arduino Fanboy D. schrieb:> Mirco G. schrieb:>> Gibt es überhaupt einen unterschied zwischen uint8_t und unsigned char?>> unsigned char kann durchaus 36 Bit breit sein
Aber nicht auf einem Arduino. :-)
SCNR …
Walter K. schrieb:> char Nachricht[2];> ...> Nachricht[0]=SHORT_H_BYTE(Befehl);> Nachricht[1]=SHORT_L_BYTE(Befehl);>> Wo steht im char-Array '\0' ?
Ich verstehe ehrlich gesagt nicht was du von mir willst :D.
Arduino Fanboy D. schrieb:> unsigned char kann durchaus 36 Bit breit sein
Ich dachte das wäre nur bei int so das es unteschiedliche größen haben
kann. Deswegen soll man ja immer uint8_t usw. benutzen. Ist das bei char
auch so?
Niklas G. schrieb:> Weil dein Parameter als "char" definiert ist. Warum?
Ich meinte doch das ich sowohl den Übergabeparamter der Funktion als
auch den Typ des Arrays aufeinander angepasst habe.
Niklas G. schrieb:> "remainder" ist "signed". Ein Rechts-Shift von "signed" Typen ist in C> und C++ plattformabhängig; es ist nicht definiert, ob das Sign-Bit mit> geshiftet wird. Definiere "remainder" lieber als "uint16_t". Wie sich> das xor "^" auf einem vorzeichenbehafteten "char" verhält weiß ich> gerade nicht. Es ist wahrscheinlich sinnvoll, wenn data[i] auch unsigned> wäre!
Oh gut zu wissen das wusste ich zum Beispiel nicht :D. Wo finde ich denn
dann raus wie es sich jetzt bei meinem STM32 verhält? Steht das im
Datenblatt?
Niklas G. schrieb:> Also: Alles unsigned machen, du brauchst ja gar keine negativen Zahlen.
Da hast du recht. Ich hab die Pec Berechnung aus dem Datenblatt von
meinem IC übernommen. Deswegen hab ich das einfach so gelassen. Die
werden sich da bestimmt was bei gedacht haben oder? Aber komisch das die
dann dort plattformabhängigen Code veröffentlichen.
Niklas G. schrieb:> Wie sind diese Makros definiert?
Sry vergessen.
1
#define SHORT_H_BYTE(x) ( ( x >> 8 ) & 0xFF )
2
#define SHORT_L_BYTE(x) ( x & 0xFF )
Niklas G. schrieb:> Das ist nicht zufällig für den LTC6804... :)LTC6813 also ähnlich nur eine Generation neuer.
Jörg W. schrieb:> Weil deine "Nachricht" vom Typ "char []" ist.>> Ein Zeiger darauf ist folglich "char *".
Siehe Antwort für "Niklas G."
Ich habe mittlerweile auch schon, durch Zufall, eine Lösung gefunden.
Die ich aber nicht wirklich verstehe :D.
Wenn ich Nachricht mit (char *) von uint8_t in char umcaste geht es. Das
hatte ich auch vorher schon probiert aber ohne das *.
1
voidSPI_Befehl_Senden(uint16_tBefehl)
2
{
3
uint8_tNachricht[2];
4
uint8_tSpiBufTx[4];
5
6
Nachricht[0]=SHORT_H_BYTE(Befehl);//Zerlegen in High und Low Byte
7
Nachricht[1]=SHORT_L_BYTE(Befehl);
8
9
SpiBufTx[0]=Nachricht[0];
10
SpiBufTx[1]=Nachricht[1];
11
SpiBufTx[2]=SHORT_H_BYTE(pec15((char*)Nachricht,2));//erstmal so umständlich weil unsicher mit der Pec berechnung
Mirco G. schrieb:> Ich dachte das wäre nur bei int so das es unteschiedliche größen haben> kann.
Nein, alle der Integer-Typen (char, short, int, long, long long) haben
keine fixe Größe, nur eine Mindest-Größe. Die (u)intXX_t Typen sind,
falls sie existieren, Aliase.
Mirco G. schrieb:> Deswegen soll man ja immer uint8_t usw. benutzen.
Wenn man eine fixe Größe haben möchte, ja.
Mirco G. schrieb:> Steht das im> Datenblatt?
Ja, im ARM Architecture Reference Manual. Allerdings können die ARM
(STM32 ist ja einer) sogar beide Möglichkeiten. Was der Compiler jetzt
verwendet, musst du im Compiler-Manual nachschlagen. Besser ist es aber,
sich da gar nicht erst drauf zu verlassen, und solche Konstrukte zu
vermeiden. Dann kann man den Code auch problemlos später auf andere
Plattformen übertragen, ohne ihn nach solchen Fallen durchsuchen zu
müssen.
Mirco G. schrieb:> Die> werden sich da bestimmt was bei gedacht haben oder?
Hardware-Hersteller sind oft nicht die großen Software-Experten...
Mirco G. schrieb:> Ich finde aber keine gescheite Erklärung warum ich das * benötige
Weil du ja nicht nur einen einzelnen char übergibst, sondern einen
Pointer auf 1 oder mehrere char'.
Aber, mach's doch einfach richtig:
1
uint16_tpec15(constuint8_t*data,size_tlen)
2
{
3
uint16_tremainder=16;
4
uint16_taddress;
5
6
for(size_ti=0;i<len;++i){
7
address=((remainder>>7)^data[i])&0xff;
8
remainder=(remainder<<8)^pec15Table[address];
9
}
10
return(remainder*2);
11
}
12
13
voidSPI_Befehl_Senden(uint16_tBefehl)
14
{
15
uint8_tSpiBufTx[4];
16
17
SpiBufTx[0]=SHORT_H_BYTE(Befehl);//Zerlegen in High und Low Byte
18
SpiBufTx[1]=SHORT_L_BYTE(Befehl);
19
20
uint16_tcrc=pec15(SpiBufTx,2);// Nicht zweimal berechnen!
21
SpiBufTx[2]=SHORT_H_BYTE(crc);
22
SpiBufTx[3]=SHORT_L_BYTE(crc);
23
24
HAL_SPI_Transmit(&hspi1,SpiBufTx,4,1);//Warten bis gesndet wurde
25
}
So ist alles Unsigned, du brauchst keine Casts, und da sollten jetzt
keine Plattform-Abhängigen Dinge mehr drin sein (bzw keine, die sich
kompilieren lassen aber heimlich etwas falsches tun).
Niklas G. schrieb:> Nein, alle der Integer-Typen (char, short, int, long, long long) haben> keine fixe Größe, nur eine Mindest-Größe.
Nur, damit das nicht falsch rüberkommt, die haben alle schon schon eine
fixe Größe, deren Wahl der Sprachstandard aber bis auf eine Mindestgröße
der Implementierung überlässt.
Oliver
Mirco G. schrieb:> hier wird es auf Folie 15 einfach als cast für Zeiger bezeichnet.
Ja. "Nachricht" ist vom Typ "uint8_t[2]". Das ist wahrscheinlich ein
Alias für "unsigned char[2]". C kann aber Arrays nicht konsistent
weiterverwenden oder übergeben. Daher wird, Wenn du "Nachricht"
verwendest, daraus automatisch ein "unsigned char*", ein Zeiger auf das
0. Element des Arrays. Durch Addition des Zeigerwerts kann man auch auf
die weiteren Elemente zugreifen.
Allerdings möchte "pec15" bei dir ein "char*" haben, also einen Zeiger
auf einen oder mehrere char's.
"char" hat die Besonderheit, dass "unsigned char", "signed char" und
"char" drei einzelne Typen sind, aber char entweder ein Vorzeichen hat
oder nicht (plattformabhängig). Es ist aber dennoch ein eigener Typ, der
sich lediglich gleich verhält zu einem der anderen beiden.
Auch wenn in C alle Zeiger (außer Funktionszeiger) letztlich gleich
funktionieren (es sind immer Adressen auf Speicherzellen), sind sie
nicht direkt austauschbar (außer zu void*) - wenn du versuchst einen
Zeiger auf Typ A wie einen Zeiger auf Typ B zu behandeln, gibt es
Compiler-Fehler/Warnungen. Dies soll vor Fehlern schützen, indem
verhindert wird, dass Daten anders behandelt werden als gedacht. Du
kannst dem Compiler aber sagen, dass du weißt dass das Ziel eines
Zeigers einen bestimmten Typ hat, und der Compiler diesen Typen annehmen
soll. So kannst du einen Zeiger auf A (also A*) auf einen Zeiger nach B
(also B*) konvertieren. Du hast hier also einen Zeiger vom Typ "unsigned
char*" nach "char*" umgewandelt, dem Compiler also gesagt "ich weiß dass
der Zeiger auf Nachricht eigentlich ein Zeiger auf char ist, nimm das so
hin". Das ist aber nicht ganz korrekt, denn Nachricht ist ja tatsächlich
"unsigned char[2]" - der Compiler wurde also angelogen!
Normalerweise ist so etwas gefährlich - z.B. einen "int*" nach "float*"
zu konvertieren und dann zu dereferenzieren kann beliebig schief gehen.
Allerdings gibt es bei "char" eine weitere Besonderheit: Man darf jeden
beliebigen Zeiger-Typ nach "char*" konvertieren und dann
dereferenzieren. Das daraus erhaltene Ergebnis ist aber wieder
undefiniert. Wenn du also z.B. die Zahl 128 in einen "unsigned char"
schreibst, darauf einen Zeiger nimmst, diesen nach char* konvertierst
und dann dereferenzierst, ist das Ergebnis Prozessor-abhängig.
Dieser Cast ist also erlaubt, aber auch nicht besonders portabel. Zum
Glück wird er aber gar nicht gebraucht - ändere das Funktionsargument
einfach auf "uint8_t*".
Mirco G. schrieb:> Wenn ich einfach einen uint8_t Array an pec15 übergebe obwohl ich *data> weiterhin als char deklariert habe bekomme ich "nur" eine Warnung> pointer targets in passing argument 1 of 'pec15' differ in signedness
Der Parameter in der Funktion ist vom Typ char*. Das ist eine
vorzeichenbehaftet 8 Bit Wert.
uint8_t Array ist ein Array mit vorzeichenlose 8 Bit Werte. Das passt
einfach nicht zusammen.
Man kann bei der Deklaration der Funktion auch untypisierte Zeiger
verwenden. Wie z.B. bei memcpy:
http://www.cplusplus.com/reference/cstring/memcpy/
Typisierte Zeiger sind z.B. wichtig damit die Zeigerarithmetik
funktioniert.
p++ bei einem char Zeiger erhöht den Zeigerwert um 1
p++ bei einem long Zeiger hingegen um 4 !!!!!
Ein untypisierte Zeiger verhält sich wie ein char Zeiger. Man ist dann
selbst für die richtige Zeigerarithmetik verantwortlich.
GEKU schrieb:> p++ bei einem long Zeiger hingegen um 4 !!!!!
Das ist plattformabhängig. Kann sogar 1 sein, wenn z.B.
char,short,int,long alle 32bit groß sind...
GEKU schrieb:> Das ist eine vorzeichenbehaftet 8 Bit Wert.
Nein. Das ist ein 8-Bit-Wert, dessen Vorzeichen unbestimmt ist
(implementation-defined). Aus diesem Grunde muss portabler Code davon
ausgehen, dass char, signed char und unsigned char drei voneinander zu
unterscheidende Datentypen sind, die nicht (ohne Cast) miteinander
vermischt werden sollen.
GEKU schrieb:> Ein untypisierte Zeiger verhält sich wie ein char Zeiger
Was soll ein untypisierter Zeiger sein? void *?
Mit dem ist gar keine (portable) Zeigerarithmetik zulässig. GCC
behandelt ihn wie char *, aber das ist nicht portabel.
Niklas G. schrieb:> Das ist plattformabhängig. Kann sogar 1 sein, wenn z.B.> char,short,int,long alle 32bit groß sind..
Größe von char 1 Byte = 8 Bit
Größe von short 2 Byte = 16 Bit
Größe von int 2 oder 4 Byte = 16 Bit oder 32 Bit *)
Größe von long 4 Byte = 32 Bit
Größe von float 4 Byte = 32 Bit
Größe von double 8 Byte = 64 Bit
Größe von long double 10 Byte = 80 Bit
http://pronix.linuxdelta.de/C/standard_C/c_programmierung_8_2.shtml
Größe von long long 8 Byte = 64 Bit
Es gibt zwar Zeichen die breiter als 8 Bit sind, aber das ist ein andere
Sache.
https://docs.microsoft.com/de-de/cpp/c-language/multibyte-and-wide-characters?view=vs-2019
Selbst in diesem Fall hat der Char eine andere Bezeichnung wchar_t
*) Soweit mir bekannt ist, ist nur der Integer maschinenabhängig
Ich lerne gerne dazu. Gibt es einen Quellennachweis für die Aussage, das
char maschinenabhängig auch größer sein kann.
GEKU schrieb:> Größe von char 1 Byte = 8 Bit> Größe von short 2 Byte = 16 Bit> Größe von int 2 oder 4 Byte = 16 Bit oder 32 Bit *)> Größe von long 4 Byte = 32 Bit> Größe von float 4 Byte = 32 Bit> Größe von double 8 Byte = 64 Bit
Das ist alles plattformabhängig.
GEKU schrieb:> Größe von long double 10 Byte = 80 Bit
long double ist nonstandard.
GEKU schrieb:> Es gibt zwar Zeichen die breiter als 8 Bit sind, aber das ist ein andere> Sache.
Jein. Es gibt Plattformen mit char=36 bit!
GEKU schrieb:> *) Soweit mir bekannt ist, ist nur der Integer maschinenabhängig
Nein, alle diese Typen sind plattformabhängig.
GEKU schrieb:> Ich lerne gerne dazu. Gibt es einen Quellennachweis für die Aussage, das> char maschinenabhängig auch größer sein kann.
Der C-Standard, oder zusammengefasst:
https://stackoverflow.com/a/881968/4730685
GEKU schrieb:> Größe von char 1 Byte = 8 Bit
Nein, das ist so nicht festgeschrieben. Das ist lediglich die
Mindestgröße, und es ist natürlich tatsächlich auf den allermeisten
Architekturen der Fall.
Es gibt aber Architekturen (etwas exotischere DSPs), die können gar
keine 8-bit-Werte verarbeiten. Bei denen sind dann char, short, int und
long alle bspw. 32 bit breit. Auch darauf kann man eine standardkomforme
C-Implementierung unterbringen, da der Standard eben genau diese Details
offen lässt. Auf solchen Architekturen kann es dann übrigens kein
uint8_t geben.
Was allerdings tatsächlich im Standard festgeschrieben ist: sizeof(char)
== 1. Das heißt aber eben nicht automatisch, dass das ein "Byte"
(genauer: Octet) sein muss.
Die Anzahl der Bits pro Char erfährt man über den Makro CHAR_BITS aus
<limits.h>.
Jörg W. schrieb:> Was allerdings tatsächlich im Standard festgeschrieben ist: sizeof(char)> == 1.
Das hieße doch, dass es keine Plattform gibt, bei der sizeof (char) != 1
ist oder?
Mag schon sein, dass bei der Deklaration char x zwei oder mehr Bytes
angepatzt werden.
GEKU schrieb:> Das hieße doch, dass es keine Plattform gibt, bei der sizeof (char) != 1> ist oder?
Richtig. sizeof liefert die Größe in Vielfachen von char. char ist genau
1 char groß, daher sizeof(char)=1. char ist aber eben nicht unbedingt 8
bit groß. Die Anzahl an Bits in einem Datentyp bestimmt man mit sizeof
(X)*CHAR_BIT.
GEKU schrieb:> Jörg W. schrieb:>> Was allerdings tatsächlich im Standard festgeschrieben ist: sizeof(char)>> == 1.>> Das hieße doch, dass es keine Plattform gibt, bei der sizeof (char) != 1> ist oder?
So ist es.
C-Standard, 6.5.3.4 The sizeof and _Alignof operators, Absatz 4:
1
When sizeof is applied to an operand that has type char, unsigned char, or
2
signed char, (or a qualified version thereof) the result is 1.
(Vorsicht übrigens: der C-Standard spricht dort tatsächlich von "Bytes".
Um die 8 Bits klar und deutlich zu unterscheiden, sprechen übliche
Standards der Telekommunikation daher nicht von "Bytes", sondern
"Octets".)
> Mag schon sein, dass bei der Deklaration char x zwei oder mehr Bytes> angepatzt werden.
???
Wer soll hier was "anpatzen"?
Niklas G. schrieb:> uint8_t ist entweder:> - Ein Alias auf "char", falls "char" vorzeichenlos ist> - Ein Alias auf "unsigned char"> - Nichtexistent, z.B. wenn CHAR_BIT != 8
- ein Extended Integer Type
(kenne aber keine solche Implementation)
Zur Frage: Was ist ein Byte?
Möchte ich den Wikipedia Artikel empfehlen.
Der verschafft schon mal einen groben Überblick.
https://de.wikipedia.org/wiki/Byte
Jörg W. schrieb:> Wer soll hier was "anpatzen"?
Unter "anpatzen" verstehe ich, dass von z.B. 16 Bits nur 8 Bit
Informationen enthalten.
Jetzt kann man sich es leisten, aber zu Apollo 11 Zeiten wäre es
Verschwendung gewesen.
Auf der anderen Seite bei "Boolschen Variablen" , die nur 1 Bit belegen
war man immer schon großzügig.
GEKU schrieb:> Unter "anpatzen" verstehe ich, dass von z.B. 16 Bits nur 8 Bit> Informationen enthalten.
Also Padding Bytes? Die sind aber nicht Teil der Variable. Verschwendung
sind die auch nicht.
Niklas Gürtler schrieb:> GEKU schrieb:>> Unter "anpatzen" verstehe ich, dass von z.B. 16 Bits nur 8 Bit>> Informationen enthalten.>> Also Padding Bytes? Die sind aber nicht Teil der Variable. Verschwendung> sind die auch nicht.
Außerdem gibt es diese auch nur dort, wo sie notwendig sind.
Normalerweise ordnen Compiler und Linker Variablen so im Speicher an,
dass möglichst wenig Padding notwendig ist.
Hat aber mit char und 8 Bits nichts zu tun. Wenn eine Architektur
CHAR_BIT > 8 definiert, dann tut sie dies, weil sie eben gar keine
8-Bit-Einheit adressieren kann. Da wird dann nichts „angepatzt“, es
geht dort einfach nicht anders. Andererseits wird auch eine
8-Bit-Einheit mit einem 7-Bit-ASCII-Zeichen ja nur zur Hälfte
ausgenutzt, während andere Codierungen als ASCII deutlich mehr Bits
benötigen können, um ein einzelnes darstellbares Zeichen zu beherbergen.
Dass "char" ein beliebiges druckbares Zeichen darstellt, ist folglich so
universell nicht haltbar.
Niklas G. schrieb:> Nein, alle der Integer-Typen (char, short, int, long, long long) haben> keine fixe Größe, nur eine Mindest-Größe. Die (u)intXX_t Typen sind,> falls sie existieren, Aliase.
Ok :).
Niklas G. schrieb:> Ja, im ARM Architecture Reference Manual. Allerdings können die ARM> (STM32 ist ja einer) sogar beide Möglichkeiten. Was der Compiler jetzt> verwendet, musst du im Compiler-Manual nachschlagen. Besser ist es aber,> sich da gar nicht erst drauf zu verlassen, und solche Konstrukte zu> vermeiden. Dann kann man den Code auch problemlos später auf andere> Plattformen übertragen, ohne ihn nach solchen Fallen durchsuchen zu> müssen.
Das vermeinde ich ja z.B. mit dem Alias "uint8_t" oder?
Niklas G. schrieb:> uint16_t pec15 (const uint8_t *data , size_t len)> {> uint16_t remainder = 16;> uint16_t address;>> for (size_t i = 0; i < len; ++i) {> address = ((remainder >> 7) ^ data[i]) & 0xff;> remainder = (remainder << 8 ) ^ pec15Table[address];> }> return (remainder*2);> }>> void SPI_Befehl_Senden(uint16_t Befehl)> {> uint8_t SpiBufTx[4];>> SpiBufTx[0]=SHORT_H_BYTE(Befehl); //Zerlegen in> High und Low Byte> SpiBufTx[1]=SHORT_L_BYTE(Befehl);>> uint16_t crc = pec15(SpiBufTx, 2); // Nicht zweimal berechnen!> SpiBufTx[2]=SHORT_H_BYTE(crc);> SpiBufTx[3]=SHORT_L_BYTE(crc);>> HAL_SPI_Transmit(&hspi1, SpiBufTx, 4, 1);> //Warten bis gesndet wurde> }> So ist alles Unsigned, du brauchst keine Casts, und da sollten jetzt> keine Plattform-Abhängigen Dinge mehr drin sein (bzw keine, die sich> kompilieren lassen aber heimlich etwas falsches tun).
Super Danke!
Niklas G. schrieb:> Ja. "Nachricht" ist vom Typ "uint8_t[2]". Das ist wahrscheinlich ein> Alias für "unsigned char[2]". C kann aber Arrays nicht konsistent> weiterverwenden oder übergeben. Daher wird, Wenn du "Nachricht"> verwendest, daraus automatisch ein "unsigned char*", ein Zeiger auf das> 0. Element des Arrays. Durch Addition des Zeigerwerts kann man auch auf> die weiteren Elemente zugreifen.> Allerdings möchte "pec15" bei dir ein "char*" haben, also einen Zeiger> auf einen oder mehrere char's.> "char" hat die Besonderheit, dass "unsigned char", "signed char" und> "char" drei einzelne Typen sind, aber char entweder ein Vorzeichen hat> oder nicht (plattformabhängig). Es ist aber dennoch ein eigener Typ, der> sich lediglich gleich verhält zu einem der anderen beiden.> Auch wenn in C alle Zeiger (außer Funktionszeiger) letztlich gleich> funktionieren (es sind immer Adressen auf Speicherzellen), sind sie> nicht direkt austauschbar (außer zu void*) - wenn du versuchst einen> Zeiger auf Typ A wie einen Zeiger auf Typ B zu behandeln, gibt es> Compiler-Fehler/Warnungen. Dies soll vor Fehlern schützen, indem> verhindert wird, dass Daten anders behandelt werden als gedacht. Du> kannst dem Compiler aber sagen, dass du weißt dass das Ziel eines> Zeigers einen bestimmten Typ hat, und der Compiler diesen Typen annehmen> soll. So kannst du einen Zeiger auf A (also A*) auf einen Zeiger nach B> (also B*) konvertieren. Du hast hier also einen Zeiger vom Typ "unsigned> char*" nach "char*" umgewandelt, dem Compiler also gesagt "ich weiß dass> der Zeiger auf Nachricht eigentlich ein Zeiger auf char ist, nimm das so> hin". Das ist aber nicht ganz korrekt, denn Nachricht ist ja tatsächlich> "unsigned char[2]" - der Compiler wurde also angelogen!> Normalerweise ist so etwas gefährlich - z.B. einen "int*" nach "float*"> zu konvertieren und dann zu dereferenzieren kann beliebig schief gehen.> Allerdings gibt es bei "char" eine weitere Besonderheit: Man darf jeden> beliebigen Zeiger-Typ nach "char*" konvertieren und dann> dereferenzieren. Das daraus erhaltene Ergebnis ist aber wieder> undefiniert. Wenn du also z.B. die Zahl 128 in einen "unsigned char"> schreibst, darauf einen Zeiger nimmst, diesen nach char* konvertierst> und dann dereferenzierst, ist das Ergebnis Prozessor-abhängig.> Dieser Cast ist also erlaubt, aber auch nicht besonders portabel. Zum> Glück wird er aber gar nicht gebraucht - ändere das Funktionsargument> einfach auf "uint8_t*".
Super. Vielen vielen Dank für die ausführliche Erklärung!
Für mich zusammengefasst:
- Standard-Typen wie char, int usw. sind plattenformabhängig. Deswegen
immer "uint8_t" usw. verwenden dann bekommt man dort keine Probleme.
- Bei Zeigern immer darauf achten das man gleiche Typen verwendet.
- Wenn man mal casten muss immer testen ob man zwischen den gewünschten
Typen überhaupt casten kann.
richtig ? :D
Vielen Dank!
Mirco G. schrieb:> Das vermeinde ich ja z.B. mit dem Alias "uint8_t" oder?
Bei unsigned Typen trifft dieses Problem sowieso nicht zu. Einen int8_t
rechts zu shifen ist hingegen auch plattformabhängig.
Mirco G. schrieb:> - Standard-Typen wie char, int usw. sind plattenformabhängig. Deswegen> immer "uint8_t" usw. verwenden dann bekommt man dort keine Probleme.
Na Probleme gibt's da auch, aber eines weniger. "char" braucht man wenn
man Speicher direkt manipulieren möchte, wie bei memcpy().
Mirco G. schrieb:> - Wenn man mal casten muss immer testen ob man zwischen den gewünschten> Typen überhaupt casten kann.
Das sowieso immer. Durch Zeiger-Casts kann man viel falsch machen, aber
manchmal ist es unumgänglich.
Mirco G. schrieb:> Deswegen immer "uint8_t" usw.
Aber auch nur, wenn du wirklich genau 8 Bit brauchst.
Wenn du mindestens 8 Bit brauchst, aber dir die genaue Größe egal ist,
kommst du besser, wenn du uint_fast8_t nimmst. Das wäre auf einem ARM
dann ein 32-bit-Typ, denn damit ist dieser Prozessor schneller als mit
dem Handling eines 8-bit-Typs.
Mirco G. schrieb:> - Wenn man mal casten muss immer testen ob man zwischen den gewünschten> Typen überhaupt casten kann.
Da der Compiler zwischen den genannten eingebauten Datentypen (char,
int, unsigned, ...) bei gemischter Verwendung in einem Ausdruck
kommentarlos selber casted, solltest du dir auch die Regeln dazu mal
anschauen. Sichwort "Implicit conversions" oder "Implizite
Typkonvertierung"
Oliver
Darf ich noch einwenden, dass C gar kein Call-by-Reference kennt und man
deswegen einen Referenz (Pointer/Zeiger) als Call-by-Value Parameter
übergeben muss? ;-)
(duck und wech)
Mirco G. schrieb:> - Standard-Typen wie char, int usw. sind plattenformabhängig. Deswegen> immer "uint8_t" usw. verwenden dann bekommt man dort keine Probleme.
Nein.
Neben den schon genannten fast Typen gibt es auch noch least: z.B.
uint_least8_t
Das liefert die 8 Bit, kann aber auch mehr haben.
Das ist meist sinnvoller (und portabler) als die exakte Vorgabe.
Dirk B. schrieb:> Das liefert die 8 Bit, kann aber auch mehr haben.
Sind speicherplatzoptimiert, d.h. der Zugriff kann dann wieder länger
dauern.
Auf üblichen Architekturen werden diese den Typen ohne "least" gleichen:
es soll ja möglichst speichersparend gearbeitet werden.
Mirco G. schrieb:> Standard-Typen wie char, int usw. sind plattenformabhängig. Deswegen> immer "uint8_t" usw. verwenden dann bekommt man dort keine Probleme.
Ist statt unsigned char sinnlos: wenn das mehr als 8 Bit hat, gibt es
uint8 nicht, also auch Fehler.
Der große Vorteil von uint8 ist, dass es kein char ist.
Stringzuweisungen können dann angemeckert werden. Und es passt
namensmäßig zu 16_t, 32_t, ...
Jörg W. schrieb:> Auf üblichen Architekturen werden diese den Typen ohne "least" gleichen:> es soll ja möglichst speichersparend gearbeitet werden.
Auf unüblichen Architekturen (dort wo CHAR_BIT > 8 ist) gibt es aber
auch uint_least8_t.
Der kann dann auch 16 oder 24 oder 9 (oder was anderes >=8) Bit haben.
Das uint8_t macht die Sache weniger portabel als die meisten denken.
Es fällt aber meist nicht auf, weil CHAR_BIT meist 8 ist.
A. S. schrieb:> Ist statt unsigned char sinnlos: wenn das mehr als 8 Bit hat, gibt es> uint8 nicht, also auch Fehler.Dirk B. schrieb:> Das uint8_t macht die Sache weniger portabel als die meisten denken.
Die Intention ist, dass man Compiler-Fehler bekommt, wenn es uint8_t
nicht gibt und man die zu korrigierenden Stellen praktisch serviert
bekommt. Das ist immer noch viel besser als Code der zwar kompiliert
aber dann was anderes macht als gewünscht. Sollte man tatsächlich einen
8-Bit-Integer mit entsprechendem Overflow brauchen, bringt uint_least8_t
auch nix. Daher verwendet man hier uint8_t und hat die Chance das ggf.
anzupassen.
Niklas G. schrieb:> Die Intention ist, dass man Compiler-Fehler bekommt, wenn es uint8_t> nicht gibt und man die zu korrigierenden Stellen praktisch serviert> bekommt.
So ist es. Neben dem genannten integer overflow betrifft das natürlich
auch Dinge, bei denen eben exakt 8 Bits irgendwo gespeichert werden
müssen, weil es bspw. in der Kommunikation mit anderen Geräten
erforderlich ist. (Natürlich hat man dann immer noch endianess-Probleme
zu beachten.)
Wenn es nicht exakt 8 Bits sein müssen, kommt man wiederum in aller
Regel mit den "fast"-Typen besser weg, daher hatte ich die
"least"-Variante oben nicht weiter erwähnen wollen. Der TE wird ohnehin
schon hinreichend verwirrt sein. :)
Mirco G. schrieb:> Ich verstehe nicht warum ich den Array zwingend als char übergeben muss.
Ein Array übergibt man als Pointer auf den Arraytyp. Also geht char
schonmal gar nicht, sondern höchstens (char *).
Die Übergabe als Pointer macht C sehr effizient. Es gibt andere
Programmiersprachen, die erst umständlich eine Kopie des gesamten Arrays
anlegen. Das kostet Zeit und Speicherplatz.
Char ist historisch begründet für String-Arrays reserviert, was keine
glückliche Festlegung war. Jegliche Arten von Operationen sind damit
geeignet, sich ins Knie zu schießen, selbst simple Vergleiche zur
Konvertierung von Umlauten. Da muß man dann immer nach unsigned casten
bzw. nach uint8_t.
Niklas G. schrieb:> Die Intention ist, dass man Compiler-Fehler bekommt, wenn es uint8_t> nicht gibt und man die zu korrigierenden Stellen praktisch serviert> bekommt.
Ja, kann man so sehen. Auf der anderen Seite dürfte es sehr selten sein,
dass dies relevant wird.
- wenige Systeme mit neuen Compilern (die uint8_t vorhalten würden) UND
mehr als 8 Bit pro char UND portabler Code, der dahin portiert werden
soll
- ungeeignet als BYTE-Puffer für Zahlen von 0..255 (dafür muss man dann
wieder (unsigned) char nehmen, weil dieser Wertebereich IMMER passt)
- für Compiler-Fehler kann man auch CHAR_BIT nutzen.
Von daher: Es passt in die Benamung, es eignet sich für BYTE-Puffer,
aber
Dirk B. schrieb:> Das uint8_t macht die Sache weniger portabel als die meisten denken.> Es fällt aber meist nicht auf, weil CHAR_BIT meist 8 ist.