Forum: Mikrocontroller und Digitale Elektronik Probleme mit Call by Reference


von Mirco G. (mirco432)


Lesenswert?

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
const uint16_t pec15Table[256]=
2
    {
3
  0x0000,
4
  0xc599,
5
        .....
6
  0x4e3e,
7
  0x450c,
8
  0x8095
9
    };
10
11
uint16_t pec15 (char *data , int len)
12
{
13
  int16_t remainder;
14
  int16_t address;                                          
15
16
  remainder = 16;
17
  for (int i = 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
 void SPI_Befehl_Senden(uint16_t Befehl)
26
{
27
    char   Nachricht[2];
28
    uint8_t   SpiBufTx[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

von Walter K. (walter_k488)


Lesenswert?

char   Nachricht[2];
    ...
    Nachricht[0]=SHORT_H_BYTE(Befehl);
    Nachricht[1]=SHORT_L_BYTE(Befehl);

Wo steht im char-Array '\0' ?

von Einer K. (Gast)


Lesenswert?

Mirco G. schrieb:
> Gibt es überhaupt einen unterschied zwischen uint8_t und unsigned char?

unsigned char kann durchaus 36 Bit breit sein

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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... :)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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 *".

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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 …

von Mirco G. (mirco432)


Lesenswert?

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
 void SPI_Befehl_Senden(uint16_t Befehl)
2
{
3
    uint8_t   Nachricht[2];
4
    uint8_t   SpiBufTx[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
12
    SpiBufTx[3]=SHORT_L_BYTE(pec15((char *)Nachricht,2));
13
14
    HAL_SPI_Transmit(&hspi1, SpiBufTx, 4, 1);                    //Warten bis gesndet wurde
15
}

Ich finde aber keine gescheite Erklärung warum ich das * benötige

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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_t pec15 (const uint8_t *data , size_t len)
2
{
3
  uint16_t remainder = 16;
4
  uint16_t address;
5
6
  for (size_t i = 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
 void SPI_Befehl_Senden(uint16_t Befehl)
14
{
15
    uint8_t   SpiBufTx[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_t crc = 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).

: Bearbeitet durch User
von Mirco G. (mirco432)


Lesenswert?

https://www.luis.uni-hannover.de/fileadmin/kurse/material/CKurs/c6_Zeiger.pdf

hier wird es auf Folie 15 einfach als cast für Zeiger bezeichnet.

von Oliver S. (oliverso)


Lesenswert?

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

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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*".

von GEKU (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von GEKU (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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>.

: Bearbeitet durch Moderator
von GEKU (Gast)


Lesenswert?

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.

von Niklas Gürtler (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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"?

von Jemand (Gast)


Lesenswert?

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)

von Einer K. (Gast)


Lesenswert?

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

von GEKU (Gast)


Lesenswert?

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.

von Niklas Gürtler (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Mirco G. (mirco432)


Lesenswert?

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!

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Oliver S. (oliverso)


Lesenswert?

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

von Eric B. (beric)


Lesenswert?

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)

von Dirk B. (dirkb2)


Lesenswert?

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.

Beitrag #5918184 wurde vom Autor gelöscht.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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, ...

von Dirk B. (dirkb2)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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. :)

von Peter D. (peda)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

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.