Forum: PC-Programmierung in_addr_t - Datentypen Umwandlung Sinnfrage


von Matthias X. (Gast)


Lesenswert?

Die Funktion inet_addr() wandelt, sofern ich es richtig verstanden habe, 
einen IP-String in die Network Byte Order um.

Urspruenglich sieht die IP so aus
1
char *test_ip = "192.168.2.101";

In der Manpage steht, dass der Rueckgabewert von inet_addr() vom Typ 
in_addr_t ist, was lt. /usr/include/netinet/in.h ein uint32_t ist und 
somit nichts anderes als
1
typedef unsigned long int

ist.

Ich nehme jetzt einfach mal an, dass inet_addr somit einen String in 
einen Zahlenstrang umwandelt, ist das korrekt?

Eine zweite Frage betrifft die Zuweisung dieses Ergebnisses an eine 
Struktur vom Typ arp_header.

Das sieht so aus *(u_long *)arp->ar_tip = inet_addr(dst_ip);

Wenn ich den Sachverhalt oben richtig interpretiert habe
wird unsigned char, welcher urspruenglich in der Struktur steht, so auf 
das Ergebnis von inet_addr() umgecastet.

Allerdings ist mir die Bedeutung der beiden Zeiger-Sterne hier unklar. 
Wieso wird aus einem zurueckgegebenen unsigned long int ein (u_long *) 
Zeiger???

Und wofür ist der äußere Zeiger???

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Matthias X. schrieb:
> Ich nehme jetzt einfach mal an, dass inet_addr somit einen String in
> einen Zahlenstrang umwandelt

Was ist ein "Zahlenstrang"?

von g457 (Gast)


Lesenswert?

> In der Manpage steht [..]
> Ich nehme jetzt einfach mal an, dass inet_addr somit einen String in
> einen Zahlenstrang umwandelt, ist das korrekt?

Warum nich ein paar Zeilen weiterlesen?

> Und wofür ist der äußere Zeiger???

Das ist kein Zeiger sondern Dereferenzieren.

von (prx) A. K. (prx)


Lesenswert?

Matthias X. schrieb:
> in_addr_t ist, was lt. /usr/include/netinet/in.h ein uint32_t ist und
> somit nichts anderes als
>
1
typedef unsigned long int
> ist.

Kann auch ein anderer Typ sein:
/usr/include/netinet/in.h: typedef uint32_t in_addr_t;
/usr/include/stdint.h: typedef unsigned int uint32_t;

Nimm deshalb in_addr_t für Deklarationen, nicht u_long.

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Die Funktion inet_addr() ist übrigens veraltet.
Heutzutage nimmt man eher sowas wie getaddrinfo(), was auch IPv6 kann.

von Matthias X. (Gast)


Lesenswert?

g457 schrieb:

>Das ist kein Zeiger sondern Dereferenzieren.

Verstehe. Den Dereferenzierungsstern mal außen vor gelassen, wieso wird 
aus einem unsigned long int ein (u_long *)?

Die Wertzuweisung durch den Referenzierer mach Sinn, wenn man es mit 
Zeigern zu tun hat, aber hier hatte ich einen unsigned long int, was 
soweit ich weiss, kein Zeiger ist.

Ich habe mir die Manpage gerade noch einmal angesehen und zu diesem 
Rueckgabeergebnis nichts finden koennen, abgesehen davon, dass die 
Rueckgabe vom Typ in_addr_t ist.

von Matthias X. (Gast)


Lesenswert?

Jim Meba:

>Die Funktion inet_addr() ist übrigens veraltet.
>Heutzutage nimmt man eher sowas wie getaddrinfo(), was auch IPv6 kann.

In der Manpage steht auch
>Avoid   its   use   in   favor   of  inet_aton(), inet_pton(3), or 
>getaddrinfo(3), which provide a cleaner way  to  indicate error return.

Nur war das nicht die Frage.

von Matthias X. (Gast)


Lesenswert?

A. K. schrieb:

>Kann auch ein anderer Typ sein:
>/usr/include/netinet/in.h: typedef uint32_t in_addr_t;
>/usr/include/stdint.h: typedef unsigned int uint32_t;

Mir hat grep nur zwei ausgespuckt, die die ich schon genannt habe und 
die netinet/in.h

von (prx) A. K. (prx)


Lesenswert?

Matthias X. schrieb:
> Mir hat grep nur zwei ausgespuckt, die die ich schon genannt habe und
> die netinet/in.h

Anderes System, andere Typen. in_addr_t muss ein 32-Bit Typ sein, und 
"unsigned long" ist zwar in 32-Bit Linux und bei Microsoft 32 Bits 
breit, nicht aber in 64-Bit Linux.

Deshalb ist es ja wichtig, in_addr_t statt "unsigned long" zu verwenden. 
Deshalb gibt es diesen Typ.

: Bearbeitet durch User
von Matthias X. (Gast)


Lesenswert?

@ A. K.:

In meinem Programm nutze ich auch in_addr_t.

Nur habe ich mich gefragt, was hinter diesem Datentyp steht, was dann in 
der Folge zu der hier eingestellten Frage geführt hat.

von (prx) A. K. (prx)


Lesenswert?

Matthias X. schrieb:
> Nur habe ich mich gefragt, was hinter diesem Datentyp steht, was dann in
> der Folge zu der hier eingestellten Frage geführt hat.

*(u_long *)arp->ar_tip = inet_addr(dst_ip);
   ist
*(u_long *)(arp->ar_tip) = inet_addr(dst_ip);

/usr/include/linux/if_arp.h: unsigned char ar_tip[4];

Es besagt, dass das Resultat von inet_addr() als u_long in das Array
   arp->ar_tip
geschrieben wird. Dessen Adresse wird umgewandelt in einen Pointer auf 
u_long, und der wird dann dereferenziert.

Solche "Schönheiten" sind leider sehr typisch für TCP/IP-Code.

Auf einem 64-Bit Linux werden hier 8 Bytes in ein Array geschrieben, das 
nur 4 Bytes lang ist. Nicht ratsam. Es könnte dabei auch ein 
Alignment-Problem mitsamt Crash geben, je nach Kontext.

: Bearbeitet durch User
von Matthias X. (Gast)


Lesenswert?

A.K. schrieb:

>Es besagt, dass das Resultat von inet_addr() als u_long in das Array
>   arp->ar_tip
>geschrieben wird. Dessen Adresse wird umgewandelt in einen Pointer auf
>u_long, und der wird dann dereferenziert.

Kann sein das ich komplizierter gedacht habe oder es selbst 
verkompliziert habe.

Ich habe eine IP, sagen wir

char *meine_ip = "192.168.2.101"

Das ist ein char Array richtig. Nun will ich diese IP einem unsigned 
char array zuweisen, so wie es dir Struktur vorsieht. Da ich aber die 
Newtwork Byte Order benötige, benutze ich inet_addr(), so der weitere 
Gedanke.

Wie gesagt, ob diese Funktion nicht mehr up to date ist lasse ich mal 
außen vor.

Also char array nach unsigned char array. Das kann ich nachvollziehen. 
Beides ist der Datentyp char. Wieso wird aus einem char ein int, einfach 
gesprochen, wenn ich jetzt mal den Basisdatentyp nehme, dass ist meine 
Frage.

von (prx) A. K. (prx)


Lesenswert?

Matthias X. schrieb:
> Das ist ein char Array richtig. Nun will ich diese IP einem unsigned
> char array zuweisen, so wie es dir Struktur vorsieht. Da ich aber die
> Newtwork Byte Order benötige, benutze ich inet_addr(), so der weitere
> Gedanke.

Falsch. inet_addr wandelt nicht die Byteorder um, sondern deinen 
12-Zeichen Text in eine 4-Byte Binärdarstellung in einem 32-Bit Wert. 
Die Byteorder ist dann nochmal ein ganz anderes Thema.

: Bearbeitet durch User
von Matthias X. (Gast)


Lesenswert?

Auszug aus der Manpage:

>The  inet_addr()  function  converts  the Internet host address cp from
>IPv4 numbers-and-dots notation into binary data in network byte  order

Steht so in der Manpage. Auch das es in binary data umgewandelt wird. 
Nur wieso dieses Format?

von (prx) A. K. (prx)


Lesenswert?

Matthias X. schrieb:
> Steht so in der Manpage. Auch das es in binary data umgewandelt wird.
> Nur wieso dieses Format?

Frag den Erstautor dieser Lib-Funktion, wenn er noch lebt. Die 
Darstellung von IP-Adressen als 32-Bit Datentyp ist zwar zeit- und 
platzsparend, sorgte aber für einen gewaltigen Rattenschwanz an 
Folgeproblemen.

: Bearbeitet durch User
von Matthias X. (Gast)


Lesenswert?

Das ich im weiteren eine Zahl benötige ist aus Netzwerksicht klar. 
Unterteilung der Netzwerke selbst, bishin zu der jeweiligen "Zahl" des 
dazugehörigen Hosts.

Auch wenn man bspw. von Klasse C Netzwerken spricht, so funktioniert die 
Ansteuerung mittels numerischer Werte [192.0.0.0 - 223.255.255.255].

Ergibt dann irgendwie doch Sinn, von char nach long umzuwandeln. Hm

von Matthias X. (Gast)


Lesenswert?

...anfänglich irgendwie zu kompliziert gedacht!

von (prx) A. K. (prx)


Lesenswert?

Matthias X. schrieb:
> Ergibt dann irgendwie doch Sinn, von char nach long umzuwandeln. Hm

Kann man machen. Wenn man type casts, misalignment crashes und buffer 
overflows gern hat. Und IPv6 hasst.

: Bearbeitet durch User
von Matthias X. (Gast)


Lesenswert?

Ich sag ja, ich nutze in_addr_t.

Nur finde ich es irgendwie schade mit solchen Typedefs zu arbeiten, wenn 
ich erst einmal nachschauen muss, was dahinter steckt.

Aus den von Dir genannten Gründen kann und soll man ja gerne typedef 
nutzen, um Portabilität und Sicherheit zu gewährleisten.

Etwas einheitlichere Datentypen oder solche, anhand derer man zumindest 
ableiten kann, was sich dahinter verbirgt, wären aus meiner Sicht aber 
irgendwie wünschenswert.

von Klaus (Gast)


Lesenswert?

A. K. schrieb:
> Die
> Darstellung von IP-Adressen als 32-Bit Datentyp ist zwar zeit- und
> platzsparend, sorgte aber für einen gewaltigen Rattenschwanz an
> Folgeproblemen.

Sorry, aber IP(V4)-Adressen sind ein vorzeichenloser 32-Bit Datentyp, 
so sind sie erfunden worden. Und genau damit funktionieren Netzmasken, 
Netzadressen und Brodcastadressen. Sie sind einfache logische Funktionen 
auf 32-Bit unsigned Integer. Alle anderen Darstellungen sind nur Krücken 
für User, die keine Binärarithmetik verstehen.

Platzsparen war nur ein Aspekt, das direkte matchen in HW durch einige 
Komparatoren während die Bits noch ins Eingangsregister geschoben 
werden, war ein anderer.

Matthias X. schrieb:
> Nur finde ich es irgendwie schade mit solchen Typedefs zu arbeiten, wenn
> ich erst einmal nachschauen muss, was dahinter steckt.

Versteh ich jetzt nicht. Warum muß man wissen, was hinter in_addr_t 
steckt? Man schaut da nur mit den dafür vorgegebenen Funktionen rein, 
objektorientiert eben. Wenn man sauber programmiert, könnte das 
problemlos auch ein struct sein.

MfG Klaus

von Matthias X. (Gast)


Lesenswert?

Karl schrieb:

>Versteh ich jetzt nicht. Warum muß man wissen, was hinter in_addr_t
>steckt?

Müssen muss man gar nichts. Klar, man kann alles einfach gemäß den 
Vorgaben übernehmen und so hinnehmen, aber man kann es auch 
hinterfragen, sei es, dass man gerne etwas verstehen möchte, was man 
macht oder aus reiner Neugier.

Ich finde die typedefs in c teilweise sehr unglücklich.

Bsp. u_int8_t, ist, wenn man das als Laie das erste Mal sieht, intuitiv 
ein integer...In der types.h steht aber das es unsigned char ist.

Auch die verschiedenen Schreibweise, leicht abgewandelt....
u_int8_t     u_int8      uint8

Aber das ist ein Thema für einen eigenen Thread.

von Rolf M. (rmagnus)


Lesenswert?

Matthias X. schrieb:
> Karl schrieb:
>
>>Versteh ich jetzt nicht. Warum muß man wissen, was hinter in_addr_t
>>steckt?
>
> Müssen muss man gar nichts. Klar, man kann alles einfach gemäß den
> Vorgaben übernehmen und so hinnehmen, aber man kann es auch
> hinterfragen, sei es, dass man gerne etwas verstehen möchte, was man
> macht oder aus reiner Neugier.

Moment mal. Du kannst dich doch nicht einerseits beschweren, dass du das 
nachschauen musst, aber andererseits dann sagen, dass du es ja nur aus 
persönlichem Interesse tust und es eigentlich nicht müsstest.

> Bsp. u_int8_t, ist, wenn man das als Laie das erste Mal sieht, intuitiv
> ein integer...In der types.h steht aber das es unsigned char ist.
>
> Auch die verschiedenen Schreibweise, leicht abgewandelt....
> u_int8_t     u_int8      uint8

Das ist "historisch so gewachsen". Leider gibt es solche Typen als Teil 
der C-Norm erst seit C99. Davor musste sich die jeder selbst defnieren. 
Deshalb gibt es so viele Varianten davon.

von Matthias X. (Gast)


Lesenswert?

Rolf Magnus schrieb:

>Moment mal. Du kannst dich doch nicht einerseits beschweren, dass du das
>nachschauen musst, aber andererseits dann sagen, dass du es ja nur aus
>persönlichem Interesse tust und es eigentlich nicht müsstest.

Muss ich ja auch nicht, wenn ich der Logik von Klaus folge. Aber es kann 
ja nicht Sinn der Sache sein, einfach Dinge zu übernehmen und danach 
gefragt, was Du da entgegengenommen hast oder wofür es ist, dann zu 
sagen, keine Ahnung, steht so in der Manpage.

Ich meine wovon reden wir. Wenn man anfängt zu Programmieren erfährt, 
respektive liest man als erstes, dass es wichtig ist seinen Code gut zu 
dokumentieren und bei Variablennamen beispielsweise bestimmte 
Konventionen einzuhalten, damit man drei Monate später oder bei Übergabe 
an eine Dritte Person noch sagen kann, was die ganze Geschichte macht.

>Das ist "historisch so gewachsen". Leider gibt es solche Typen als Teil
>der C-Norm erst seit C99. Davor musste sich die jeder selbst defnieren.
>Deshalb gibt es so viele Varianten davon.

Ich persönlich hätte es vorgezogen, wenn man typedefs nur rudimentär 
einsetzen würde. char ist char, unsigned char ist unsigned char und 
fertig. Einheitlich, jeder weiß sofort was es ist und muss nicht erst
grep -l -i -r u_int_96 in der /usr/include/x86_64-linux-gnu/ irgendwas 
suchen.

typedef 8_intU-t __kUhgLocke_23

von Klaus W. (mfgkw)


Lesenswert?

Matthias X. schrieb:
> char ist char, unsigned char ist unsigned char und
> fertig.

Eben nicht.

char kann mal signed char sein oder unsigned char, char muß nicht 8 Bit 
haben, int nicht 2 char und so weiter.

int ist halt immer die natürliche und effektive Größe der jeweiligen HW, 
short nie größer und long nie kleiner als das natürliche int.
char ist immer die kleinste adressierbare Größe.
Und ob char signed char ist oder unsigned char, kann in jedem System (HW 
und Compiler/toolchain) anders geregelt sein.

Die genaue Definition ergibt sich also aus den Umständen, das ist in C 
nunmal so.

Wenn man im konkreten Fall aber damit nicht leben will und bspw. einen 
Wert mit 8 Bit ohne Vorzeichen braucht, ist man mit char schlecht 
beraten - kann gehen, muß aber nicht.
Dann nimmt man halt uint8_t und fertig. Wo ist das Problem? Genau für 
solche Fälle sind die Werte in stdint.h eingeführt worden.
Wem sie nicht gefallen, der darf sie auch ignorieren.

Wenn in deinem Auto ein Knopf ist, den du nicht kennst und der dich 
überfordert: drücke einfach nicht drauf.

von Klaus (Gast)


Lesenswert?

Matthias X. schrieb:
> Muss ich ja auch nicht, wenn ich der Logik von Klaus folge. Aber es kann
> ja nicht Sinn der Sache sein, einfach Dinge zu übernehmen und danach
> gefragt, was Du da entgegengenommen hast oder wofür es ist, dann zu
> sagen, keine Ahnung, steht so in der Manpage.

Es ist sogar sehr sinnvoll, diese Sachen einfach zu übernehmen. Auf 
anderen Rechnerarchitekturen oder in einer neueren Version kann das 
intern ganz anders aussehen. Solange man nur die vorgegebenen Funktionen 
benutzt, bleibt der Source kompatibel, muß nur neu übersetzt werden. Wie 
war es sonst möglich, Millionen und Abermillionen Lines of Code aus 
einer 32 Bit Welt in eine 64 Bit Umgebung in so kurzer Zeit zu 
portieren.

Kapselung oder Information hiding sind hier Stichworte.

MfG Klaus

von PittyJ (Gast)


Lesenswert?

Wie schon öfter mal, hier ein kurzer Hinweis auf den TI 28xxx DSP (ein 
aktuelles Produkt):
die kleinste Speichereinheit ist dort 16 bit. Ein char hat also 16 Bit. 
Ein long hat 32 Bit.
sizeof(char) -> 1
sizeof(long) -> 2

Soviel dazu, dass es immer 8-Bit Zugriff geben muss.

von Rolf M. (rmagnus)


Lesenswert?

Matthias X. schrieb:
> Rolf Magnus schrieb:
>
>>Moment mal. Du kannst dich doch nicht einerseits beschweren, dass du das
>>nachschauen musst, aber andererseits dann sagen, dass du es ja nur aus
>>persönlichem Interesse tust und es eigentlich nicht müsstest.
>
> Muss ich ja auch nicht, wenn ich der Logik von Klaus folge. Aber es kann
> ja nicht Sinn der Sache sein, einfach Dinge zu übernehmen und danach
> gefragt, was Du da entgegengenommen hast oder wofür es ist, dann zu
> sagen, keine Ahnung, steht so in der Manpage.

Nein. Du sagst: "in_addr_t ist ein Typ, um eine IPv4-Adresse 
aufzunehmen."
Was auf deiner spezifischen Zielplattform dafür am besten geeignet ist, 
darum hat sich schon jemand gekümmert.

> Ich meine wovon reden wir. Wenn man anfängt zu Programmieren erfährt,
> respektive liest man als erstes, dass es wichtig ist seinen Code gut zu
> dokumentieren und bei Variablennamen beispielsweise bestimmte
> Konventionen einzuhalten, damit man drei Monate später oder bei Übergabe
> an eine Dritte Person noch sagen kann, was die ganze Geschichte macht.

Ja, und da sind z.B. auch genau die typedefs, die du hier kritisierst, 
ein Element davon. Denn so wie aussagekräftige Variablennamen sind auch 
aussagekräftige Typnamen förderlich für ein leichteres Verständnis des 
Codes.
Und dann ist da eben auch die Unabhängigkeit von der Zielplattform. 
Beispiel: Du willst wissen, wie weit die Speicherstellen, auf die zwei 
Zeiger zeigen, auseinander sind. Du musst also die Differenz zweier 
Zeiger nehmen. Welchen Typ nimmst du nun, um das Ergebnis zu speichern? 
Ganz einfach! Nim ptrdiff_t. Ganz unabhängig davon, für welche Plattform 
du programmierst, ist das der Typ, um jede mögliche Differenz zweier 
Zeiger aufnehmen zu können.

>>Das ist "historisch so gewachsen". Leider gibt es solche Typen als Teil
>>der C-Norm erst seit C99. Davor musste sich die jeder selbst defnieren.
>>Deshalb gibt es so viele Varianten davon.
>
> Ich persönlich hätte es vorgezogen, wenn man typedefs nur rudimentär
> einsetzen würde. char ist char, unsigned char ist unsigned char und
> fertig.

Und "int ist int"? Kann 16 Bit sein, kann aber auch 32 Bit sein - oder 
24.

von Matthias X. (Gast)


Lesenswert?

Erst mal meinen Dank dafür, dass die Rückmeldungen sachlich sind!

Um bei dem Eingangsbeispiel zu bleiben, nämlich dem uint32_t, was soviel 
wie ein unsigned long int ist.

Kann ja sein, dass ich das nicht ganz erfasst habe, aber durch das 
typedef ändert sich doch bloss die Schreibweise -in diesem konkreten 
Fall-.

Das Stichwort der Portabilität kann ich somit nicht ganz nachvollziehen, 
weil egal ob es das eine oder das andere auf irgendeinem System ist.

Beides beschreibt doch grundsätzlich das gleiche, oder irre ich mich da?

p.s. Als Ergänzung...Wenn der uint32_t auf einem bestimmten System so 
und so viele Bytes einnimmt, dann tut es das Äquivalent unsigned long 
int doch auch, oder?

von Rolf M. (rmagnus)


Lesenswert?

Matthias X. schrieb:
> Das Stichwort der Portabilität kann ich somit nicht ganz nachvollziehen,
> weil egal ob es das eine oder das andere auf irgendeinem System ist.
>
> Beides beschreibt doch grundsätzlich das gleiche, oder irre ich mich da?

Nein, eben nicht - also ... ja, du irrst dich. ;-)
Ein uint32_t ist ein vorzeichenloser 32-Bit-Typ. Immer. Das mag auf 
deiner Zielplattform einem unsigned long entsprechen, aber auf einer 
anderen kann das anders sein. Es gibt nämlich auch Systeme, wo ein 
unsigned long 64 Bit breit ist, ein uint32_t aber selbstverständlich 
weiterhin 32 Bit.
Das ist gerade die Idee dahinter, solche Plattformunterschiede an den 
Stellen, wo man sie nicht gebrauchen kann, los zu werden.

: Bearbeitet durch User
von Matthias X. (Gast)


Lesenswert?

Rolf Magnus schrieb:

>Nein, eben nicht. Ein uint32_t ist ein vorzeichenloser 32-Bit-Typ.
>Immer. Das mag auf deiner Zielplattform einem unsigned long entsprechen,
>aber auf einer anderen kann das anders sein. Es gibt nämlich auch
>Systeme, wo ein unsigned long 64 Bit breit ist, ein uint32_t aber
>selbstverständlich weiterhin 32 Bit.

Bis jetzt arbeite ich ausschließlich auf einer 64 Bit Architektur. Mir 
ist es nicht in den Sinn gekommen, dass native Datentypen, je nach 
Plattform, unterschiedliche Größen aufweisen können.

Da es mir nicht darum ging hier den Argumentationspunk zu geben, sondern 
die Thematik besser zu verstehen, Frage, wo finde ich Infos über diese 
Thematik, die ich nachlesen kann bzw. wonach sollte ich meine 
Suchmaschine meines Vertrauens konkret fragen, wenn ich mehr darüber 
erfahren will.

Aktuell ist nämlich noch 'Lost in Translation' angesagt.

von (prx) A. K. (prx)


Lesenswert?

Matthias X. schrieb:
> Um bei dem Eingangsbeispiel zu bleiben, nämlich dem uint32_t, was soviel
> wie ein unsigned long int ist.

Was bei dir ein unsigned long ist.

> p.s. Als Ergänzung...Wenn der uint32_t auf einem bestimmten System so
> und so viele Bytes einnimmt, dann tut es das Äquivalent unsigned long
> int doch auch, oder?

Wenn du genau und nur für ein bestimmtes System programmierst, dann nimm 
was du willst. Aber sei dir ganz sicher, diesen Code nie wiederverwenden 
zu wollen.

Da ich jedoch weitgehend unveränderten Code auch mal auf 16-, 32- und 
64-Bit Systemen verwende, auch mit unterschiedlichen Betriebssystemen, 
insbesondere wenn es sich um Netzwerk-Code handelt, halte ich es da 
anders.

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


Lesenswert?

Matthias X. schrieb:
> Bis jetzt arbeite ich ausschließlich auf einer 64 Bit Architektur.

Und zwar offensichtlich in Windows. Denn long ist m.W. nur in Windows 
ein 32-Bit Typ (LLP64). Und das auch nur deshalb, weil Microsoft und die 
Anwendungsprogrammierer es ähnlich hielten wie du und in 16- wie 32-Bit 
Windows long implizit als 32-Bit Typ ansahen. Und Microsoft sich 
folglich nicht traute, das nahe liegende zu tun, statt dessen long long 
für 64 Bits benötigte.

Auf anderen 64-Bit Systemen ist long ein 64-Bit Typ (LP64). Es gibt 
(oder gab) auch Systeme, auf denen int 64 Bit breit ist (ILP64), sogar 
mit short in 64 Bits (SILP64).
https://de.wikipedia.org/wiki/64-Bit-Architektur#Programmiermodell

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Matthias X. schrieb:
> Bis jetzt arbeite ich ausschließlich auf einer 64 Bit Architektur. Mir
> ist es nicht in den Sinn gekommen, dass native Datentypen, je nach
> Plattform, unterschiedliche Größen aufweisen können.

Genau das tun sie. Vorgegeben sind im wesentlichen Mindestgrößen, bzw 
Wertebereiche. Ein int muss z.B. mindestens den Bereich -32767 bis 
+32767 abdecken können, darf aber auch beliebig mehr.
Auf einem AVR ist int 16 Bit breit, auf deinem PC aber 32 Bit.
Das gleiche gilt ja auch für andere Typen wie Zeiger. Ein AVR braucht 
auch viel kleinere Zeiger als ein 64-Bit-PC, da der Adressraum ja viel 
kleiner ist. So sind die Typen alle abhängig von der Plattform, um auf 
diese ideal zugeschnitten werden zu können.
Oft braucht man dann aber eben Typen, die bestimmte Garantien geben 
können, wie "ein Integer, der die Differenz zweier Zeiger aufnehmen 
kann" oder "ein exakt 32 Bit breiter Integer", und dann kommen die 
typedefs ins Spiel.

von (prx) A. K. (prx)


Lesenswert?

Ich hatte mal einen Compiler für eine Hardware-Plattform gestrickt, die 
es mit identischem Befehlssatz in 16- und 32-Bit Ausführungen gab. Bei 
der 16-Bit Version war folglich short/int/pointer=16bit angesagt, bei 
der 32-Bit Version aber short/int/pointer=32bit. Weil es darin nur 
Hardware-Support für 8-Bit Bytes und Maschinenworte gab, nicht aber für 
Halbworte, somit in der 32-Bit Version kein Hardware-Support für 16-Bit 
Typen bestand (hatte ARM übrigens anfangs auch nicht).

Typen wie uint16_t wären darin nicht existent, hätte es stdint.h damals 
überhaupt schon gegeben. Das war allerdings noch in einer Ära, in der 
TCP/IP Networking keine Grundvoraussetzung war. Umgang mit dessen 
Headern hätte etwas anders ausgesehen.

Die vorhin erwähnten ILP64/SILP64 Plattformen werden ein ähnliches 
Problem haben. Bei ILP64 fehlt ein Basisdatentyp aus dem C Standard für 
entweder 8, 16 oder 32 Bits und bei SILP64 fehlen gleich zwei davon.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Rolf M. schrieb:
> Denn so wie aussagekräftige Variablennamen sind auch
> aussagekräftige Typnamen förderlich für ein leichteres Verständnis des
> Codes.

Genau so siehts aus, das bringt es auf den Punkt.

Es ist in erster Linie der historisch verankerten Typschwäche von C und 
Konsorten zu verdanken (und der dadurch begünstigten jahrzehntelang 
eingerissenen Praxis der Typschlamperei) daß überhaupt der (eigentlich 
absurde) Wunsch auftaucht man müsse alles mit generischen Basistypen 
erschlagen können müssen.

Man stelle sich mal eine Programmiersprache vor in der man vom Compiler 
gezwungen würde als erstes seine Datentypen zu definieren bevor man 
sie überhaupt verwenden kann, bei der sich der Compiler weigert das 
folgende zu übersetzen:
1
double velo;         // Geschwindigkeit in m/s
2
double boost_accel;  // Beschleunigung in inch/(s^2)
3
double boost_time;   // Zeit in millisekunden
4
5
double velo2() {
6
  return velo + boost_accel * boost_time;
7
}
8
9
// Error: cannot use generic type (double) as a function result
10
// Error: cannot use generic type (double) in a calculation

Stattdessen aber:
1
// keiner der folgenden Typen wäre Zuweisungskompatibel,
2
// obwohl sie alle auf dem selben Basistyp basieren würden:
3
4
typedef velo_metric_t                  double;
5
typedef accel_metric_t                 double;
6
typedef accel_inch_sec_t               double;
7
typedef time_millisec_t                double;
8
typedef time_metric_t                  double;
9
10
// und natürlich sind irgendwo auch alle Operatoren und darauf 
11
// basierend dann alle Umrechnungen definiert damit man überhaupt
12
// irgendwas rechnen kann, das könnte für gängige Sachen wie die
13
// Einheiten da oben ein komplett fertiger Header sein
14
15
// und dann endlich:
16
17
velo_metric_t                velo;
18
accel_inch_sec_t             boost_accel;
19
time_millisec_t              boost_time;
20
21
velo_metric_t blub() {
22
  return velo + to_metric_accel(boost_accel) * to_metric_time(boost_time);
23
}

und jede falsche Zuweisung (meter + inch) würde einem dann schon zur 
Compilezeit sofort um die Ohren fliegen. Milliarden Dollars und Euros 
würde sowas jedes Jahr sparen, ganz zu schweigen von geretteten 
Menschenleben.

So eine extrem starke Typisierung könnte man auch in dynamisch getypten 
Sprachen gut unterbringen. Man stelle sich eine dahin gehend "gepimpte" 
Version von PHP vor bei der $_GET und $_POST vom Typ "string_insecure" 
sind und Datenbankfunktionen aber nur Strings vom typ "string_sql" 
akzeptieren, diese nicht zuweisungskompatibel wären und absolute kein 
Weg an Umwandlungsfunktionen vorbeiführen würde, vorbei wären die Zeiten 
von sql-injection und ähnlichen Spielchen;

von (prx) A. K. (prx)


Lesenswert?

Bernd K. schrieb:
> Man stelle sich mal eine Programmiersprache vor in der man vom Compiler
> gezwungen würde als erstes seine Datentypen zu definieren bevor man
> sie überhaupt verwenden kann, bei der sich der Compiler weigert das
> folgende zu übersetzen:

PL/I und Ada sind nicht weit weg davon. Beide Sprachen haben zwar einen 
implementierungsabhängigen Basisdatentyp für Integers, erlauben darüber 
hinaus aber eine explizite Spezifikation des geforderten Wertebereichs 
oder der Bits.

In Ada kann man zudem bei von einem bestehenden Typ abgeleiteten 
Typdefinitionen den neuen Typ inkompatibel machen. Dann kann man in 
Berechnungen verschiedene Integer-Typen nicht miteinander mischen.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Weil es darin nur Hardware-Support für 8-Bit Bytes und Maschinenworte gab,
> nicht aber für Halbworte, somit in der 32-Bit Version kein Hardware-Support
> für 16-Bit Typen bestand (hatte ARM übrigens anfangs auch nicht).

Ist das bei ARM heute anders? Es gibt soweit ich weiß zwar zumindest 
Befehle für 8- und 16-Bit-Speicherzugriffe, aber alle Rechenoperationen 
werden grundsätzlich in 32 Bit durchgeführt.

A. K. schrieb:
> Die vorhin erwähnten ILP64/SILP64 Plattformen werden ein ähnliches
> Problem haben. Bei ILP64 fehlt ein Basisdatentyp aus dem C Standard für
> entweder 8, 16 oder 32 Bits und bei SILP64 fehlen gleich zwei davon.

Ja, deshalb ist wohl ein 32-Bit-int, obwohl nicht ganz ideal passend - 
heute das am weitesten verbreitete auf 64-Bit-Plattformen.
Nicht ganz ideal, weil int laut ISO-C eigentlich der native Typ der 
Zielplattform sein sollte (allerdings nicht zwingend muss).
Aber wozu macht man short 64 Bit breit? Oder gibt es tatsächlich 
Plattformen, die zwar mit 8 und 64 Bit direkt arbeiten können, nicht 
aber mit 16 oder 32 Bit?

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Ist das bei ARM heute anders? Es gibt soweit ich weiß zwar zumindest
> Befehle für 8- und 16-Bit-Speicherzugriffe, aber alle Rechenoperationen
> werden grundsätzlich in 32 Bit durchgeführt.
1
short x;
2
3
int f(void)
4
{
5
        return x;
6
}
gibt bis ARM7 (ARMv3) noch
1
        ldrb    r2, [r3]        @ zero_extendqisi2
2
        ldrb    r0, [r3, #1]    @ zero_extendqisi2
3
        mov     r0, r0, asl #24
4
        orr     r0, r2, r0, asr #16
und erst ab ARM7TMDI (ARMv4)
1
        ldrsh   r0, [r3]

Davor fehlten schlicht die LD/ST Befehle für 16 Bits. Genau wie bei 
meinen Transputern in der 32-Bit Ausführung. Der abweichenden Codierung 
der 16 Bit LD/ST Befehle sieht man dies auch an.

> Oder gibt es tatsächlich
> Plattformen, die zwar mit 8 und 64 Bit direkt arbeiten können, nicht
> aber mit 16 oder 32 Bit?

Offensichtlich gab es die noch in jener Zeit, in der C schon öfter 
genutzt wurde. Sind eben reine Rechenknechte:
http://docs.cray.com/books/004-2179-001/html-004-2179-001/rvc5mrwh.html#QEARLRWH

Davor wars ohnehin nicht selten. Die CDC6600/7600 hatten dank analoger 
Zielsetzung nur 60-Bit Wortverarbeitung für Daten. Nicht einmal einzelne 
Zeichen waren vorgesehen. Weshalb es dann auch egal war, ob man 10 Stück 
zu 6 Bits reinpackte, oder 8 Bits für Zeichen nutzte, und Betriebsysteme 
auf CDC7600 beides gleichzeitig unterstützten.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Davor fehlten schlicht die LD/ST Befehle für 16 Bits.

Ja, wie gesagt ist mir das für die Speicherzugriffe durchaus bekannt. 
Aber Berechnungen werden trotzdem immer in 32 Bit durchgeführt, oder 
nicht? Zumindest auf dem ARM7TDMI, dem einzigen, auf dem ich bisher in 
Assembler programmiert habe.
Wenn man also zwei 16-Bit-Variablen addiert, muss das Ergebnis direkt 
danach in den Speicher geschrieben oder die obere Hälfte ggf. per 
logischer Operation weggeschnitten werden.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Aber Berechnungen werden trotzdem immer in 32 Bit durchgeführt, oder
> nicht?

Klar. Aber das ist für die Unterstützung von Datentypen durch einen 
Compiler nicht relevant, weil das ohnehin ungefähr der Rechenvorschrift 
von C entspricht. Fehlende Lade/Speicheroperationen sind hingegen nur 
aufwändig zu ersetzen. Weshalb es da eine Abwägung ist, ob man trotzdem 
16-Bit Typen implementiert, oder es bleiben lässt.

> Wenn man also zwei 16-Bit-Variablen addiert, muss das Ergebnis direkt
> danach in den Speicher geschrieben oder die obere Hälfte ggf. per
> logischer Operation weggeschnitten werden.

Das hängt nicht von der Operation selbst ab, sondern von der 
Folgeoperation. Du kannst auch 1 Mio 16 Bit Werte addieren ohne den 
Inhalt anpassen zu müssen. Aber wenn du das Ergebnis vergleichen willst, 
dann musst du maskieren oder das Vorzeichen erweitern. Bei einigen 
Bitoperationen kann ggf. auch das entfallen.

Für einen Compiler kann es in diesem Zusammenhang interessant sein, den 
Zustand der eigentlich nicht relevanten Bits zu tracken. Nach ADD sind 
sie unbrauchbar, sofern der Compiler das nicht durch Kenntnis des 
auftretenden Wertebereichs ausschliessen kann, nach OR jedoch nicht 
unbedingt.

Mindestens GCC 4.7 scheint in ähnlichem Zusammenhang bei amd64 Schwächen 
zu haben, denn die implizite zero extension aller 32 Bit Operationen 
wird nicht konsequent genutzt.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Rolf M. schrieb:
>> Aber Berechnungen werden trotzdem immer in 32 Bit durchgeführt, oder
>> nicht?
>
> Klar. Aber das ist für die Unterstützung von Datentypen durch einen
> Compiler nicht relevant, weil das ohnehin ungefähr der Rechenvorschrift
> von C entspricht.

Aber nur dann, wenn ausschließlich temporäre Zwischenwerte in Registern 
gehalten werden und Variablen grundsätzlich im Speicher stehen.
Bei einem
1
uint16_t x;
2
//...
3
x + = 5;
4
x >>= 3;
erwarte ich eigentlich nicht, dass er nach der Addition das Ergebnis in 
den Speicher schreibt und dann sofort wieder von dort liest, sondern das 
komplett in Registern macht. Dann muss nach der Addition aber die obere 
Hälfte des Registers explizit gelöscht werden.
Vor allem im Zusammenhang mit Optimierung stelle ich es mir nicht so 
einfach vor, das so umzusetzen, dass in jeder Situation das richtige 
rauskommt, ohne dabei unnötige Operationen einzubauen.

> Fehlende Lade/Speicheroperationen sind hingegen nur aufwändig zu
> ersetzen. Weshalb es da eine Abwägung ist, ob man trotzdem 16-Bit Typen
> implementiert, oder es bleiben lässt.

Ja, vor allem, wenn man sieht, dass die meisten wohl kleinere Typen 
hauptsächlich verwenden, weil sie der Meinung sind, damit etwas 
einsparen zu können.

>> Wenn man also zwei 16-Bit-Variablen addiert, muss das Ergebnis direkt
>> danach in den Speicher geschrieben oder die obere Hälfte ggf. per
>> logischer Operation weggeschnitten werden.
>
> Das hängt nicht von der Operation selbst ab, sondern von der
> Folgeoperation.

Das ist klar. Deshalb der kleine Zusatz "ggf". Gemeint war: In dem Fall, 
in dem das für die nächste Operation wichtig ist. Wobei, wie du ja 
selbst hier feststellst, eigentlich beide Operationen wichtig für die 
Entscheidung sind:

> Für einen Compiler kann es in diesem Zusammenhang interessant sein, den
> Zustand der eigentlich nicht relevanten Bits zu tracken. Nach ADD sind
> sie unbrauchbar, sofern der Compiler das nicht durch Kenntnis des
> auftretenden Wertebereichs ausschliessen kann, nach OR jedoch nicht
> unbedingt.

Spannendes Thema, und doch bin ich ganz froh, keinen Compiler für so 
eine Plattform (oder irgendeine andere) schreiben zu müssen.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Vor allem im Zusammenhang mit Optimierung stelle ich es mir nicht so
> einfach vor, das so umzusetzen, dass in jeder Situation das richtige
> rauskommt, ohne dabei unnötige Operationen einzubauen.

Nicht nur das. Bei Maschinen mit Teilwortoperationen kommt noch ein 
nettes Detail hinzu: was die reale Maschine mit dem Restwort anstellt. 
Also ob die den gesamten Inhalt durch die ALU schiebt, auch wenn nur ein 
Teil davon verändert wird, oder nur den relevanten Teil des Registers. 
Beides kann je nach Befehlsfolge zur unbeabsichtigten Abhängigkeit von 
vorherigen Operationen führen und die ganze schöne schnelle out-of-order 
Welt zur Schnecke machen.

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

Bernd K. schrieb:
> und jede falsche Zuweisung (meter + inch) würde einem dann schon zur
> Compilezeit sofort um die Ohren fliegen.

Und der Ober könnte nicht, wie wir früher immer gesagt haben, das Datum 
noch auf die Rechnung draufaddieren.

Ich kenne aber auch wirkliche Fälle aus dem Investmentbanking, wo munter 
Australische und Amerkanische Dollar direkt zu Hongkong Dollar addiert 
wurden und kein System Alarm geschriehen hat. Und das Ganze mit 
Sprachen, die in ihrer Abstraktionshöhe weit über C liegen. Da es aber 
üblich zu sein scheint, wie man auch hier vielfach liest, etwaige 
Probleme in Richtung void wegzucasten, ist das nicht verwunderlich. Viel 
wichtiger als zuverlässig richtige Ergebnisse scheinen ja 
Maschinenzyklen oder Speicherplatz zu sein.

MfG Klaus

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.