Forum: Compiler & IDEs Wie sinnvoll ist ein union bei 32Bit Arm CPUs?


von Sven (Gast)


Lesenswert?

Hallo zusammen!

Ich habe folgenden Codeausschnitt gesehen (in der Software eines 16Bit 
Motorrollers):
1
union Chartoword
2
{
3
  WORD w;
4
  BYTE c[2];
5
};

Damit wird wohl ein WORD (16bit) in zwei BYTE (8bit) und umgekehrt 
umgewandelt.

Jetzt stelle ich mir die Frage, ist es wohl sinnvoll solch ein union 
auch auf einem 32Bit Arm Prozessor zu benutzen oder ergeben 
Schiebeoperationen mehr Sinn? Der Arm verarbeitet doch standardmäßig 
32Bit Daten. Ich kann mich an eine Passage in einem Buch (ich glaub das 
hieß "ARM System Developers Guide") erinnern, in der steht "Use signed 
and unsigned int types for local variables [...] This avoids casts and 
uses the ARM's native 32Bit data processing instructions efficiently"

Ich denke an ein union in der Art:
1
union CharToWordToInt
2
{
3
  int i;
4
  WORD w[2];
5
  BYTE c[4];
6
};

von Stefan E. (sternst)


Lesenswert?

Sven schrieb:
> Jetzt stelle ich mir die Frage, ist es wohl sinnvoll solch ein union
> auch auf einem 32Bit Arm Prozessor zu benutzen oder ergeben
> Schiebeoperationen mehr Sinn?

Das musst du mit deinem Gewissen/Geschmack selber vereinbaren.

Zugriff auf die einzelnen Bytes eines größeren Datentyps per:
1) Schiebeoperationen
Portabel (funktioniert unabhängig von der Endianness), aber 
möglicherweise ineffizient (je nach dem, wie gut der Optimizer des 
Compilers ist)
2) Union
Immer effizient, aber nicht portabel (und eigentlich illegal).

von Karl H. (kbuchegg)


Lesenswert?

Sven schrieb:

>
1
> union Chartoword
2
> {
3
>   WORD w;
4
>   BYTE c[2];
5
> };
6
>
>
> Damit wird wohl ein WORD (16bit) in zwei BYTE (8bit) und umgekehrt
> umgewandelt.

umgewandelt ist eigentlich das falsche Wort.
Gewandelt wird da nichts.
Mit der union werden die beiden Member 'übereinandergelegt'. D.h. es 
gibt dann 2 Zugriffspfade auf denselben Speicherbereich. Dieselben 2 
Bytes werden einmal in ihrer Gesamtheit als WORD aufgefasst und ein 
anderes mal als 2 einzelne Bytes.

> Jetzt stelle ich mir die Frage, ist es wohl sinnvoll solch ein union
> auch auf einem 32Bit Arm Prozessor zu benutzen oder ergeben
> Schiebeoperationen mehr Sinn?

Die Sache ist die:
Die andere Möglichkeit wie man auf die Bytes eines WORD zugreifen kann, 
besteht darin, sich einen Pointer umzucasten.
Es gibt 2 Problemkreise mit beiden Methoden (union + pointer casting)
* zum einen sind die beiden Operationen, wenn man es ganz streng nimmt
  vom C Standard her 'undefiniertes Verhalten'.
  Gut, das ist in der Praxis kein Thema, Auf allen bekannten Compilern
  funktionieren beide Methoden so, wie man sich das vorstellt
* man liefert sich dem Byte-Sex aus, also der Reihenfolge was zuerst
  im Speicher liegt: Das Low Byte oder das High Byte
  wenn das kein Problem ist, dann ist es gut
  wenn man allerdings die Zerlegung macht um damit zu einem komplett
  anderen System zu transferieren, dann muss man das berücksichtigen.
  Zudem muss man ganz einfach 'magisch wissen', wie der Byte Sex auf
  seinem eigenen System ist.

All diese Nachteile hat die Methode mit Schieben nicht. Man kann 
dezidiert und völlig systemunabhängig sagen, wie das Low-byte entsteht. 
Das ergibt daher portablen Code.

> 32Bit Daten. Ich kann mich an eine Passage in einem Buch (ich glaub das
> hieß "ARM System Developers Guide") erinnern, in der steht "Use signed
> and unsigned int types for local variables [...] This avoids casts and
> uses the ARM's native 32Bit data processing instructions efficiently"

Die Frage ist, inwiefern dieser Rat in der gegenständlichen Situation 
überhaupt relevant ist. Eine derartige Union kommt normalerweise an 
genau einer Stelle vor: Nämlich dann, wenn es gilt irgendwelche Daten 
über irgendwelche I/O Kanäle auf den Weg zu bringen bzw. von dort zu 
holen und ins restliche System einzuspeisen. An diesen Schnittstellen 
ist die Sache aber überwiegend so, dass der Löwenanteil der Laufzeit 
durch die eigentliche Behandlung der Schnittstellen draufgeht. Man sagt 
die Applikation ist an dieser Stelle I/O-bound (das Gegenteil davon wäre 
CPU-bound) und meint damit genau das: Die Laufzeit wird von der I/O 
Hardware bestimmt. Eine Beschleunigung des Codes auf zb das 10-fache 
beschleunigt die Datenübertragung nicht um das 10-fache.

>
1
> union CharToWordToInt
2
> {
3
>   int i;
4
>   WORD w[2];
5
>   BYTE c[4];
6
> };
7
>

Ja. Kannst du machen. Warum auch nicht.

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


Lesenswert?

Stefan Ernst schrieb:
> 2) Union
> Immer effizient

Quatsch.  Wenn die entsprechende Architektur zum Extrahieren eines
Bytes aus dem 32-bit-Datenwort 8 Schiebeoperationen braucht, dann
braucht sie dafür 8 Schiebeoperationen, völlig unabhängig davon,
ob im C-Code ein ">> 8" steht oder eine union.  Wenn die Hardware
einen Zugriff auf einzelne Bytes eines 32-bit-Wortes gestattet,
dann steht es dem Compiler natürlich frei, das Schieben und Maskieren
in einen direkten Zugriff des Bytes umzusetzen.

Spätestens bei einer 32-bit-Architektur sollte man sich vom Microsoft-
schen WORD als Bezeichnung einer 16-bit-Zahl wohl aber endgültig
verabschieden.  Deren "word" ist, wenn überhaupt, eine 32-bit-Zahl,
die 16-bit-Zahl ist dann bestenfalls ein HWORD (half word).

Weithin sinnvoller ist es, statt dieser schwammigen Prosa gleich die
Bezeichnungen gemäß C99 <stdint.h> zu benutzen.

von Stefan E. (sternst)


Lesenswert?

Jörg Wunsch schrieb:
> Stefan Ernst schrieb:
>> 2) Union
>> Immer effizient
>
> Quatsch.  Wenn die entsprechende Architektur zum Extrahieren eines
> Bytes aus dem 32-bit-Datenwort 8 Schiebeoperationen braucht, dann
> braucht sie dafür 8 Schiebeoperationen, völlig unabhängig davon,
> ob im C-Code ein ">> 8" steht oder eine union.

Und was ist dann Quatsch? Ich schrieb "effizient" und nicht "effizienter 
als überhaupt technisch möglich".

> Wenn die Hardware
> einen Zugriff auf einzelne Bytes eines 32-bit-Wortes gestattet,
> dann steht es dem Compiler natürlich frei, das Schieben und Maskieren
> in einen direkten Zugriff des Bytes umzusetzen.

Ich sagte ja auch nicht, dass "Schieben und Maskieren" immer ineffizient 
wäre, oder immer ineffizienter als die Union.

Ok, ich formuliere meine Aussage oben etwas um, dann wird vielleicht 
deutlicher, was ich meine:
Die Möglichkeit/Gefahr, dass der Compiler Code erzeugt, der 
ineffizienter ist, als eigentlich nötig, ist beim "Schieben und 
Maskieren" größer als bei der Union.

von Sven (Gast)


Lesenswert?

Vielen Dank für die Antworten.

In der Tat geht es um I/O Geschichten. Es werden per CAN Bus Daten 
empfangen, die dann zu 16 Bit daten zusammengesetzt werden und umgekehrt 
werden 16bit ADC Daten per CAN verschickt...

Sprich also, im Prinzip ist es egal, ob ich caste und schiebe oder die 
"Umwandlung" per union mache, da beiedes sowieso schneller ist als die 
I/O Zugriffe.

von (prx) A. K. (prx)


Lesenswert?

Stefan Ernst schrieb:

> 2) Union
> Immer effizient, aber nicht portabel (und eigentlich illegal).

Unions führen i.d.R. zu Speicheroperationen. Auf Maschinen mit schnellem 
Shifter (m.E. alle 32-Bitter) sind Register effizienter als Speicher.

> Die Möglichkeit/Gefahr, dass der Compiler Code erzeugt, der
> ineffizienter ist, als eigentlich nötig, ist beim "Schieben und
> Maskieren" größer als bei der Union.

Ist leider nicht so einfach. Bei High-Performance Architekturen wie den 
meisten x86ern sind Lade/Schreiboperationen unterschiedlicher 
Datenbreite auf die gleichen Daten u.U. ziemlich teuer (zweistellige 
Takte).

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


Lesenswert?

Stefan Ernst schrieb:
> Und was ist dann Quatsch?

Dass eine union immer effizient wäre.  Wenn die Architektur keine
effiziente Auflösung kennt, kann die union es halt auch nur als
Schiebeoperation umsetzen.

Beide können durchaus identischen Code produzieren, Beispiel:
1
#include <stdint.h>
2
3
uint8_t get2ndbyte_shift(uint32_t l)
4
{
5
        return (l & 0x0000ff00ul) >> 8;
6
}
7
8
uint8_t get2ndbyte_union(uint32_t l)
9
{
10
        union {
11
                uint32_t l;
12
                uint8_t b[4];
13
        } u;
14
15
        u.l = l;
16
        return u.b[1];
17
}

Compiliert für einen AVR ergibt:
1
        .text
2
.global get2ndbyte_shift
3
get2ndbyte_shift:
4
        mov r24,r23
5
        ret
6
7
.global get2ndbyte_union
8
get2ndbyte_union:
9
        mov r24,r23
10
        ret

> Die Möglichkeit/Gefahr, dass der Compiler Code erzeugt, der
> ineffizienter ist, als eigentlich nötig, ist beim "Schieben und
> Maskieren" größer als bei der Union.

Dem würde ich unter A. K.s Vorbehalt zustimmen.

von Sebastian Hepp (Gast)


Lesenswert?

> Spätestens bei einer 32-bit-Architektur sollte man sich vom Microsoft-
> schen WORD als Bezeichnung einer 16-bit-Zahl wohl aber endgültig
> verabschieden.  Deren "word" ist, wenn überhaupt, eine 32-bit-Zahl,
> die 16-bit-Zahl ist dann bestenfalls ein HWORD (half word).

Stimmt nicht ganz. Soweit ich das weis, gilt folgendes:
BYTE----8 Bit
WORD---16 Bit
DWORD--32 Bit
QWORD--64 Bit

von Stefan E. (sternst)


Lesenswert?

Jörg Wunsch schrieb:
> Dass eine union immer effizient wäre.  Wenn die Architektur keine
> effiziente Auflösung kennt, kann die union es halt auch nur als
> Schiebeoperation umsetzen.

Also macht sich unsere "Unstimmigkeit" in erster Linie an einer 
unterschiedlichen Interpretation von "Effizienz von Code" fest. ;-)
Während du es eher absolut siehst (wenn die Architektur nicht effizient 
ist, kann auch der Code nicht effizient sein), sehe ich es eher relativ 
(Code ist effizient, wenn er die durch die Architektur vorgegebene 
maximale Effizienz erreicht).

Jörg Wunsch schrieb:
> Beide können durchaus identischen Code produzieren,

Das habe ich ja auch in keiner Weise bestritten. Im Gegenteil, ich würde 
eigentlich bei jedem modernen Compiler erwarten, dass der Code gleich 
ist. Auch im Hinblick auf A.K.s Einwand, denn der Compiler ist bei der 
Union ja nicht dazu gezwungen, daraus einen unaligned Speicherzugriff zu 
machen.


Nichtsdestotrotz war das "immer" natürlich eine Torheit. ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:
> Im Gegenteil, ich würde
> eigentlich bei jedem modernen Compiler erwarten, dass der Code gleich
> ist.

Na dann erwarete mal nicht zu viel von den "modernen" ;-)

Hinschreiben und staunen was rauskommt...

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


Lesenswert?

Sebastian Hepp schrieb:

> Stimmt nicht ganz. Soweit ich das weis, gilt folgendes:
> BYTE----8 Bit
> WORD---16 Bit
> DWORD--32 Bit
> QWORD--64 Bit

Nur in Microsofts Denkweise.  Für alle anderen ist "word" die natürliche
Wortgröße der CPU.  Wenn du dir die Befehle einer RISC-CPU mal ansiehst,
wirst du dort entsprechende Modifier wie .h (für "half word") finden
können.

von (prx) A. K. (prx)


Lesenswert?

Stefan Ernst schrieb:

> ist. Auch im Hinblick auf A.K.s Einwand, denn der Compiler ist bei der
> Union ja nicht dazu gezwungen, daraus einen unaligned Speicherzugriff zu
> machen.

Auf Alignment-Probleme habe ich mich dabei nicht bezogen. Die sind 
vergleichsweise harmlos, maximal ein paar Takte mehr, und hierfür 
ohnehin nicht relevant.

Absolut nicht harmlos ist es jedoch, ein Wort häppchenweise zu schreiben 
und dann als Wort zu lesen. Damit fegst du bei einer out-of-order 
Mikroarchitektur die halbe CPU leer, da der Load dann nicht aus der 
spekulativen Store Queue bedient wird, sondern warten muss, bis alle 
betreffenden Stores nicht mehr spekulativ und endgültig im Cache 
angekommen sind. Das kann zig Takte dauern, da der spekulative Teil 
heutiger CPUs mehr als hundert Befehle fassen kann.

von Stefan E. (sternst)


Lesenswert?

A. K. schrieb:
> Das kann zig Takte dauern, da der spekulative Teil
> heutiger CPUs mehr als hundert Befehle fassen kann.

Und wie groß ist der spekulative Teil beim ARM?

von (prx) A. K. (prx)


Lesenswert?

Stefan Ernst schrieb:

> Und wie groß ist der spekulative Teil beim ARM?

Inwieweit beispielsweise Cortex-Ax Cores von ähnlichen Problemen 
betroffen sein können weiss ich nicht, die kenne ich nicht. Kleinere 
Cores sind es jedenfalls nicht.

Mir ging es auch nur darum, der verallgemeinernden Aussage, der Weg über 
Unions sei hinsichtlich der Effizienz sicherer, etwas entgegen zu 
treten.

Wenn man sich auf Cores mit relativ kurzer nicht nennenswert 
spekulativer Pipeline beschränkt, wie ARM7/9 oder Cortex-Mx, dann kann 
man ziemlich risikofrei beide Wege gehen, der Unterschied wird so gross 
nicht sein. Aufgrund der ARM Architektur habe ich aber den Verdacht, 
dass die Shift-Variante etwas vorne liegen wird.

Wenn man jedoch über den Tellerrand hinaus guckt, dann wird es 
schwierig, eine allgemeine Aussage zu treffen. Shifts können bei kleinen 
8/16-Bit CPUs ohne Barrel-Shifter je nach Compiler katastrophal enden, 
Unions bei den ganz grossen 32/64-Bittern.

von (prx) A. K. (prx)


Lesenswert?

Hab mal kurz in die Doku vom Cortex-A9 reingesehen. Es stehen zwar wenig 
Details drin und nichts zum beschriebenen Problem, aber wird deutlich, 
dass dieser Core einen erheblichen spekulativen Kontext besitzt. Ich 
wäre folglich nicht überrascht, wenn dieser ARM-Core die Union-Technik 
ebenfalls nicht allzu sehr mag.

von Peter D. (peda)


Lesenswert?

Ich benutze immer nur die Schiebeversion. Damit bin ich von Compiler und 
CPU unabhängig. Der Code läuft also überall.

Ich benutze vorzugsweise 8-Bitter und da ist das Schieben um Vielfaches 
von 8Bit effizient programmiert, also kein merkbarer Overhead.


Peter

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:

> Ich benutze vorzugsweise 8-Bitter und da ist das Schieben um Vielfaches
> von 8Bit effizient programmiert, also kein merkbarer Overhead.

Allerdings sollte man anfangs mal kontrollieren, ob der betreffende 
Compiler die entsprechende Optimierung auch wirklich drin hat, 
insbesondere bei 32-Bit Datentypen. Würde ich nicht bei jedem Compiler 
blind drauf wetten.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

@Sven:
Die Weisheit, dass man auf einer 32Bit CPU lokale Variablen ebenfalls
als 32Bit Typen deklariert kommt daher, dass man mit kleineren
Datentypen ohnehin keinen Platz spart (weder im Register noch auf dem
Stack). Die manchmal notwendige Vorzeichenerweiterung/Maskierung bei
kleineren Daten in großen Registern entfällt ebenfalls.

Deine Frage mit der Union bezieht sich aber eher auf die
Zugriffsbreite auf den Speicher.

Sven schrieb:
> Es werden per CAN Bus Daten empfangen, die dann zu 16 Bit daten
> zusammengesetzt werden und umgekehrt werden 16bit ADC Daten per CAN
> verschickt...

Diese Information ist leider etwas dünn geraten. Poste doch mal
etwas mehr Code.

Ziel ist vermutlich, die Anzahl der Speicherzugriffe zu minimieren.
Falls die 16bit Daten mit geeigneter Endianess an geeigneter Stelle im
Speicher liegen, kannst Du Sie als 32Bit Daten lesen und mit den
einzelnen Halbworten... ja was eigentlich?

Oder liest Du die Daten als Bytes (z.B. aus einem Peripherie Register)
und willst sie als größere Bocken in einen Speicher schreiben?

Die ganze Diskussion um spekulative Zugriffe und bestimmte Cores lenkt
von der eigentlichen Thematik ab. Das kann wieder relevant werden,
falls wir den eigentlichen Code zu sehen bekommen. Wobei ich bezweifle,
dass Sven mit einem C-A9 arbeitet...

Gruß
Marcus

von Sven (Gast)


Lesenswert?

Es handelt sich um einen LPC2214 (ARM7tmdi)

Ich versuchs nochmal etwas präziser zu formulieren:
Ich habe in meinem Programm eine 32 Bit bzw. 16 Bit Variable und will 
diese byteweise übertragen. Konkret geht es eben darum einen AD Wandler 
wert (genau genommen den Mittelwert aus mehreren AD Werten) per CAN-Bus 
zu übertragen. Dafür muss ich den Wert halt byteweise per SPI 
Schnittstelle in einen externen CAN-Controller übertragen. Die zweite 
Anwendung ist das auslesen einer Seriennummer aus einem EEPROM. Da lese 
ich 4 Bytes aus und muss diese zur einer 32 Bit Seriennummer 
zusammensetzen.

Hier mal die Funktion die eine 32 Bit Zahl byteweise aus dem EEPROM 
liest:
1
int32_t eeprom_readSingleInt(uint32_t* data, uint16_t address)
2
{
3
  int32_t returnValue = 0;
4
  uint8_t addressAsChar[2] = {0};
5
  uint8_t bytes[4] = {0};
6
7
  addressAsChar[0] = (uint8_t) address;        // Low byte of the address.
8
  addressAsChar[1] = (uint8_t) (address >> 8);    // High byte of the address.
9
10
  returnValue += i2c_start(EEPROM_WRITE_ADDRESS);      // Send START and EEPROM write address.
11
  returnValue += i2c_write(addressAsChar, 2);        // Write EEPROM-internal address.
12
  returnValue += i2c_start(EEPROM_READ_ADDRESS);      // Send repeat START and EEPROM read address.
13
14
15
  returnValue += i2c_read(&bytes[0]);            // Read one byte and increment pointer to data.
16
  returnValue += i2c_read(&bytes[1]);            // Read one byte and increment pointer to data.
17
  returnValue += i2c_read(&bytes[2]);            // Read one byte and increment pointer to data.
18
  returnValue += i2c_readLast(&bytes[3]);          // Read last byte.
19
20
  returnValue += i2c_stop();                // Send STOP.
21
22
23
/*** HIER IST DAS WORUM ES GEHT ***/
24
/*** Die Frage ist, ist hier ein Union sinvoller ***/
25
  *data = bytes[0];
26
  *data += bytes[1] << 8;
27
  *data += bytes[2] << 16;
28
  *data += bytes[3] << 24;
29
30
  if (returnValue != 0)                  // If something went wrong, return error.
31
  {
32
    return EEPROM_READ_ERROR;
33
  }
34
  else                          // Else return success.
35
  {
36
    return SUCCESS;
37
  }
38
}

Beim durchlesen fällt mir auf, dass man ja auch beim umwandeln der 
uint16_t Adresse in zwei uint8_t bytes ein Union hätte verwenden 
können...

von (prx) A. K. (prx)


Lesenswert?

Eine Union ist an dieser Stelle nicht sinnvoller, weil das Protokoll 
dadurch abhängig von der Bytereihenfolge der beteiligen Maschinen wird. 
Selbst wenn alle heute beteiligten Nodes die gleiche Bytereihenfolge 
haben, ist sowas dennoch eine selbstgestellte Falle in die man Jahre 
später mal reintappt.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:

> haben, ist sowas dennoch eine selbstgestellte Falle in die man Jahre
> später mal reintappt.

Und die Erfahrung zeigt, dass es nur eine Frage der Zeit ist, bis man 
reintappt. Sicher ist hingegen, dass es irgendwann so weit ist.

Praktisch jeder 'clevere' Quick-Hack den man irgendwann einmal gemacht 
hat, fällt einem irgendwann auf den Kopf.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Sven schrieb:
> /*** HIER IST DAS WORUM ES GEHT ***/
> /*** Die Frage ist, ist hier ein Union sinvoller ***/
>   *data = bytes[0];
>   *data += bytes[1] << 8;
>   *data += bytes[2] << 16;
>   *data += bytes[3] << 24;

Für diese Anwendung lohnt sich die Überlegung wegen der Effizienz
vermutlich nicht. Das geht dann schon eher in Richtung Geschmack.
Aber man spart durchaus ein paar Speicherzugriffe.

Du kannst die Definition der Union etwas umgestalten (kein array,
du nutzt es sowieso nicht als solches). Damit hast Du direkten
Einfluss auf die Reihenfolge der Bytes. Etwa so (ungetestet):
1
#if defined WORDS_BIGENDIAN /* autoconf */ || defined __BIG_ENDIAN /* armcc */ 
2
    /* don't know what the GCC predefine is. Couldn't find any */
3
typedef union 
4
{
5
    uint32_t w;
6
    struct
7
    {
8
        uint8_t b3;
9
        uint8_t b2;
10
        uint8_t b1;
11
        uint8_t b0;
12
    } b;
13
} uint32_whb_t;
14
#else
15
typedef union 
16
{
17
    uint32_t w;
18
    struct
19
    {
20
        uint8_t b0;
21
        uint8_t b1;
22
        uint8_t b2;
23
        uint8_t b3;
24
    } b;
25
} uint32_whb_t;
26
#endif
27
28
29
int32_t eeprom_readSingleInt_union(uint32_t* data, uint16_t address)
30
{
31
  int32_t returnValue = 0;
32
  uint8_t addressAsChar[2] = {0};
33
34
  addressAsChar[0] = (uint8_t) address;        // Low byte of the address.
35
  addressAsChar[1] = (uint8_t) (address >> 8);    // High byte of the address.
36
37
  returnValue += i2c_start(EEPROM_WRITE_ADDRESS);      // Send START and EEPROM write address.
38
  returnValue += i2c_write(addressAsChar, 2);        // Write EEPROM-internal address.
39
  returnValue += i2c_start(EEPROM_READ_ADDRESS);      // Send repeat START and EEPROM read address.
40
41
42
  returnValue += i2c_read(    &(((uint32_whb_t *)data)->b.b0));            // Read one byte and increment pointer to data.
43
  returnValue += i2c_read(    &(((uint32_whb_t *)data)->b.b1));            // Read one byte and increment pointer to data.
44
  returnValue += i2c_read(    &(((uint32_whb_t *)data)->b.b2));            // Read one byte and increment pointer to data.
45
  returnValue += i2c_readLast(&(((uint32_whb_t *)data)->b.b3));         // Read last byte.
46
47
  returnValue += i2c_stop();                // Send STOP.
48
49
  if (returnValue != 0)                  // If something went wrong, return error.
50
  {
51
    return EEPROM_READ_ERROR;
52
  }
53
  else                          // Else return success.
54
  {
55
    return SUCCESS;
56
  }
57
}

Mit einer "transparent union" kann man den code sogar noch etwas
eleganter gestalten, wird aber nicht vom Standard abgesegnet.

--
Marcus

von Klaus S. (Gast)


Lesenswert?

Sven schrieb:
> Ich habe folgenden Codeausschnitt gesehen (in der Software eines 16Bit
> Motorrollers):

So einen will Ich auch haben. Hast du eine Bezugsquelle?

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.