Forum: Compiler & IDEs C Language: char vs. signed char vs. unsigned char


von Mikro 7. (mikro77)


Lesenswert?

Moin,

im Nachbarthread habe ich ja schon geäußert, dass ich die Unterscheidung 
"verwirrend" finde.

Ich kann nachvollziehen, dass man die kleinste adressierbare Einheit, 
die in der CPU auch für Integerarithmetik benutzt wird, auf un/signed 
char gemapped wird.

Ein zusätzlicher Datentyp <char> mit der gleichen Breite erscheint mir 
da redundant. Er könnte vielleicht sinnvoll sein wenn (fehlerträchtige) 
Operationen (+,-,<,>> etc.) nicht erlaubt wären (wie in anderen 
Sprachen), sind sie aber.

Bei der Arbeit mit Zeicheintabellen wird mit Indizes gearbeitet, also 
nicht-negativen Zeichencodes, was für <unsigned char> sprechen würde.

Bleibt die Argumentation den <char> als Zeichentyp zur Unterscheidung 
(besseren Lesbarkeit) von den Integertypen un/signed char zu führen. Das 
wird allerdings schnell unscharf wenn man trotzdem die o.g. 
(mathematischen) Operationen darauf durchführen darf und zudem un/signed 
char heutzutage in der STL (IOS) nicht als Integer sondern als Zeichen 
interpretiert werden.

Kennt jemand den historischen Ursprung für den Datentyp <char>?

Grüße, mikro77

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

S. J. schrieb:
> Ein zusätzlicher Datentyp <char> mit der gleichen Breite erscheint mir
> da redundant.

Inhaltlich ist er redundant. Aber speziell bei C++ merkt man sehr 
deutlich, dass man da mit 2 Typen nicht auskommt, sondern alle 3 
benötigt.

Ursächlicher Fehler ist, dass man sich beim Typ <char> in C nicht darauf 
festgelegt hatte, ob der nun signed oder unsigned sein soll, dieser Typ 
aber implizit auf Strings zutrifft. Wenn dann <char>, <signed char> und 
<unsigned char> nicht formal verschiedene Typen sind, dann wird 
Overloading massiv erschwert.

von ... (Gast)


Lesenswert?

char ist je nach Compilervorliebe entweder ein signed char
oder ein unsigned char.
War ist daran unklar.

von (prx) A. K. (prx)


Lesenswert?

Als C erfunden wurde war es noch wenig relevant, ob mit oder ohne 
Vorzeichen. Damals waren das auch nur 2 Typen und <char> war entweder 
der eine oder der andere.

Die Bedeutung der Sprache C entwickelte sich aber massegeblich über die 
PDP-11 Familie, und die kann mit Bytes erheblich besser umgehen, wenn 
die ein Vorzeichen haben.

von (prx) A. K. (prx)


Lesenswert?

... schrieb:
> char ist je nach Compilervorliebe entweder ein signed char
> oder ein unsigned char.
> War ist daran unklar.

Er erwähnte die STL, also geht es auch um C++. Und da trifft das 
eindeutig nicht zu. Inhaltlich gibt es zwar nur 2 Typen, formal sind 
aber <char *>, <signed char *>, <unsigned char *> 3 verschiedene 
Pointertypen.

von ... (Gast)


Lesenswert?

> Und da trifft das
> eindeutig nicht zu. Inhaltlich gibt es zwar nur 2 Typen, formal sind
> aber <char *>, <signed char *>, <unsigned char *> 3 verschiedene
> Pointertypen.

Tja,

sowas waere mir ja suspekt mit der Konsequenz sowas nicht zu benutzen.

von (prx) A. K. (prx)


Lesenswert?

... schrieb:
> sowas waere mir ja suspekt mit der Konsequenz sowas nicht zu benutzen.

Das ist definitiv ein Konstruktionsfehler von C.
Aber "Hätte man damals nicht ..." hilft nicht weiter.

Abgesehen von IBM kannten die Amis damals eben nur ASCII,
und da reicht 0..127 aus.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

Die "signed"- oder "unsigned"-Frage ist weniger durch eine 
"Compilervorliebe" (was auch immer das sein soll) begründet, als durch 
konkrete Limitierung was die Plattform angeht.

C-Compiler generieren letztendlich Binärcode. Dazu müssen sie die 
Assemblerbefehle verwenden, die die Plattform zur Verfügung stellt. Wenn 
die dann eben mit signed 8-Bit Arithmetik besser umgehen kann als mit 
unsigned, dann wird der default char Datentyp für diese Plattform eben 
signed (x86, Sparc, ...) und wenn das nicht der Fall ist, unsigned (PPC, 
ARM, ...).

Da kann man sich drüber aufregen oder es bleiben lassen, es wird nix 
ändern.

Wäre es nicht so, wie's ist, würde wahrscheinlich darüber lamentiert, 
daß irgendein Compiler für 8-Bit Arithmetik ständig "unnötige" 
sign-extends in den Binärcode reinfummelt oder mit "zu breiten" ints 
rechnet.

von Mikro 7. (mikro77)


Lesenswert?

A. K. schrieb:

> Inhaltlich ist er redundant. Aber speziell bei C++ merkt man sehr
> deutlich, dass man da mit 2 Typen nicht auskommt, sondern alle 3
> benötigt.

Werden wirklich alle 3 Varianten "benötigt"? Oder ist das lediglich ein 
Zugeständnis an die C Altlast?

Man hätte prinzipiell auch alle Library Funktionen für Zeichen mit 
<unsigned char> definieren können, oder?

Oder bietet <char> tatsächlich einen Mehrwert?!

von (prx) A. K. (prx)


Lesenswert?

S. J. schrieb:
> Werden wirklich alle 3 Varianten "benötigt"? Oder ist das lediglich ein
> Zugeständnis an die C Altlast?

Andersrum. Das Problem tritt erst in C++ so richtig auf.

Wenn du nur 2 Typen hast und in C++ Overloads verwendest
  void f(signed char *);
  void f(unsigned char *);
dann weisst du nicht, welche davon bei f("Hallo") aufgerufen wird. Bei
  void f(char *);
  void f(signed char *);
  void f(unsigned char *);
ist das hingegen eindeutig. Das geht aber nur bei 3 Typen.

: Bearbeitet durch User
von Mikro 7. (mikro77)


Lesenswert?

Markus F. schrieb:
> C-Compiler generieren letztendlich Binärcode. Dazu müssen sie die
> Assemblerbefehle verwenden, die die Plattform zur Verfügung stellt. Wenn
> die dann eben mit signed 8-Bit Arithmetik besser umgehen kann als mit
> unsigned, dann wird der default char Datentyp für diese Plattform eben
> signed (x86, Sparc, ...) und wenn das nicht der Fall ist, unsigned (PPC,
> ARM, ...).

Das "gefällt" mir als historische Begründung bisher am Besten (paßt gut 
zur C-Historie).

von (prx) A. K. (prx)


Lesenswert?

S. J. schrieb:
> Das "gefällt" mir als historische Begründung bisher am Besten (paßt gut
> zur C-Historie).

Wobei die Vorliebe für signed/unsigned nicht nur mit der bestehenden 
Zielmaschine zu tun hat. Da hatte sich über die PDP-11 eine gewisse 
Gewohnheit entwickelt, chars als signed anzusehen. Man hatte einen 
Haufen Quellcode von ebendort, bei dem man sich nicht sicher war, wie er 
sich bei unsigned verhält. Also hat man beispielsweise auch auf x86 
einfach so weiter gemacht.

von Mikro 7. (mikro77)


Lesenswert?

A. K. schrieb:
> ...

Die Implikationen sind mich durchaus bewußt.

Mir geht es eher um die Sinnhaftigkeit des zusätzlichen <char> Typs im 
Rahmen der Sprachdefinition, die ich nicht wirklich sehe. Mit der 
historischen Deutung (als plattformabhängiger performanter Datentyp) 
kann ich aber zumindest "leben".

von Yalu X. (yalu) (Moderator)


Lesenswert?

S. J. schrieb:
> Ein zusätzlicher Datentyp <char> mit der gleichen Breite erscheint mir
> da redundant.
> ...
> Kennt jemand den historischen Ursprung für den Datentyp <char>?

Kurze Antwort:

char ist älter als unsigned char und signed char. "Zusätzlich" vorhanden
ist also nicht das char, sondern die beiden anderen char-Typen.

Lange Antwort:

Am Anfang, als die Welt (bzw. C) erschaffen wurde, was es noch ganz
einfach:

Es gab an elementaren Datentypen nur char, int, float und double, aber
noch nicht dieses neumodische Zeugs wie signed, unsigned, long, short
usw. (siehe C Reference Manual von Dennis M. Ritchie).

Die Integer-Typen int und char waren beide vorzeichenbehaftet und wurden
in Zweierkomplementdarstellung im Speicher abgelegt. Die verwendete
Zeichencodierung (ASCII) umfasste nur 7 Bit, so dass man jedes Zeichen
als nichtnegativen char-Wert darstellen konnte.

Da es noch keinen verbindlichen Standard gab sind einige Compilerbauer
von diesen Vorgaben abgewichen, weil es für ihre Rechnerarchitekturen
günstiger war.

Die einen haben für die vorzeichenbehafteten Zahlen die Einer- statt der
Zweierkomplementdarstellung gewählt, weil die Rechnerhardware diese
Darstellung verlangte.

Andere haben aus dem vorzeichenbehafteten char ein vorzeichenloses
gemacht. Ein Grund dafür könnte bspw. darin gelegen haben, dass die
verwendete CPU keinen Befehl zur vorzeichenrichtigen Erweiterung eines
Bytes in ein Wort hatte und deswegen für jede explizite oder implizite
Konvertierung von char nach int eine Fallunterscheidung erforderlich
gewesen wäre.

Irgendwann kam dann das unsigned, zuerst nur für den Typ int, später (im
ASNI-Standard) auch für alle anderen Integer-Typen. Bei int war jetzt
klar: mit unsigned davor ist es vorzeichenlos, sonst vorzeichenbehaftet.
Bei char ohne unsigned gab es je nach Plattform bzw. Compilerhersteller
nach wie vor beide Möglichkeiten. Man wollte dies auch nicht ändern,
weil sonst bestehende Software, die von einem bestimmten Verhalten von
char aufbaute, nicht mehr funktioniert hätte.

Also hat der ANSI-Standard zusätzlich zum klar definierten unsigned char
und dem wankelmütigen char noch ein klar definiertes signed char
hinzugefügt. Bei dieser Gelegenheit hat man das signed-Schlüsselwort
auch für die anderen Integer-Typen erlaubt, obwohl es dort nicht nötig
ist, weil es bereits den Default darstellt.

Daran hat sich bis heute nichts mehr geändert. Wichtig zu wissen ist,
dass char-Literale wie bspw. 'A' oder Zeichen in Stringliteralen wie
"AAA" immer vom Typ char sind, weswegen es plattformabhängig ist, ob
bspw. ein nach ISO/IEC 8859-1 codiertes 'Ä' in C den Wert 196 oder -60
hat. Deswegen ist es ratsam, char nur für Zeichen zu verwenden. Für
8-Bit Zahlen sollte man hingegen immer signed char oder unsigned char
(oder auch einen passenden Typ aus stdint.h) nehmen, um bei Berechnungen
plattformunabhängig zu sein.

von Mikro 7. (mikro77)


Lesenswert?

Sehr schön geschrieben. Danke! :-)

von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> Die einen haben für die vorzeichenbehafteten Zahlen die Einer- statt der
> Zweierkomplementdarstellung gewählt, weil die Rechnerhardware diese
> Darstellung verlangte.

Gab es wirklich mal C-Compiler für solche Rechner?

von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> Irgendwann kam dann das unsigned, zuerst nur für den Typ int, später (im
> ASNI-Standard) auch für alle anderen Integer-Typen.

Die unsigned Varianten der Integer-Typen gab es bereits in K&R C vor dem 
ANSI Standard, meiner Erinnerung nach auch das Keyword "signed". In der 
allerersten Sprachfassung, noch vor K&R, mag das anders gewesen sein.

Unklar war damals aber noch, welche Variante sich z.B. auf 32-Bittern in
   (unsigned short)x + (int)y
durchsetzt, sign preservation vs. value preservation nannte sich das. 
Das wurde erst in C89 geklärt.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

A. K. schrieb:
> Yalu X. schrieb:
>> Die einen haben für die vorzeichenbehafteten Zahlen die Einer- statt der
>> Zweierkomplementdarstellung gewählt, weil die Rechnerhardware diese
>> Darstellung verlangte.
>
> Gab es wirklich mal C-Compiler für solche Rechner?

Das hatten wir hier erst vor kurzem: Die UNIVAC-Rechner und deren Söhne
von Unisys, für die es auch einen C-Compiler und sogar Java gibt.

A. K. schrieb:
> Die unsigned Varianten der Integer-Typen gab es bereits in K&R C vor dem
> ANSI Standard, meiner Erinnerung nach auch das Keyword "signed". In der
> allerersten Sprachfassung, noch vor K&R, mag das anders gewesen sein.

Kann sein, ich habe gerade keinen Zugriff auf einen alten K&R.

Im "Rationale for International Standard — Programming Languages — C,
Revision 5.10, April-2003" steht jedenfalls geschrieben:

1
Several new types were added in C89:
2
3
    void
4
    void*
5
    signed char
6
    unsigned char
7
    unsigned short
8
    unsigned long
9
    long double
10
11
And new designations for existing types were added:
12
13
    signed short    for short
14
    signed int      for int
15
    signed long     for long

Ich weiß allerdings nicht, auf welche Prä-C89-C-Version sich das "added"
bezieht.

von Markus F. (mfro)


Lesenswert?

A. K. schrieb:

>
> Gab es wirklich mal C-Compiler für solche Rechner?

Ich denke schon. Die Cyber 180 war eine solche Maschine. Die konnte 
sowohl mit Zweierkomplementen (NOS/VE) als auch mit Einerkomplementen 
(NOS wie beim Vorgängermodel Cyber 170) umgehen und einen C-Compiler 
gab's auch.

War halt für Heimanwendungen nicht so verbreitet, mit so was wurde 
jahrzehntelang unser Wetter ausgerechnet ...

http://www.computerwoche.de/a/us-anbieter-ersetzt-serie-170-durch-sechs-neue-modelle-control-data-verdoppelt-cyber-leistung,1174189

P.S.: und wenn ich mich ganz täusche, dann haben die "Cray-Wohnrechner" 
zumindest ihre Adressrechnung im Einerkomplement gemacht.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> Kann sein, ich habe gerade keinen Zugriff auf einen alten K&R.

Korrektur - die Sprachdefinition kannte diese Typen zwar nicht, aber 
viele Compiler implementierten auch die übrigen "unsigned" Typen.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Markus F. schrieb:
> P.S.: und wenn ich mich ganz täusche, dann haben die "Cray-Wohnrechner"
> zumindest ihre Adressrechnung im Einerkomplement gemacht.

Ja, dass es im Umfeld von Cray solche Maschinen gab ist mir bewusst. Ich 
fragte mich eher, inwieweit dafür C als Programmiersprache existierte.

von Markus F. (mfro)


Lesenswert?

A. K. schrieb:
> Ja, dass es im Umfeld von Cray solche Maschinen gab ist mir bewusst. Ich
> fragte mich eher, inwieweit dafür C als Programmiersprache existierte.

Für die Cray's gab's (fast) "normale" C-Compiler.

von (prx) A. K. (prx)


Lesenswert?

Markus F. schrieb:
> Für die Cray's gab's (fast) "normale" C-Compiler.

Als die Crays schon seinen Namen trugen arbeiteten sie bereits im 
Zweierkomplement. Es waren die vorherigen Crays von CDC, die im 
Einerkomplement arbeiteten, also CDC6600, CDC7600 und deren spätere 
Varianten.

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

> Abgesehen von IBM kannten die Amis damals eben nur ASCII, und da reicht 0..127 
aus.

ASCII war die Erlösung. Davor gab es 6..9 Bit pro Zeichen mit teils von 
Rechnermodell zu Rechnermodell innerhalb einer Familie unterschiedlicher 
Kodierung. Nur IBM hat noch 2..3 Jahrzehnte gebraucht, um sich von ihrem 
Zone-Code der Lochkarten zu lösen.

von (prx) A. K. (prx)


Lesenswert?

Carl D. schrieb:
>> Abgesehen von IBM kannten die Amis damals eben nur ASCII, und da reicht 0..127
> aus.
>
> ASCII war die Erlösung.

EBCDIC und ASCII kamen praktisch zeitgleich. Aber bei EBCDIC wäre 
niemand auf die Idee gekommen, das in 8 Bits mit Vorzeichen zu codieren. 
Durchgesetzt hatte sich das 8-Bit Byte als universelle Daten- und 
Adressierungseinheit mit IBMs 360 Familie in den 60ern.

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

Die 8 Bit, ja, die hatten sich durchgesetzt, auch wenn wir heute eher 
bei WCHAR sind als 16 Bit.
 IBM hat mit EBCDIC eher versucht ihr Lochkartenformat rüberzuretten. 
Und andere schräge Sachen gemacht. Z.B. das durchgestrichene O um es von 
der Zahl 0 unterscheiden zu können. Als auf den Kisten Assembler lernte, 
nach einem Jahrzehnt ASCII-Zeichensatz, hab ich mich über den Befehl 
"null" gewundert bis ich verstanden hatte, daß das "OR" bedeuten sollte.
Ansonsten ein schöne Zeit und mehr mit Assembler zu verdienen als auf 
Mainframes, war damals und ist heute kaum vorstellbar.

von Hans (Gast)


Lesenswert?

A. K. schrieb:
> S. J. schrieb:
>> Ein zusätzlicher Datentyp <char> mit der gleichen Breite erscheint mir
>> da redundant.
>
> Inhaltlich ist er redundant. Aber speziell bei C++ merkt man sehr
> deutlich, dass man da mit 2 Typen nicht auskommt, sondern alle 3
> benötigt.

Leider macht sich die C++ Standardbibliothek das nicht zunutze. Bei 
einem std::ostream werden char, signed char und unsigned char immer als 
Zeichen ausgegeben. Konsequenter wäre, die signed/unsigned-Variante als 
Zahl auszugeben und nur die reinen chars als Zeichen.

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.