Forum: Mikrocontroller und Digitale Elektronik unions structs bildfields


von unions structs bildfields (Gast)


Lesenswert?

Hallo,

Ich hätte gern eine union bestehend aus einem byte array und einer 
struct. Die Struct befülle ich zunächst mit Daten und bei versenden oder 
emfang / auswerten möchte ich die daten im bytearray mithilfe der struct 
auseinander klammüsern.

Was ist nun mein Problem?
Ich habe irgendwie im hinterkopf, dass es irgendwie compiler abhängig 
sein könnte wie die bit reihenfolge, das alignment der structur 
gespeichert wird. Das könnte bei der "wandlung" von struct zu byte array 
problematisch werden.. vll doch nur ein bytearray nehmen und sich alles 
zusammen shiften?

Beitrag #6385397 wurde von einem Moderator gelöscht.
Beitrag #6385402 wurde von einem Moderator gelöscht.
Beitrag #6385408 wurde von einem Moderator gelöscht.
von Random .. (thorstendb) Benutzerseite


Lesenswert?

Die Bits stapelt der Compiler so zusammen, wie sie eintreffen. Wichtig 
ist nur, das ganze PACKED zu machen, damit dir da kein Padding in die 
Quere kommt. Und auf LE/BE achten, falls notwendig, da hilft auf dem 
Cortex-M der REVSH.

Beitrag #6385415 wurde von einem Moderator gelöscht.
von unions structs bildfields (Gast)


Lesenswert?

Hallo Random,

Die Strukturen sind immer vielfache von 4bytes. Wenn das der Fall ist, 
dann kann ich doch sicher immer auf das "pack" verzichten. Was meinst du 
mit LE/BE?

von Jim M. (turboj)


Lesenswert?

Random .. schrieb:
> Und auf LE/BE achten, falls notwendig, da hilft auf dem
> Cortex-M der REVSH.

OP schrieb nix von einem Cortex-M.

Und ich habe bei 8-Bit Compilern schon gesehen das die Hersteller sich 
nicht über Big oder Little Endian auf einer Plattform einig waren.

OP hätte schreiben sollen wo der Code überall laufen muss, davon hängt 
ab wie aufwändig man die (De-) Serialisierung macht.

von unions structs bildfields (Gast)


Lesenswert?

>> OP schrieb nix von einem Cortex-M.
>> OP hätte schreiben sollen wo der Code überall laufen muss, davon hängt
>> ab wie aufwändig man die (De-) Serialisierung macht.
Ich rede zunächste von einem atmega :)

von unions structs bildfields (Gast)


Lesenswert?

als compiler nutze ich avr-gcc

Beitrag #6385461 wurde von einem Moderator gelöscht.
Beitrag #6385466 wurde von einem Moderator gelöscht.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

unions structs bildfields schrieb:
> Ich habe irgendwie im hinterkopf, dass es irgendwie compiler abhängig
> sein könnte wie die bit reihenfolge, das alignment der structur
> gespeichert wird.

Korrekt. In C++ ist es sogar komplett verboten. Siehe Serialisierung 
und meine Bibliothek welche dieses Problem löst: 
https://github.com/Erlkoenig90/uSer

Beitrag #6385500 wurde von einem Moderator gelöscht.
Beitrag #6385518 wurde von einem Moderator gelöscht.
von Frickel (Gast)


Lesenswert?

Um mal über den Tellerrand zu schauen: Andere Sprachen als C/CPP haben 
das besser, oder zumindest anders oder noch besser, eindeutiger gelöst 
um von vorn herein portabel zu sein. Und das sogar Compiler unabhängig 
;)

Beitrag "[Ada] Record Representation Clauses - Wert eines "Records" in einem Zug füllen?"

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Frickel schrieb:
> Um mal über den Tellerrand zu schauen

Stimmt, aber das Hauptproblem von C und C++ ist hier, dass Fehler dieser 
Art noch sehr oft falsch beigebracht werden, auch von Büchern und 
Kursen. Allein die Tatsache dass hier im Forum bei solchen Fragen immer 
sofort der Begriff "union" fällt spricht Bände...

von A. S. (Gast)


Lesenswert?

Also
Keine union: stattdessen memcpy, byte-ptr oder einzeln aufdröseln

Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:
die Optionen und Eigenschaften raussuchen und konfigurieren
Per compiletime asserts absichern.

von Niklas Gürtler (Gast)


Lesenswert?

A. S. schrieb:
> Keine union: stattdessen memcpy, byte-ptr oder einzeln aufdröseln

memcpy und Pointer sind genau so Plattform/Compiler abhängig.

A. S. schrieb:
> Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:
> die Optionen und Eigenschaften raussuchen und konfigurieren

Klingt ziemlich aufwendig, das für die Hunderte an Optionen und 
Plattformen zu prüfen. Ich bevorzuge es so zu machen, wie der 
Sprachstandard die Funktion garantiert, nämlich per bitweisen 
Operationen. Ist letztlich auch nicht mehr Code als die Byte-Reihenfolge 
nach einem memcpy zu korrigieren, wenn sie nicht passt.

von Olaf (Gast)


Lesenswert?

Kannst du so machen:

struct DataTypeA
{
  uint16_t Data01; //  2
  uint16_t Data02; //  4
  uint16_t Data03; //  6
  uint16_t Data04; //  8
  uint32_t Data05; // 12
  uint32_t Data06; // 16
  uint32_t Data07; // 20
  double   Data08; // 28
  double   Data09; // 36
  double   Data10; // 44
  uint8_t  Data11; // 45
  uint8_t  Data12; // 46
  uint8_t  Data13; // 47
  uint8_t  Data14; // 48
} _attribute_ ((packed));

union DataUnionA
{
  struct  DataTypeA DataA;
  uint8_t           Byte[48];
};

Olaf

von Niklas Gürtler (Gast)


Lesenswert?

Olaf schrieb:
> Kannst du so machen

Ist immer noch Implementation defined und somit unportabel.

Niklas G. schrieb:
> Allein die Tatsache dass hier im Forum bei solchen Fragen immer sofort
> der Begriff "union" fällt spricht Bände...

von soviet union (Gast)


Lesenswert?

A. S. schrieb:
> Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:

warum ist der Durchlauf des ganzen struct-Speicherblockes per 
char-pointer nicht allgemengültig und vom Compiler abhängig?

von mh (Gast)


Lesenswert?

soviet union schrieb:
> A. S. schrieb:
>> Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:
>
> warum ist der Durchlauf des ganzen struct-Speicherblockes per
> char-pointer nicht allgemengültig und vom Compiler abhängig?

Kurz: Weil der Standard es nicht vorschreibt.
Etwas länger: Der Standerd erlaubt den Zugriff auf die Bytes über einen 
Char-Pointer, schreibt aber nicht vor was in den Bytes steht, oder 
wieviele es davon gibt.

von fields in soviet union (Gast)


Lesenswert?

Olaf schrieb:
> uint8_t  Data12; // 46
>   uint8_t  Data13; // 47
>   uint8_t  Data14; // 48

Ok, dann ist schon das hier UB oder wie? Wenn char > 8 Bytes ist?

Welchen Sinn hat dann uint8_t-Typ? Da verlässt man sich doch auch auf 8 
bits.

Nach der Logik sollte dann uint8_t ein Synonym für uint_least8_t sein. 
Und kein typedef für unsigned char.

von fields in soviet union (Gast)


Lesenswert?

fields in soviet union schrieb:
> Wenn char > 8 Bytes ist?

Bits

von soviet union (Gast)


Lesenswert?

So, ich habe mal "gegoogelt".

Die Datentypen uint8_t / int8_t kann es auf einer Platform zwar geben, 
muss es aber nicht. Wenn es sie gibt, dann ist sizeof(char) == 1 Byte.

Tja, damit ist jeder Code, der uint8_t benutzt "implemantation defined". 
Und nicht nur irgendwelche Zeigerzugriffe. Das Problem entsteht also 
viel früher.

Es ist also nicht sehr sinnvoll  uint8_t nutzen und beim char*-Zeiger 
den Zeigefinger erheben.

So sehe ich das.

von A. S. (Gast)


Lesenswert?

soviet union schrieb:
> Tja, damit ist jeder Code, der uint8_t benutzt "implemantation defined".
> Und nicht nur irgendwelche Zeigerzugriffe. Das Problem entsteht also
> viel früher.
> Es ist also nicht sehr sinnvoll  uint8_t nutzen und beim char*-Zeiger
> den Zeigefinger erheben.
> So sehe ich das.

Da hast Du mh und mich falsch verstanden: char oder byte-ptr sind immer 
OK, um eine Struktur zu übertragen. Keine Einschränkung. Man muss 
Compiler und Plattform prüfen, weil:

mh schrieb:
> Der Standerd erlaubt den Zugriff auf die Bytes über einen Char-Pointer,
> schreibt aber nicht vor was in den Bytes steht, oder wieviele es davon
> gibt
Konkret: little oder big endian, padding Bytes, bitstrukturen, 2er 
Komplement, Repräsentation von floats, 8 oder 9 Bit pro char/Byte,

Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die 
komplette Struktur in Bytes zu zerlegen.

von Olaf (Gast)


Lesenswert?

> Ist immer noch Implementation defined und somit unportabel.

Klar, ist es muss. Muss man pruefen. Man koennte Pech haben.

In aller Regel klappt es aber. Zumal ich in den Datenbloecken die ich 
damit verschicke auch eine CRC32 drin habe.

Im Vergleich zu den inkompetenten Murks den ich hier oft im 
Hardwarebereich sehe ich das euer geringstes Problem, glaubt mir. .-)


Olaf

von Niklas Gürtler (Gast)


Lesenswert?

Olaf schrieb:
> In aller Regel klappt es aber

Bei wie viel Prozent der Plattformen reicht es? Zwischen AVR und ARM ist 
es schon nicht übertragbar. Und das "packed" Attribut ist Gift für die 
Performance auf 32 bit Plattformen.

Olaf schrieb:
> Zumal ich in den Datenbloecken die ich damit verschicke auch eine CRC32
> drin habe.

Die bringt überhaupt nichts wenn sie über die einzelnen Bytes berechnet 
ist.

Olaf schrieb:
> Im Vergleich zu den inkompetenten Murks den ich hier oft im
> Hardwarebereich sehe ich das euer geringstes Problem, glaubt mir. .-)

Und das ist ein Argument, es nicht richtig zu machen, obwohl es doch so 
einfach wäre, und partout an "union" festzuhalten?

von Rolf M. (rmagnus)


Lesenswert?

Niklas Gürtler schrieb:
> A. S. schrieb:
>> Keine union: stattdessen memcpy, byte-ptr oder einzeln aufdröseln
>
> memcpy und Pointer sind genau so Plattform/Compiler abhängig.

Aber es ist weniger umständlich und missbraucht nicht ein Sprachfeature, 
das dafür eigentlich gar nicht gedacht war.

soviet union schrieb:
> So, ich habe mal "gegoogelt".
>
> Die Datentypen uint8_t / int8_t kann es auf einer Platform zwar geben,
> muss es aber nicht. Wenn es sie gibt, dann ist sizeof(char) == 1 Byte.

sizeof(char) ist immer 1 Byte, egal ob es uint8_t gibt oder nicht. 
Ansonsten soweit richtig.

> Tja, damit ist jeder Code, der uint8_t benutzt "implemantation defined".

Wenn man davon ausgeht, Datentypen mit einer ganz bestimmten Größe 
vorzufinden, ist das immer implementation-defined.

> Es ist also nicht sehr sinnvoll  uint8_t nutzen und beim char*-Zeiger
> den Zeigefinger erheben.

Es gibt da schon einen Unterschied. char muss nicht exakt 8 Bit breit 
sein, uint8_t aber schon. Wenn ich also auf einer Plattform, die keinen 
8-Bit-Typen kennt, char verwende und dabei voraussetze, dass der exakt 8 
Bit breit ist, kommt dabei ein Programm raus, das Blödsinn macht. Nehme 
ich uint8_t, dann bricht der Compiler mit Fehler ab, da der benötigte 
Typ nicht existiert. Außerdem dokumentiert es den Code besser, wenn 
durch uint8_t klar gemacht wird, dass hier ganz speziell ein exakt 8 Bit 
breiter Datentyp gefordert ist.

A. S. schrieb:
> Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die
> komplette Struktur in Bytes zu zerlegen.

Allerdings nur per memcpy, nicht per einfachem Pointer-cast.

Niklas Gürtler schrieb:
> Olaf schrieb:
>> In aller Regel klappt es aber
>
> Bei wie viel Prozent der Plattformen reicht es? Zwischen AVR und ARM ist
> es schon nicht übertragbar. Und das "packed" Attribut ist Gift für die
> Performance auf 32 bit Plattformen.

Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem 
Alignment gar nicht unterstützt wird und das Programm z.B. mit einer 
Hardware-Exception abgebrochen wird.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem
> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer
> Hardware-Exception abgebrochen wird.

Dafür gibt es im GCC das "-munaligned-access" -Flag - ist es gesetzt, 
wird angenommen die Hardware kann solche unaligned Zugriffe, und wenn 
sie es doch nicht kann, gibt es eine Exception. Ist das Flag nicht 
gesetzt, werden die Zugriffe über einzelne Byte-Zugriffe umgesetzt, die 
dann besonders langsam sind. So oder so ist "packed" langsam und keine 
echte Lösung.

von Vincent H. (vinci)


Lesenswert?

Niklas G. schrieb:
> Rolf M. schrieb:
>> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem
>> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer
>> Hardware-Exception abgebrochen wird.
>
> Dafür gibt es im GCC das "-munaligned-access" -Flag - ist es gesetzt,
> wird angenommen die Hardware kann solche unaligned Zugriffe, und wenn
> sie es doch nicht kann, gibt es eine Exception. Ist das Flag /nicht/
> gesetzt, werden die Zugriffe über einzelne Byte-Zugriffe umgesetzt, die
> dann besonders langsam sind. So oder so ist "packed" langsam und keine
> echte Lösung.

Offtopic:
An jener Stelle sei erwähnt dass dieses Flag automatisch gesetzt wird 
wenn die entsprechende Architektur übergeben wird, sprich etwa 
-march=armv7e-m. Man muss sich nicht selbst "händisch" darum kümmern ob 
die Architektur unaligned Zugriffe unterstützt oder nicht.

von Olaf (Gast)


Lesenswert?

> Bei wie viel Prozent der Plattformen reicht es?

Hab ich bisher zwischen diversen ARMs, SH2, 68000, PC(Qt), 68332 und 
M16C erfolgreich genutzt. Zu AVR kann ich nichts sagen weil ich von dem 
Murks schon seit 20Jahren Weg bin.

> Die bringt überhaupt nichts wenn sie über die einzelnen Bytes berechnet
> ist.

Und du wirst gleich sterben weil ich gerade die Eingebung hatte das du 
nur von Pizza und Cola lebst. Wieso definierst du etwas dummes und 
leitest daraus daraus eine Voraussage fuer deine Umwelt ab?

> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem
> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer
> Hardware-Exception abgebrochen wird.

Hier noch nicht passiert. Aber ist ja super. Merkt man dann ja gleich 
beim allerersten Test oder?

Wie hier ja ausreichend dargelegt, es mag nicht perfekt sein, aber in 
>90% der Faelle kein Problem. Man muss vielleicht also mal sein Hirn 
einschalten. Ansonsten ist es euer geringstes Problem. Einfach mal 
ausprobieren...

Olaf

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Olaf schrieb:
> Hab ich bisher zwischen diversen ARMs, SH2, 68000, PC(Qt), 68332 und
> M16C erfolgreich genutzt.

Hast du auch geprüft wie viel langsamer die "packed" Zugriffe sind?

Olaf schrieb:
> Wieso definierst du etwas dummes

Wo habe ich eine Definition gegeben?

Olaf schrieb:
> Merkt man dann ja gleich
> beim allerersten Test oder?

Nö. Bei manchen Plattformen (Cortex-M0?) passiert gar nichts, es wird 
nur Murks gelesen. Du merkst dann irgendwann dass du falsche Daten hast.

Olaf schrieb:
> Man muss vielleicht also mal sein Hirn
> einschalten. Ansonsten ist es euer geringstes Problem. Einfach mal
> ausprobieren...

Auch ein Problem von C - es erzeugt eine Art kognitive Dissonanz. In C 
gibt es eine Reihe an Dingen wie "union", die man zweckentfremden kann 
und die dann den Anschein geben, zu funktionieren. Das installiert sich 
dann im Gehirn als "SO macht man das", auch wenn es falsch ist. Wenn man 
dann darauf hingewiesen wird, dass es problematisch ist, und dass die 
korrekte Lösung eigentlich ganz einfach wäre, kommen Beleidigungen und 
"in 90% der Fälle funktioniert es" um auf jeden Fall die alt-bekannten 
falschen Wege zu rechtfertigen.

von Rolf M. (rmagnus)


Lesenswert?

Olaf schrieb:
>> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem
>> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer
>> Hardware-Exception abgebrochen wird.
>
> Hier noch nicht passiert. Aber ist ja super. Merkt man dann ja gleich
> beim allerersten Test oder?

Ja, sofern tatsächlich eine Exception kommt und nicht wie z.B. oben von 
Vincent erklärt der Compiler jeden Zugriff im Hintergrund automatisch 
über Zusammenstückelung einzelner Bytes umsetzt.

von Olaf (Gast)


Lesenswert?

> Hast du auch geprüft wie viel langsamer die "packed" Zugriffe sind?

Noe, hat mich nicht interessiert. Ich hab nur noch Anwendungen wo der 
Controller 10-1000x schneller ist als man es braucht und lasse ihn 
meistens nur noch mit wenigen Mhz laufen. Und hey, mit sowas sollen 
Daten uebertragen werden. Wenige Bytes pro Minute. Du machst dir Sorgen 
um Softwareeffizienz? Schau dir mal durchschnittliche Handy oder 
PC-Software an. Da gibt es Handlungsbedarf!

> Nö. Bei manchen Plattformen (Cortex-M0?) passiert gar nichts, es wird
> nur Murks gelesen. Du merkst dann irgendwann dass du falsche Daten hast.

Ach? Du merkst das also dann gleich beim allerersten Test deine Software 
oder? Ich meine du testest doch eine Funktion wenn du sie schreibst? Du 
merkst doch sicher  an der Pruefsumme wenn falsche Daten kommen? Du 
staunst doch bestimmt wenn der andere Mikrocontroller der die Daten 
empfaengt nicht das macht was er soll?

> Wenn man dann darauf hingewiesen wird, dass es problematisch ist,

Du wiederholst dich...

Olaf

von mh (Gast)


Lesenswert?

Rolf M. schrieb:
> A. S. schrieb:
>> Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die
>> komplette Struktur in Bytes zu zerlegen.
>
> Allerdings nur per memcpy, nicht per einfachem Pointer-cast.

Mit einem Char-Pointer auf die Bytes der Struktur zugreifen ist erlaubt.

von Rolf M. (rmagnus)


Lesenswert?

mh schrieb:
> Rolf M. schrieb:
>> A. S. schrieb:
>>> Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die
>>> komplette Struktur in Bytes zu zerlegen.
>>
>> Allerdings nur per memcpy, nicht per einfachem Pointer-cast.
>
> Mit einem Char-Pointer auf die Bytes der Struktur zugreifen ist erlaubt.

Sie per memcpy in ein Array aus unsigned char kopieren und dann auf 
dieses zugreifen, ist erlaubt. Das wird ausdrücklich genannt:

"Values stored in non-bit-field objects of any other object type consist 
of n×CHAR_BIT bits, where n is the size of an object of that type, in 
bytes.  The value may be copied into an object of type unsigned 
char[n](e.g., by memcpy); the resulting set of bytes is called the 
object representation of the value."

Ich wüsste keine Passage, die es explizit erlaubt, auch direkt auf die 
Bytes des Objekts als Array aus char zuzugreifen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Ich wüsste keine Passage, die es explizit erlaubt, auch direkt auf die
> Bytes des Objekts als Array aus char zuzugreifen.

Die Passage weiß ich gerade nicht, aber es ist definitiv erlaubt. Genau 
das macht memcpy auch, und memcpy ist an sich nichts besonderes, das 
kann man sich auch selbst implementieren über char-Pointer. Die 
C-Library hat nur meistens eine für die Plattform handoptimierte 
Version.

Ist aber egal, denn das Ergebnis ist natürlich auch implementation 
defined.

von mh (Gast)


Lesenswert?

6.6.7 im c11 draft (n1570) geguckt:
1
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
2
— a type compatible with the effective type of the object,
3
— a qualified version of a type compatible with the effective type of the object,
4
— a type that is the signed or unsigned type corresponding to the effective type of the
5
object,
6
— a type that is the signed or unsigned type corresponding to a qualified version of the
7
effective type of the object,
8
— an aggregate or union type that includes one of the aforementioned types among its
9
members (including, recursively, a member of a subaggregate or contained union), or
10
— a character type.

von Blechbieger (Gast)


Lesenswert?

Für Datenübertragung zwischen unterschiedlichen Systemen mit den ganzen 
Little/big endian, Bitfelder und Enum Problemen definiere ich die 
möglichen Nachrichten formal mit Hilfe von ASN.1

https://de.wikipedia.org/wiki/Abstract_Syntax_Notation_One

Für fast jede gebräuchliche Programmiersprache gibt es Codegeneratoren 
um die verschiedenen Encodings zu erzeugen. Ich bevorzuge dabei 
unaligned Packet Encoding Rules (uPER) da das sehr kompakt ist.

Als Generator verwende ich
https://github.com/ttsiodras/asn1scc
von der ESA aber k.A. ob der für AVR geeignet ist, habe den bisher nur 
auf Cortex-A, NIOS und X86-64 benutzt.

Eine Alternative zu ASN.1 sind z.B. Google Protocol Buffer

von Rolf M. (rmagnus)


Lesenswert?

mh schrieb:
> — a character type.

Ok, überredet. :)

von Stefan (Gast)


Lesenswert?

@Niklas
Auf Deiner Serialisierungs-Seite 
(https://www.mikrocontroller.net/articles/Serialisierung) schreibts Du:
  "je nachdem ob die Implementation eine Sign-Extension vornimmt"
Hast Du da weitergehende Informationen? Ich habe nichts zu 
Implementierungsabhängigkeit von Sign-Extensions finden können?


Darüberhinaus ist mir aber auch schon der Begriff "Sign-Extension" nicht 
ganz klar. In Deinem Beispiel steht '(int16_t) -32768'. Warum sollte 
irgendein Compiler da was erweitern, das lässt sich doch mit den 16 Bit 
darstellen:
  10000000 00000000
Oder bedeutet Sign-Extension, dass der Compiler/die Plattform aus 
irgendwelchen Gründen evtl. intern gar keine 16-Bit-Integer kennt und 
intern z.B. immer mit 32 Bit rechnet?

von mh (Gast)


Lesenswert?

Niklas G. schrieb:
> Rolf M. schrieb:
>> Ich wüsste keine Passage, die es explizit erlaubt, auch direkt auf die
>> Bytes des Objekts als Array aus char zuzugreifen.
>
> Die Passage weiß ich gerade nicht, aber es ist definitiv erlaubt. Genau
> das macht memcpy auch, und memcpy ist an sich nichts besonderes, das
> kann man sich auch selbst implementieren über char-Pointer. Die
> C-Library hat nur meistens eine für die Plattform handoptimierte
> Version.
>
> Ist aber egal, denn das Ergebnis ist natürlich auch implementation
> defined.

Ich habe noch einen Nachtrag:
1
5.1.2.1 Freestanding environment
2
[...]
3
Any library facilities available to a freestanding program, other than the minimal set required by clause 4, are implementation-defined.
4
[...]
und
1
4.6
2
[...]
3
A conforming freestanding implementation shall accept any strictly conforming program in which the use of the features specified in the library clause (clause 7) is confined to the contents of the standard headers <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h>, and <stdnoreturn.h>. A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any strictly conforming program.
Alles aus dem c11 draft (n1570)

Man könnte also sagen, ein Programm das memcpy nutzt ist 
implementation-definded, da string.h nicht aufgelistet ist.

von Rolf M. (rmagnus)


Lesenswert?

mh schrieb:
> Man könnte also sagen, ein Programm das memcpy nutzt ist
> implementation-definded, da string.h nicht aufgelistet ist.

Aber nur wenn es sich um eine freestanding implementation handelt. 
Soweit ich weiß, sind avr-gcc und ein Großteil der ARM-Toolchains als 
hosted implementation ausgelegt.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Stefan schrieb:
> Hast Du da weitergehende Informationen? Ich habe nichts zu
> Implementierungsabhängigkeit von Sign-Extensions finden können?

https://stackoverflow.com/a/7636 und im C-Standard¹, S. 85:

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has 
an unsigned type or if E1 has a signed type and a nonnegative value, the 
value of the result is the integral part of the quotient of E1 / 2^E2. 
If E1 has a signed type and a negative value, the resulting value is 
implementation-defined.

Bei manchen Plattformen macht der Compiler halt eine Sign-Extension, und 
bei anderen nicht. Das hängt typischerweise davon ab, was der Prozessor 
am Besten kann.

Stefan schrieb:
> das lässt sich doch mit den 16 Bit
> darstellen:
>   10000000 00000000

Richtig. Und wenn man das als signed Integer in C nach rechts shiftet, 
wird die 1 entweder nur verschoben oder kopiert. Diese Zahl 3 bits 
nach rechts shiften kann als Ergebnis 00010000 00000000 oder auch 
11110000 00000000 haben. Der C-Standard legt das nicht fest.

Stefan schrieb:
> Oder bedeutet Sign-Extension, dass der Compiler/die Plattform aus
> irgendwelchen Gründen evtl. intern gar keine 16-Bit-Integer kennt und
> intern z.B. immer mit 32 Bit rechnet?

Nö, das ist nochmal was anderes.

1: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

von mh (Gast)


Lesenswert?

Rolf M. schrieb:
> Aber nur wenn es sich um eine freestanding implementation handelt.
> Soweit ich weiß, sind avr-gcc und ein Großteil der ARM-Toolchains als
> hosted implementation ausgelegt.
1
5.1.2.2.2 Program execution
2
In a hosted environment, a program may use all the functions, macros, type definitions, and objects described in the library clause (clause 7).

Man kann mit dem avr-gcc alles nutzen, was in "clause 7" steht?

Stefan schrieb:
> In Deinem Beispiel steht '(int16_t) -32768'. Warum sollte
> irgendein Compiler da was erweitern, das lässt sich doch mit den 16 Bit
> darstellen

Auf einer Plattform mit 16bit 2er Komplement int, ist "-32768" ein long. 
Das "-" ist kein Teil des Literals.

von Rolf M. (rmagnus)


Lesenswert?

mh schrieb:
> Rolf M. schrieb:
>> Aber nur wenn es sich um eine freestanding implementation handelt.
>> Soweit ich weiß, sind avr-gcc und ein Großteil der ARM-Toolchains als
>> hosted implementation ausgelegt.
> 5.1.2.2.2 Program execution
> In a hosted environment, a program may use all the functions, macros,
> type definitions, and objects described in the library clause (clause
> 7).
>
> Man kann mit dem avr-gcc alles nutzen, was in "clause 7" steht?

Ob er (bzw. die avr-libc) das vollständig untestützt, kann ich nicht 
sagen. Ich vermute mal, nicht. Aber zumindest wird gcc per Default im 
"hosted"-Modus betrieben. Ohne den würde er keine Optimierung der 
Standard-Funktionen wie eben gerade memcpy machen, da er nicht mehr 
annehmen kann, dass sie sich gemäß Standard verhalten.

von Stefan (Gast)


Lesenswert?

Niklas G. schrieb:
> Richtig. Und wenn man das als signed Integer in C nach rechts shiftet,
> wird die 1 entweder nur verschoben oder kopiert. Diese Zahl 3 bits
> nach rechts shiften kann als Ergebnis 00010000 00000000 oder auch
> 11110000 00000000 haben. Der C-Standard legt das nicht fest.

D.h. die Variante 11110000 00000000 ist mit Sign-Extension (weil die 
Zweierkomplement-Zahl praktisch von links mit Einsen negativ erweitert 
wurde) und 00010000 00000000 ohne, richtig?

Niklas G. schrieb:
> If E1 has a signed type and a negative value, the resulting value is
> implementation-defined.

Ist das für einen Standard nicht etwas dünn? Wo liest man denn da Deine 
beiden o.g. Varianten heraus (Sign-Extension wird ja auch überhaupt 
nicht erwähnt)?
Das ist übrigens auf Seite 95 statt 85 (falls noch jemand anderes 
nachschauen will)


mh schrieb:
> Auf einer Plattform mit 16bit 2er Komplement int, ist "-32768" ein long.
> Das "-" ist kein Teil des Literals.
Habe ich jetzt nicht verstanden. Also ist -32768 nicht 10000000 00000000
als Zweierkomplement?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Stefan schrieb:
> D.h. die Variante 11110000 00000000 ist mit Sign-Extension (weil die
> Zweierkomplement-Zahl praktisch von links mit Einsen negativ erweitert
> wurde) und 00010000 00000000 ohne, richtig?

Ja. Sign Extension bedeutet praktisch dass man das ganz linke Bit (das 
Sign Bit) kopiert.

Stefan schrieb:
> Ist das für einen Standard nicht etwas dünn?

Die C- und C++-Standards sind halt so definiert, dass die Sprache auf 
einer größtmöglichen Anzahl an Plattformen effizient lauffähig ist. 
Daher werden den Compilern viele Freiheiten gelassen. Es wird ja nicht 
einmal gefordert, dass 8-Bit-Bytes zur Verfügung stehen, denn char kann 
auch größer sein. Sprachen mit sehr fixen Regeln wie Java sind auf 
exotischeren Plattformen kaum lauffähig; auf einem DSP der nur 
36bit-Integer kennt müsste Java jede einzelne "short" oder "int" 
Operation aufwendig emulieren. Selbst auf AMD64 passt Java nicht so 
richtig, da Array-Größen & Indices 32bit sind. C und C++ haben da 
hingegen keine Probleme, da wird size_t einfach als 64bit definiert.

Allerdings sind selbst diese Freiheiten nicht immer ausreichend; auf AVR 
oder 8051 lässt sich C auch nicht 100% korrekt abbilden und man braucht 
Krücken wie "PROGMEM" oder "__flash".

Stefan schrieb:
> Wo liest man denn da Deine
> beiden o.g. Varianten heraus (Sign-Extension wird ja auch überhaupt
> nicht erwähnt)?

Die Varianten sind nicht vorgeschrieben. Die Plattform kann irgendwas 
tun. Die genannten Varianten sind nur die m.W. tatsächlich 
existierenden. Das 2er-Komplement ist ja auch nicht vorgegeben, die 
Plattform kann etwas anderes nutzen.

Stefan schrieb:
> Das ist übrigens auf Seite 95 statt 85 (falls noch jemand anderes
> nachschauen will)

Unten auf der Seite steht 85. Es ist nur PDF-Seite 97 (!).

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Stefan schrieb:
> D.h. die Variante 11110000 00000000 ist mit Sign-Extension (weil die
> Zweierkomplement-Zahl praktisch von links mit Einsen negativ erweitert
> wurde) und 00010000 00000000 ohne, richtig?

Ja.

> Niklas G. schrieb:
>> If E1 has a signed type and a negative value, the resulting value is
>> implementation-defined.
>
> Ist das für einen Standard nicht etwas dünn?

Das Problem ist eben, dass nicht jede Hardware das direkt untersützt. 
Der Standard lässt dem Compiler die Wahl, damit der keine unnötig 
ineffizienten Verrenkungen anstellen muss, um ein vom Standard 
vorgeschriebenes Verhalten umzusetzen. So etwas gibt es in C an vielen 
Stellen, damit eine sinnvolle Implementation der Sprache für nahezu jede 
erdenkliche Plattform möglich ist.

> Wo liest man denn da Deine beiden o.g. Varianten heraus (Sign-Extension
> wird ja auch überhaupt nicht erwähnt)?

implementation-defined heißt nur, dass der Compiler es definieren muss. 
Mit und ohne Sign-Extension sind halt in der Praxis die gängigsten 
Varianten. Im Prinzip darf der Compiler es aber machen, wie er will, 
solange das Verhalten für positive Werte korrekt ist und für negative 
Werte reproduzierbar und im Handbuch des Compilers dokumentiert ist.

> mh schrieb:
>> Auf einer Plattform mit 16bit 2er Komplement int, ist "-32768" ein long.
>> Das "-" ist kein Teil des Literals.
> Habe ich jetzt nicht verstanden.

Es wird zuerst nur die 32768 betrachtet. Die passt nicht in einen 
16-Bit-int, also muss ein long draus gemacht werden. Erst danach wird 
der Wert negiert. Da würde er zwar in den int passen, aber es ist schon 
zu spät. Er ist bereits ein long.

von Stefan (Gast)


Lesenswert?

Rolf M. schrieb:
> Es wird zuerst nur die 32768 betrachtet. Die passt nicht in einen
> 16-Bit-int, also muss ein long draus gemacht werden. Erst danach wird
> der Wert negiert. Da würde er zwar in den int passen, aber es ist schon
> zu spät. Er ist bereits ein long.

Also macht der Compiler folgendes (im Folgenden wird immer angenommen, 
der Compiler arbeitet mit der Zweierkomplement-Darstellung):
 1. 32768 wird als 32 Bit behandelt (weil bei 16 Bit ein Überlauf 
aufträte):
    00000000 00000000 10000000 00000000
 2. -32768 wird als 32-Bit-Zweierkomplement geschrieben:
    11111111 11111111 10000000 00000000
 3. Cast mit (int16_t)
    Was bewirkt das jetzt? Werden einfach die höherwertigen 16 Bit 
abgeschnitten? Dann hätte ich ja wieder das gewünschte Ergebnis.


Aber hat das überhaupt was mit dem Thema 'implementation-defined 
Sign-Extension' zu tun?
Angenommen die Zahl wäre -20000 statt -32768:
  '((int16_t) -20000) >> 3)'
Der Audruck '(int16_t) -20000' ergibt (ohne dass irgendwelche Überläufe 
aufteten) als Zweierkomplement
  10110001 11100000
Wenn ich das Zitat aus dem Standard und die Aussagen von Niklas richtig 
deute, tritt das Problem 'implementation-defined Sign-Extension' ja 
jetzt trotzdem auf (obwohl vorher kein Überlauf auftrat), nämlich 
einfach immer bei Bit-Shifting mit Signed-Integer, denn ja nach 
Implementierung kann die Verschiebung um 3 Bits nach rechts 
(erfahrungsgemäß) diese beiden Ergebnisse erzeugen:
  11110110 00111100 (Sign-Extension, von links mit Einsen auffüllen)
  00010110 00111100 (keine Sign-Extension, von links mit Nullen 
auffüllen)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Stefan schrieb:
> Aber hat das überhaupt was mit dem Thema 'implementation-defined

-32768 ist negativ :) Das hatte ich nur als besonders prägnantes 
Beispiel (-1 vs 1) gewählt. Das Problem besteht bei allen negativen 
Zahlen.

Das wiederum hat auch wenig mit dem Thread zu tun, denn die ganze 
Signed-Right-Shift-Problematik war auch nur ein Beispiel für 
implementation defined behaviour.

von Rolf M. (rmagnus)


Lesenswert?

Stefan schrieb:
> Also macht der Compiler folgendes

Ja.

>     Was bewirkt das jetzt? Werden einfach die höherwertigen 16 Bit
> abgeschnitten? Dann hätte ich ja wieder das gewünschte Ergebnis.

Korrekt.

> Aber hat das überhaupt was mit dem Thema 'implementation-defined
> Sign-Extension' zu tun?

Nein.

> Angenommen die Zahl wäre -20000 statt -32768:
>   '((int16_t) -20000) >> 3)'
> Der Audruck '(int16_t) -20000' ergibt (ohne dass irgendwelche Überläufe
> aufteten) als Zweierkomplement
>   10110001 11100000

Hier ist kein Cast nötig, da 20000 in einen int16_t passt.

> Wenn ich das Zitat aus dem Standard und die Aussagen von Niklas richtig
> deute, tritt das Problem 'implementation-defined Sign-Extension' ja
> jetzt trotzdem auf (obwohl vorher kein Überlauf auftrat), nämlich
> einfach immer bei Bit-Shifting mit Signed-Integer, denn ja nach
> Implementierung kann die Verschiebung um 3 Bits nach rechts
> (erfahrungsgemäß) diese beiden Ergebnisse erzeugen:
>   11110110 00111100 (Sign-Extension, von links mit Einsen auffüllen)
>   00010110 00111100 (keine Sign-Extension, von links mit Nullen
> auffüllen)

Ja, richtig.

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.