Forum: Compiler & IDEs stdint.h, string.h Datentyp "Char"?


von Le_Q (Gast)


Lesenswert?

Hallo zusammen,

sorry für die Themenbeschreibung; weiß aber nicht wie ich es sonst 
nennen soll.

Ich verwende den GCC und verwende stehts die C99 Standardlibs. So auch 
stdint.h die Definitionen zu den Datentypen enthält. Es wird unsigned 
char als uint8_t und signed char als int8_t definiert, soweit so gut.
Was ist nun aber mit dem Datentyp char? Denn ein char ist ja bekanntlich 
kein signed und kein unsigned char. Bei Verwendung der beiden Datentypen 
sollte auch mit Typecasts gearbeitet werden.

Das Problem entsteht dadurch, dass ich die string.h (auch C99 oder?) 
verwende. Hier möchte ich die Funktion strnlen() verwenden. Diese ist 
definiert durch "size_t  (strnlen,(const char *, size_t));". So jetzt 
arbeite ich aber nur mit den Typen aus stdint.h, und verwende folglich 
kein "char" sondern nur uint8_t oder int8_t. Ich kann zwar Parameter mit 
diesen Typen an strnlen() übergeben, allerdings führt das zu einer 
Compiler-Warning. Funktionieren tut es natürlich, aber ich finde es 
nicht schön.

Es muss hier doch eine einheitliche Lösung geben.
Und wieso definiert die stdint.h eigentlich "char" nicht?

Kurz am Rande ich verwende die libc der aktuellsten arm code-sourcery 
toolchain.

Falls sich her jemand auskennt, darf ich um Hilfe und Anmerkungen 
bitten. Vielleicht verstehe ich es ja auch falsch.

Danke und Grüße,
Le_Q

von HoRRst (Gast)


Lesenswert?

Genau dasselbe habe ich auch gerade.

Ich übergebe mehrere uint8_t an eine Funktion, die mir einen String 
ausgiebt.
Der CCS warnt mich, dass *char nicht kompatibel mit uint8_t ist.

Also habe ich in der Funktion aus "send_string (uint8_t *string)"
ein "send_string (unsigned char *string)" gemacht, aber auch hier wird 
gemeckert...mit einem normalen char ist es OK.

Funktionieren tun alle drei Varianten.

IAR Embedded beschwert sich auch nicht, der Code Composer schon...

von Mark B. (markbrandis)


Lesenswert?

Wenn man char will, nimmt man char (insbesondere char[] bzw. char* für 
Zeichenketten).

Wenn man einen 8-Bit Wert ohne Vorzeichen haben will, nimmt man uint8_t.

Wenn man einen 8-Bit Wert mit Vorzeichen haben will, nimmt man int8_t.

von Hc Z. (mizch)


Lesenswert?

Die in stdint.h definierten Typen sind nicht dazu gedacht, die Basic 
Integer Types zu ersetzen, sondern sie zu ergänzen.  Wenn Du einen 
String oder ein Character haben willst, nimmst Du weiterhin char und 
nicht [u]int8_t.

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


Lesenswert?

Typischerweise hast du jeweils einen genau definierten Übergang
zwischen den Typen.  Beispielsweise, wenn du Zeichen von einer UART
einliest, die dann darstellbarer Text (also char im Sinne von
<string.h>) werden sollen, dann hat das UART-Datenregister den Typ
uint8_t, danach werden die von dort gelesenen Daten als char weiter
interpretiert.  Wenn man das sauber machen will, schreibt man einen
Typecast:
1
   *uartbufptr++ = (char)UDR;

Bei der Ausgabe erfolgt das dann in umgekehrter Richtung.

"char" steht in keiner Headerdatei, da es ein impliziter Datentyp
der Programmiersprache C ist.

von Le_Q (Gast)


Lesenswert?

Okay, dass klingt einleuchtend. Danke für die Eingebung! :)

Aber:
Dass ein "char" 8 Bit breit ist, ist mir klar. Aber ist garantiert das 
dies auf jedem System so ist? Alle definitionen die ich finde, sagen 
dass ein char min. 8 Bit sein muss und ein Bekannter meinte das er es 
schonmal mit 16Bit unicode chars zu tun hatte. Wobei mir persönlich 
diese nur aus Java und nicht aus C bekannt sind.
Bei dem int8 kann ich auf jeden Fall von 8 Bit ausgehen...
Ich habe Speicherbereiche in denen "irgendwelche Werte" oder aber auch 
ein String liegen kann. Wird durch eine Struct verwaltet, die enthält 
auch Infos drüber ob es sich um einen String oder einen anderen Wert 
handelt (Typ: string/zahlenwert, Lenge: in Byte, Adresse: null-pointer). 
Wenn ich über diesen Speicherbereich ein Char-Array lege, muss sicher 
sein das ein Feld wirklich 8 Bit breit ist, unabhängig vom Target für 
das der Code compiliert wurde.

von Karl H. (kbuchegg)


Lesenswert?

Le_Q schrieb:

> Dass ein "char" 8 Bit breit ist, ist mir klar. Aber ist garantiert das
> dies auf jedem System so ist?

Nein

Das macht aber nichts.
Ein char ist so gross, dass ein Zeichen, so wie es bei Textverarbeitung 
benötigt wird, hineinpasst.

> Ich habe Speicherbereiche in denen "irgendwelche Werte" oder aber auch
> ein String liegen kann.

> Wird durch eine Struct verwaltet

Die Idee ist nicht schlecht. Nur möchtest du dafür eine union nehmen. 
Genau das ist nämlich ihr Einsatzfall.

> Wenn ich über diesen Speicherbereich ein Char-Array lege, muss sicher
> sein das ein Feld wirklich 8 Bit breit ist, unabhängig vom Target für
> das der Code compiliert wurde.

Und mit einer Union ist dann auch dieses "Problem" für dich nicht mehr 
existent.

von Le_Q (Gast)


Lesenswert?

Ja ich weiß was du meinst, habe auch schon viel mit Unions gemacht,
mein Problem ist aber ein anderes. Denn der void-Pointer kann auf einen 
Bereich zeigen an dem 1GB hinterlegt ist oder auch nur 1 Byte.

typedef struct{
uint32_t fieldId;
uint32_t fieldSize;
FIELDTYPE fieldtype;
FUNCTION_PTR fieldFunction;
void * fieldAddress;
}myStruct;

Wobei FIELDTYPE ein Enum ist für den Typ. Und FUNCTION_PTR ein 
Funktionspointer auf irgendeine Funktion.


So kann ich mir z.B. eine Liste erzeugen mit unterschiedlichen 
Einträgen. Z.B.

fieldID = 1;
fieldSize = 10;
fieldtype = STR;
fieldFunction = FPTR_writeField;
fieldAddress = (void *) 0x20000000;

Ich kann z.B. in dem Funktionspointer eine Funktion hinterlegen die mir 
jetzt einen String der Länge 10 an die 0x20000000 schreiben kann, oder 
zum lesen, oder kein String sonder 1GB Daten oder oder oder...

Ist ne coole Sache, Funktioniert sogar. ;) Aber ich Adresse die Daten im 
späteren Code einfach als uint8_t array. Weil ich so einen recht 
einfachen Byte-weise Zugriff auf die Daten realisieren kann.
Also wenn ich euch alle richtig verstehe ist es das Eleganteste vor der 
Übergabe des uint8_t arrays bzw. pointers an strnlen(), diesen in char 
zu casten.

Das geht gut solange char als 8 bit definiert ist, sobald ein anderes 
Target mit z.B. 16Bit Chars arbeitet (wenn es das überhaupt gibt? in der 
C Welt?) funktioniert der Code nicht mehr und ist somit nicht mehr 
portabel. :-(

von Le_Q (Gast)


Lesenswert?

Sry ich muss mir mal einen Account machen aufm Forum, sonst kann ich 
nicht editieren:

Nachtrag:
Da die Struct selbst statisch ist, bringt eine Union hier nichts. Würden 
die Strings / Daten direkt in der Struct stehen würde eine Union auf 
jeden Fall Sinn machen!

von Oliver (Gast)


Lesenswert?

Le_Q schrieb:
> Weil ich so einen recht
> einfachen Byte-weise Zugriff auf die Daten realisieren kann.

Nun ja, wenn du VOLLE Kompabilität mit allen real existierenden Systeme 
willst, geht das schon an dieser Stelle in die Hose. Nicht jede 
Architektur lässt Byte-weise Zugriffe auf Daten zu.

Wenn du wirklich portabel programmieren willst, musst du über sizeof() 
die tatsächliche Größe von char ermitteln, und in einen Code entsprechen 
darauf reagieren.

Oder du schränkst die Portabilität auf Systeme ein, bei denen char ein 
Byte groß ist. Viel verlierst du damit nicht.

Oliver

von Karl H. (kbuchegg)


Lesenswert?

Oliver schrieb:

> Wenn du wirklich portabel programmieren willst, musst du über sizeof()
> die tatsächliche Größe von char ermitteln, und in einen Code entsprechen
> darauf reagieren.

Das geht auf jeden Fall schief.
Den sizeof(char) ist definitionsgemäs eine glatte 1

> Oder du schränkst die Portabilität auf Systeme ein, bei denen char ein
> Byte groß ist. Viel verlierst du damit nicht.

Exakt.

von Karl H. (kbuchegg)


Lesenswert?

Le_Q schrieb:

> Ist ne coole Sache, Funktioniert sogar. ;) Aber ich Adresse die Daten im
> späteren Code einfach als uint8_t array. Weil ich so einen recht
> einfachen Byte-weise Zugriff auf die Daten realisieren kann.
> Also wenn ich euch alle richtig verstehe ist es das Eleganteste vor der
> Übergabe des uint8_t arrays bzw. pointers an strnlen(), diesen in char
> zu casten.

Das eleganteste ist es, den void Pointer so schnell wie möglich wieder 
in einen Pointer auf den richtigen Datentyp umzucasten und dann nur noch 
mit dem umgecastetn Pointer zu arbeiten.

Noch eleganter ist nur noch: den void Pointer vermeiden.

von Rolf Magnus (Gast)


Lesenswert?

Le_Q schrieb:

> Was ist nun aber mit dem Datentyp char?

Nichts.

> Das Problem entsteht dadurch, dass ich die string.h (auch C99 oder?)
> verwende. Hier möchte ich die Funktion strnlen() verwenden. Diese ist
> definiert durch "size_t  (strnlen,(const char *, size_t));". So jetzt
> arbeite ich aber nur mit den Typen aus stdint.h, und verwende folglich
> kein "char" sondern nur uint8_t oder int8_t.

Warum? Woraus bestehen deine Strings? Aus Zeichen ("character") oder aus 
Integern, die exakt 8 Bit breit sein müssen. (u)int8_t ist letzteres, 
char ist ersteres.

> Es muss hier doch eine einheitliche Lösung geben.
> Und wieso definiert die stdint.h eigentlich "char" nicht?

Wozu? char ist doch schon definiert.

Außer für direkten Zugriff auf I/O-Register oder andere Stellen, wo die 
exakte Bit-Größe absolut notwendig ist, sollte man übrigens nie 
(u)int*_t verwenden.

Oliver schrieb:
> Nun ja, wenn du VOLLE Kompabilität mit allen real existierenden Systeme
> willst, geht das schon an dieser Stelle in die Hose. Nicht jede
> Architektur lässt Byte-weise Zugriffe auf Daten zu.

Jeder konforme C-Compiler unterstützt ganz automatisch byteweise 
Zugriffe.

> Wenn du wirklich portabel programmieren willst, musst du über sizeof()
> die tatsächliche Größe von char ermitteln,

sizeof(char) ist immer 1.

> Oder du schränkst die Portabilität auf Systeme ein, bei denen char ein
> Byte groß ist. Viel verlierst du damit nicht.

Das stimmt, denn so ist in C das Byte definiert - als genau die 
Datenmenge, die man in einem char speichern kann.

Wenn er allerdings uint8_t statt char verwendet, dann schränkt er sich 
auf Systeme ein, wo char exakt 8 Bit breit ist, weil nur auf denen ein 
uint8_t existiert.

von Le_Q (Gast)


Lesenswert?

Nun gut, wollte jetzt keine Diskussion los treten. Aber ich sehe schon 
"absolute kompatibilität" lässt sich nicht immer mit "höchster Eleganz" 
vereinen. :|

Und ich glaube was Oliver sagen wollte ist, dass nicht jeder 
Speicherbereich Byteweise Les- und Schreibbar sein muss. Das gilt es 
sowieso zu berücksichtigen.

@Rolf Magnus:
Ja du hast schon recht uint8_t * sollte man in erster Linie für I/O 
Register etc. verwenden. Da meine Structur aber auch I/O Register 
adressiert muss ich es so machen. Die Idee ist es eine Lookup Table zu 
haben die dann R/W Zugriff auf Speicherbereiche (I/O Register, 
Selbstdefinierte Bereiche...) ermöglicht. Wen dies an CANOpen SDO-Server 
erinnert hat recht, das Prinzip stammt im Grunde daher.

--------------------------------

Danke nochmal für alle Anmerkungen, ich halte mal fest:

Void Pointer am besten vermeiden, und wenn dann diesen vor 
Weiterverarbeitung schnell wieder zurück in den "wirklichen" Datentyp 
wandeln. Wenn es sich um einen String handelt CHAR verwenden! Char ist 
nicht von der stdint.h abgedeckt, denn diese ist dazu gedacht die 
bestehenden basic integer Datentypen zu ergänzen. Die Verwendung von 
char macht C Code ggf. unportabel, da die Größe von char nicht fest 
definiert ist.

Im ISO/IEC 9899:1999 Seite 33 (C99) steht hierzu:

"The three types char, signed char, and unsigned char are collectively 
called
the character types. The implementation shall define char to have the 
same range,
representation, and behavior as either signed char or unsigned char.
35)"

Und zur definition der Größe eines Chars findet man weiter:

"An object declared as type char is large enough to store any member of 
the basic
execution character set. If a member of the basic execution character 
set is stored in a
char object, its value is guaranteed to be positive. If any other 
character is stored in a
char object, the resulting value is implementation-defined but shall be 
within the range
of values that can be represented in that type."

Weiter ist in den Fußnoten zu finden dass die Größe aller Datentypen, 
sich aus limits.h ableitet.

---------------------------

Ich muss dazu sagen, dass diese Probleme sich durch Low-Level / Treiber 
Programmierung ergeben, auf Applikationsebene glaub ich nicht das es 
diese Konflikte gibt.


Viel Text... ich bin jetzt auf jeden Fall schlauer! Wie immer großes Lob 
an die Community hier! :)

von Le_Q (Gast)


Lesenswert?

Ich hab mal in die limits.h reingeschaut, dort ist in der Tat 
Platformabhänig die größe eines "char" definiert! :)

Ich frag mich jetzt nur... wenn ich im code "char" schreibe, ohne irgend 
einen Header einzubinden, wie stell ich dann sicher das GCC den in der 
limits.h beschränkten Datentypen und nicht einen "Build-in" Datentypen 
verwendet? Oder tut er das sobald ich die libc dazu linke?

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


Lesenswert?

limits.h hat nichts mit der Bibliothek zu tun.  Dieser Header kommt
vom Compiler selbst und dokumentiert letztlich nur das, was der
Compiler ohnehin bereits so macht und kennt.

von Le_Q (Gast)


Lesenswert?

Ah okay verstehe. Danke schön.
Dann sind wir nur wieder beim alten Problem. Die Größe von char ist 
somit Compiler abhängig. Üblicherweise entspricht sizeof(char) 1. Das 
scheint aber nicht in C99 so 100% definiert zu sein. Denn es wird darauf 
verwiesen das die Größe in limits.h definiert ist.

von Daniel (Gast)


Lesenswert?

sizeof(char) ist immer 1 (Byte). Nur wie breit ein byte/char ist, ist 
Plattform abhängig, d.h. 1 Byte/Char sind nicht immer 8 Bit breit.

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


Lesenswert?

Daniel schrieb:
> Nur wie breit ein byte/char ist, ist
> Plattform abhängig, d.h. 1 Byte/Char sind nicht immer 8 Bit breit.

Der genaue Wert ist im Makro CHAR_BIT (ebenfalls aus limits.h) zu
finden.  Der Wert muss mindestens 8 sein.

von Rolf Magnus (Gast)


Lesenswert?

Le_Q schrieb:
> Die Verwendung von char macht C Code ggf. unportabel, da die Größe von
> char nicht fest definiert ist.

Eigentlich nicht mehr als die von uint8_t. Es stimmt zwar, daß char auch 
breiter als 8 Bit sein könnte, aber er ist auch gleichzeitig der 
kleinstmögliche Datentyp. Wenn also char mehr als 8 Bit breit ist, kann 
es auf der Plattform ganz einfach keinen uint8_t geben. Portabler wirds 
dadurch also nicht (und kann's auch nicht werden).

Le_Q schrieb:
> Dann sind wir nur wieder beim alten Problem. Die Größe von char ist
> somit Compiler abhängig. Üblicherweise entspricht sizeof(char) 1.
> Das scheint aber nicht in C99 so 100% definiert zu sein.

Das steht eigentlich ziemlich explizit unter 6.5.3.4
"The sizeof operator":
1
3 When applied to an operand that has type char, unsigned char, or signed char,
2
  (or a qualified version thereof) the result is 1.

von Oliver (Gast)


Lesenswert?

Le_Q schrieb:
> Und ich glaube was Oliver sagen wollte ist, dass nicht jeder
> Speicherbereich Byteweise Les- und Schreibbar sein muss. Das gilt es
> sowieso zu berücksichtigen.

Was ich vor allem sagen wollte, ist, daß du einmal überlegen solltest, 
welche Folgen es hätte, wenn du deine Software auf Systeme einschränkst, 
bei denen ein Char 8 Bit breit ist.
Vermutlich fast gar nichts.

Le_Q schrieb:
> Ich muss dazu sagen, dass diese Probleme sich durch Low-Level / Treiber
> Programmierung ergeben, auf Applikationsebene glaub ich nicht das es
> diese Konflikte gibt.

In diesem Fall wohl noch weniger als gar nichts.

Oliver

von Le_Q (Gast)


Lesenswert?

Ich hab mal geschaut, momentan läuft die Applikation

Oliver schrieb:
> Was ich vor allem sagen wollte, ist, daß du einmal überlegen solltest,
> welche Folgen es hätte, wenn du deine Software auf Systeme einschränkst,
> bei denen ein Char 8 Bit breit ist.
> Vermutlich fast gar nichts.

Die Einschränkung ist das die Software dann eben nur auf 
Machinen/Compilern läuft, die 8 Bit Chars verwenden. Wie schon angemerkt 
wurde, ist char der kleinste Datentyp und auf einer Maschine auf der ein 
char 16 Bit ist, ist dies dann der kleinste Datentyp. Folglich entsteht 
hierdurch eine Inkompatibilität. Es gibt halt nunmal DSPs auf denen ein 
CHAR mit 16 oder sogar 32 Bit definiert ist. Auf diesen würde der 
Treiber dann nicht funktioneren den ich schreibe. Aber dies lässt sich 
wohl nicht vermeiden. ;-)

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.