Forum: Compiler & IDEs Global typedef struct mit signed char?


von Dennis H. (hoh)


Lesenswert?

Hallo,

bin etwas am verzweifeln. Ich lege global ein struct, mit signed char, 
an.
Dieser wird auf 0 gesetzt.
Nun simuliere ich in AVR Studio 5 (5.0.1163).

Wenn ich nun in der Funktion "acc" diese Variable dekrementiere, erhalte 
ich 255, erwarte jedoch -1.

In der Watchlist wird der Typ der Variablen "AXIS.acc_x" korrekt als 
"signed char:8" angezeigt, trotzdem der Wert 255... Eigentlich, kann 
dieser Typ doch min. -127 werden!
Inkrementiere ich die Variable, läuft sie auch bis 255!

Kann mir das jemand erklären, bzw helfen??

globdef.h
1
#define xAxis 0
2
3
typedef struct
4
{
5
  volatile signed char acc_x:8;    
6
}structaxis;
7
8
structaxis AXIS[xAxis];

main.c
1
#include "globdef.h"
2
3
void acc(){
4
AXIS.acc_x--;
5
}
6
7
int main(void){
8
AXIS.acc_x = 0;
9
while(1){
10
acc();
11
// HIER BREAKPOINT!
12
}
13
}

von Stefan E. (sternst)


Lesenswert?

Dennis Hoh schrieb:
> In der Watchlist wird der Typ der Variablen "AXIS.acc_x" korrekt als
> "signed char:8" angezeigt, trotzdem der Wert 255... Eigentlich, kann
> dieser Typ doch min. -127 werden!

Vielleicht hat der Debugger schlicht ein Problem mit signed Bitfeldern.

Wieso überhaupt ein Bitfeld?

von Dennis H. (hoh)


Lesenswert?

Stefan Ernst schrieb:
> Dennis Hoh schrieb:
>> In der Watchlist wird der Typ der Variablen "AXIS.acc_x" korrekt als
>> "signed char:8" angezeigt, trotzdem der Wert 255... Eigentlich, kann
>> dieser Typ doch min. -127 werden!
>
> Vielleicht hat der Debugger schlicht ein Problem mit signed Bitfeldern.
Nicht nur der Debugger, sondern auch ein ATmega1284P? Hier verursacht es 
ebenfalls Probleme.

> Wieso überhaupt ein Bitfeld?
Der gezeigte ausschnitt ist nur ein vereinfachter Teil des Bitfeldes.
Habe das gleiche Problem mit
- AxisRelPos:16;
- AxisGoTo:16;
- AxisDirection:1
Hier in voller Größe:
1
typedef struct
2
{
3
  volatile uint8_t AxisStateRef:1;
4
  volatile uint8_t AxisStateRdy:1;      
5
  volatile uint16_t AxisAbsPos:15;         
6
  volatile uint16_t AxisMaxPos:15;      
7
  volatile int16_t AxisRelPos:16;         
8
  volatile int16_t AxisGoTo:16;         
9
  volatile signed char AxisAcc:8;
10
  volatile signed char AxisDirection:8;      
11
}structaxis;

von Karl H. (kbuchegg)


Lesenswert?

Dennis Hoh schrieb:

>> Vielleicht hat der Debugger schlicht ein Problem mit signed Bitfeldern.
> Nicht nur der Debugger, sondern auch ein ATmega1284P? Hier verursacht es
> ebenfalls Probleme.

Woran machst du das fest?

Der Unterschied zwischen -1 und 255 liegt (momentan) lediglich darin, 
welche Ausgabefunktion zur Anzeige eines Wertes benutzt wird. Auf 
Bitebene sind beide 8-Bit Zahlen völlig identisch. Beide Zahlen haben 
alle Bits auf 1.

von Karl H. (kbuchegg)


Lesenswert?

typedef struct
{
  volatile uint8_t AxisStateRef:1;
  volatile uint8_t AxisStateRdy:1;
  volatile uint16_t AxisAbsPos:15;
  volatile uint16_t AxisMaxPos:15;
  volatile int16_t AxisRelPos:16;
  volatile int16_t AxisGoTo:16;
  volatile signed char AxisAcc:8;
  volatile signed char AxisDirection:8;
}structaxis;


Ich seh ehrlich gesagt nicht wirklich, wozu hier Bitfelder notwendig 
sein sollen. Wenn du Glück hast, dann hat der Compiler lediglich 
AxisStateRef und AxisStateRdy in ein gemeinsames Byte zusammengelegt und 
den Rest jeweils auf Bytegrezen allokiert. Wenn du Pech hast, dann hat 
er dir die 15 Bit Sachen in die Bytes hineingefriemelt und zahlst dafür 
mit einem horrendem Laufzeitpenalty weil jeder Zugriff in eine Maskier 
und Schiebeorgie ausartet.

von MWS (Gast)


Lesenswert?

Dennis Hoh schrieb:
> #define xAxis 0
> structaxis AXIS[xAxis];

Ein Array mit Größe 0 ? Dann als Array definiert, aber nicht als Array 
angesprochen.

> AXIS.acc_x = 0;

Lässt sich so nicht kompilieren.

Ansonsten, wenn man die Fehler korrigiert, zeigt zumindest Studio 4 
korrekt -1, -2, usw. an.

von Dennis Hohmann (Gast)


Lesenswert?

MWS schrieb:
> Ein Array mit Größe 0 ? Dann als Array definiert, aber nicht als Array
> angesprochen.

Korrekt heisst es:
1
#define xAxis 0
2
#define yAxis 1
3
#define zAxis 2
4
5
typedef struct {
6
  volatile uint8_t AxisStateRef:1;
7
  volatile uint8_t AxisStateRdy:1;
8
  volatile uint16_t AxisAbsPos:15;
9
  volatile uint16_t AxisMaxPos:15;
10
  volatile int16_t AxisRelPos:16;
11
  volatile int16_t AxisGoTo:16;
12
  volatile signed char AxisAcc:8;
13
  volatile signed char AxisDirection:8;   
14
}structaxis;
15
16
structaxis AXIS[2];
17
#define X AXIS[xAxis]
18
#define Y AXIS[yAxis]
19
#define Z AXIS[zAxis]
Die Funktion:
1
void axis_move(int8_t AxisSelect,int16_t AxisGoto, uint16_t AxisSpeed)
2
{
3
  if (AxisGoto > 0)
4
    {  
5
      AXIS[AxisSelect].AxisDirection = 1;
6
    } 
7
  else
8
    {
9
      AXIS[AxisSelect].AxisDirection = -1;
10
    }
11
  AxisGoto = abs(AxisGoto);
12
  for (uint16_t i=0; i<AxisGoto; i++)
13
    {
14
      axis_step(AxisSelect,AxisSpeed);
15
    }
16
}
Der Aufruf in main
1
axis_move(xAxis, -200,300);

Der Fehler:
In der Funktion "axis_step" berechne ich die neue Position mit:
1
AXIS[AxisSelect].AxisRelPos += AXIS[AxisSelect].AxisDirection;

Dies läuft in Studio sowie im Controller ohne Fehler, solange
AXIS[AxisSelect].AxisDirection = 1;

Mit AXIS[AxisSelect].AxisDirection = -1; zählt er mit jedem Schritt 255 
zu AXIS[AxisSelect].AxisRelPos hinzu, obwohl er in die andere Richtung 
fährt.

typecast habe ich auch schon ohne Erfolg versucht.

AXIS[AxisSelect].AxisRelPos += (int16_t)AXIS[AxisSelect].AxisDirection;

von MWS (Gast)


Lesenswert?

Dennis Hohmann schrieb:
> structaxis AXIS[2];

Wenn Du 3 Achsen hast, würd' ich AXIS auch mit Größe 3 anglegen.

Ohne Bitfielddefinition für die Ints geht's, ansonsten zählt er von 0 
auf 65535. Die Frage wurde schon gestellt: Warum glaubst Du Bitfields zu 
benötigen ?
1
typedef struct {
2
  volatile uint8_t AxisStateRef:1;
3
  volatile uint8_t AxisStateRdy:1;
4
  volatile uint16_t AxisAbsPos:15;
5
  volatile uint16_t AxisMaxPos:15;
6
  volatile int16_t AxisRelPos;
7
  volatile int16_t AxisGoTo;
8
  volatile signed char AxisAcc:8;
9
  volatile signed char AxisDirection:8;   
10
}structaxis;
11
12
volatile structaxis AXIS[3];
13
14
15
void axis_move(int8_t AxisSelect,int16_t AxisGoto, uint16_t AxisSpeed)
16
      {
17
        AXIS[AxisSelect].AxisDirection = -1;
18
        AXIS[AxisSelect].AxisRelPos += AXIS[AxisSelect].AxisDirection;
19
      }
20
21
22
23
int main (void)
24
{
25
26
    while(1){
27
              axis_move(0, 10,20);
28
            }
29
}

von MWS (Gast)


Lesenswert?

Der erzeugter Maschinencode ist übrigens in beiden Fällen gleich, egal 
mit welcher Optimierung.

Die Angabe der Bits bzw. Weglassen derselben hat offenbar nur Einfluss 
darauf, wie Studio die Werte interpretiert.

von Dennis H. (hoh)


Lesenswert?

MWS schrieb:
> Warum glaubst Du Bitfields zu benötigen ?

Um Platz zu sparen! Bis jetzt dachte ich es bringt was. Wie würdest du 
das aufbauen?

3 Achsen, div. Werte pro Achse?

von Rolf Magnus (Gast)


Lesenswert?

MWS schrieb:
>Die Frage wurde schon gestellt: Warum glaubst Du Bitfields zu benötigen ?

Vor allem ist das hier:

>   volatile uint8_t AxisStateRef:1;
>   volatile uint8_t AxisStateRdy:1;
>   volatile uint16_t AxisAbsPos:15;
>   volatile uint16_t AxisMaxPos:15;

eine sehr ungünstige Anordnung.

Im Bezug auf das Problem könnte folgende Passage aus der  ISO-Norm 
relevant sein:

****
[...] for bit-fields, it is implementation-defined whether the specifier 
int designates the same type as signed int or the same type as unsigned 
int.
****

int16_t dürfte auf dem AVR ein typedef für int sein. Dann wäre es nach 
obiger Regel möglich, daß AxisRelPos und AxisGoTo vorzeichenlos sind. 
Die Fehlerbeschreibung läßt vermuten, daß dem so ist.

von Rolf Magnus (Gast)


Lesenswert?

Ok, hab grade mal nageschaut. Daran liegts wohl doch nicht. int16_t ist 
ein typedef für signed int, muß also auch in Bitfeldern 
vorzeichenbehaftet sein.

Dennis Hoh schrieb:
> MWS schrieb:
>> Warum glaubst Du Bitfields zu benötigen ?
>
> Um Platz zu sparen! Bis jetzt dachte ich es bringt was.

Ok, und warum denkst du, daß ein gewöhnlicher int16_t mehr Platz 
braucht, als ein Bitfeld mit 16 Bit Breite?

von MWS (Gast)


Lesenswert?

Dennis Hoh schrieb:
> Um Platz zu sparen! Bis jetzt dachte ich es bringt was. Wie würdest du
> das aufbauen?
>
> 3 Achsen, div. Werte pro Achse?

Da wird gespart, lobenswert :D
Nur bei 3 Achsen nicht wirklich sinnvoll, reden wir wieder drüber bei 30 
Achsen.

Oder ist's eine Tiny, wo jedes Byte zählt ?

Wenn sich zwei oder mehr Variablen ein Byte teilen müssen, so zwingst Du 
den Compiler zu Masken- oder Schiebeoperationen, das Sparen von SRam 
kostet Dich also mehr Flash und Rechenzeit. Musst halt abwägen.

von Dennis H. (hoh)


Lesenswert?

Über einen Umweg habe ichs jetzt "hingebogen"!
Im Prinzip, lege ich die globale Var in eine lokale Var um.
1
int16_t x = X.AxisDirection;
2
X.AxisAbsPos += x;
So jedoch ging es nicht!
1
X.AxisAbsPos += (int16_t)X.AxisDirection;

Es schein in der Tat ein Debugger/Compiler-Bug zu sein.
(Oder eine Einstellung)
In AVR Studio 4 geht es. Unter Studio 4 compiliert, läuft es auch im 
ATmega1284P.

von MWS (Gast)


Lesenswert?

Dennis Hoh schrieb:
> Über einen Umweg habe ichs jetzt "hingebogen"!

Das scheint mir der falsche Weg gewesen zu sein.

Dennis Hoh schrieb:
> Es schein in der Tat ein Debugger/Compiler-Bug zu sein.

Nein, es ist kein Bug, hab's nochmal nachgesehen. Du hast sicherlich den 
Schalter -funsigned-bitfields im makefile drin.

Auszug aus "Using the GNU Compiler Collection"
> -fsigned-bitfields
> -funsigned-bitfields
> ...
> These options control whether a bit-field is signed or unsigned, when
> the declaration does not use either signed or unsigned.

Entferne oder ersetze den Schalter durch -fsigned-bitfields und die 
Darstellung zumindest in Studio4 ist in Ordnung, auch ohne Umwege.
Kannst mal testen und sagen, ob das dann auch in Studio5 in Ordnung ist.

von MWS (Gast)


Lesenswert?

Nachtrag:

Nachdem mir auffiel, daß es keinen Unterschied machen darf, ob 
-fsigned-bitfields oder -funsigned-bitfields benutzt wird, sobald der 
Integer als signed oder unsigned definiert wurde, hab' ich mir das 
nochmal näher angesehen.

In stdint.h wird (u)int16 wie folgt definiert:

uint16_t --> typedef unsigned int uint16_t;
int16_t  --> typedef signed int int16_t;

Damit darf der Schalter -f(un)signed-bitfields keine Wirkung mehr haben.
Hat er aber. Und zwar nicht nur wie AVR-Studio im Watch-Fenster den Wert 
ausgibt, sondern auch im Code.

Folgende Struct-Deklarationen müssten sich exakt entsprechen:
1
typedef struct {
2
  unsigned char AxisStateRef:1;
3
  unsigned char AxisStateRdy:1;
4
  unsigned int AxisAbsPos:15;
5
  unsigned int AxisMaxPos:15;
6
  signed int AxisRelPos:16;
7
  signed int AxisGoTo:16;
8
  signed char AxisAcc:8;
9
  signed char AxisDirection:8;   
10
}structaxis;
1
typedef struct {
2
  uint8_t AxisStateRef:1;
3
  uint8_t AxisStateRdy:1;
4
  uint16_t AxisAbsPos:15;
5
  uint16_t AxisMaxPos:15;
6
  int16_t AxisRelPos:16;
7
  int16_t AxisGoTo:16;
8
  signed char AxisAcc:8;
9
  signed char AxisDirection:8;   
10
}structaxis;

Wird -funsigned-bitfields verwendet, dann unterscheidet sich der hierzu 
erzeugte Code jedoch, im Beispiel:
1
void axis_move(int8_t AxisSelect,int16_t AxisGoto, uint16_t AxisSpeed)
2
   {
3
      AXIS[AxisSelect].AxisDirection = -1;
4
      AXIS[AxisSelect].AxisRelPos += AXIS[AxisSelect].AxisDirection;
5
         if (AXIS[AxisSelect].AxisRelPos < 0)
6
            PORTC = 0;
7
         else
8
            PORTC = 255;
9
   }

Wird mit signed / unsigned int deklariert, so wird AxisRelPos richtig 
als vorzeichenbehafteter Integer betrachtet.

Wird dagegen int16_t deklariert, wird AxisRelPos als vorzeichenloser 
Integer behandelt.

Das ist also nicht mehr nur ein Anzeigeproblem von AVR-Studio, sondern 
tatsächlich ein Fehler bei der Compilierung.

Erkennbar im Beispielcode an der unterschiedlichen Ausführung des 
Branches. Mit "signed int AxisRelPos" wird PORTC = 0, mit "int16_t 
AxisRelPos" wird PORTC = 255 ausgeführt.

Das darf so nicht sein. Würd' vorschlagen, das sich das jemand Anders 
noch anschaut, nicht dass ich was übersehen hab'.

Kann auch sein, daß dieser Fehler bekannt und in einer späteren 
AVR-GCC-Version bereits behoben ist. Verwendet wurde WinAVR-20090313.
Die richtige stdint.h wurde benutzt, geprüft durch Umbenennen.

Die "volatile" im Struct hab' ich raus genommen. Sobald der Struct 
selbst als volatile deklariert wurde, sollte es für die einzelnen 
Structmember nicht mehr notwendig sein.

von Dennis H. (hoh)


Lesenswert?

MWS schrieb:
> Entferne oder ersetze den Schalter durch -fsigned-bitfields und die
> Darstellung zumindest in Studio4 ist in Ordnung, auch ohne Umwege.
> Kannst mal testen und sagen, ob das dann auch in Studio5 in Ordnung ist.
Im Studio 5 ist der Schalter mit: "Default bitfield type is unsigned" 
benannt.
Hierbei bin ich davon ausgegangen, wenn es explizite als signed 
deklariert ist, dieser Schalter nicht wirkt.

YOU just made my day!
Das hat mich 5 Tage gekostet...

Kannst dir eine Flasche Sekt abholen!

von Karl H. (kbuchegg)


Lesenswert?

Dennis Hoh schrieb:
> MWS schrieb:
>> Warum glaubst Du Bitfields zu benötigen ?
>
> Um Platz zu sparen!


Eine plain vanilla struct braucht 12 Bytes. Mit deinem Bitfield kommst 
du auf 11 Bytes runter, bei gleichzeitig komplexeren Code.
Bei 3 Achsen hast du ganze 3 Bytes eingespart, bei 33 Bytes (36) 
Speicherverbrauch.

Bringts das wirklich?


> Bis jetzt dachte ich es bringt was. Wie würdest du
> das aufbauen?

plain vanilla.

von MWS (Gast)


Lesenswert?

Dennis Hoh schrieb:
> YOU just made my day!

Schön, freut mich :-)

Lies Dir aber noch meinen Beitrag oben durch, ich halte es für möglich 
(nicht getestet), dass uint16_t mit -fsigned-bitfields zu einem signed 
int wird. Wenn Du ganz sicher gehen willst, verwende bei der Definition 
des Bitfields nicht die Kurzformen in16_t, uint16_t, sondern mach's wie 
im Beispiel im ersten Struct.

> Kannst dir eine Flasche Sekt abholen!
Wo denn ? ;-)

von MWS (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Eine plain vanilla struct braucht 12 Bytes. Mit deinem Bitfield kommst
> du auf 11 Bytes runter, bei gleichzeitig komplexeren Code.
> Bei 3 Achsen hast du ganze 3 Bytes eingespart, bei 33 Bytes (36)
> Speicherverbrauch.

Hatte ich hier schon angesprochen:

MWS schrieb:
> Wenn sich zwei oder mehr Variablen ein Byte teilen müssen, so zwingst Du
> den Compiler zu Masken- oder Schiebeoperationen, das Sparen von SRam
> kostet Dich also mehr Flash und Rechenzeit. Musst halt abwägen.

Karl Heinz, ich würde Dich bitten, meine oben gemachten Erkenntnisse zu 
überprüfen.

von Karl H. (kbuchegg)


Lesenswert?

MWS schrieb:

> Karl Heinz, ich würde Dich bitten, meine oben gemachten Erkenntnisse zu
> überprüfen.

Ich habs mir durchgelesen.
Das was du da geschrieben hast, klingt alles schlüssig und dürfte nicht 
so sein. Ich check das nochmal mit dem C-Standard ab.

(Zeigt eigentlich nur, dass Bitfields anscheinend von niemandem für 
solche Sachen benutzt werden, sonst wärs schon aufgefallen. Bitfields 
nimmt man normalerweise um Einzelbits (true/false) in eine Struktur zu 
quetschen, maximal hat man da Dinge die 4 oder 8 Werte unsigned annehmen 
können. Aber tatsächliche Rechenwerte quetscht keiner in Bitfiels 
zusammen. In 30 Jahren kann ich mich an keinen einzigen derartigen Fall 
erinnern)

von Stefan E. (sternst)


Lesenswert?

MWS schrieb:
> In stdint.h wird (u)int16 wie folgt definiert:
>
> uint16_t --> typedef unsigned int uint16_t;
> int16_t  --> typedef signed int int16_t;

Nö, wird es nicht. Das ist nur der DOXYGEN-Teil. Die echte Definition 
steht weiter unten:
1
typedef int int16_t __attribute__ ((__mode__ (__HI__)));
2
typedef unsigned int uint16_t __attribute__ ((__mode__ (__HI__)));
Kein signed bei int16_t, daher das beobachtete Verhalten.

von Rolf Magnus (Gast)


Lesenswert?

Dann war meine Vermutung doch richtig, und es ist nur falsch 
dokumentiert.

von testpilot (Gast)


Lesenswert?

Dann gibt es ja keinen Grund uint16_t, int16_t und Co. zu verwenden, 
oder muss ich jetzt jedesmal vorher in der stdint.h nach der der genauen 
Definition schauen? Wofür gibt es die Definitionen dann überhaupt?

von Karl H. (kbuchegg)


Lesenswert?

Ganz so schwarz muss man auch nicht malen.
Die haben schon ihren Sinn.

Bei der nächsten stdint.h wird man das vielleicht auf ein explizites 
signed int ändern und gut ists. Das hat halt einfach wer übersehen, weil 
es normalerweise ja auch egal ist, und nur bei Bitfeldern einen 
Unterschied macht.

von testpilot (Gast)


Lesenswert?

Naja, stimmt auch wieder...

von MWS (Gast)


Lesenswert?

Stefan Ernst schrieb:
> Nö, wird es nicht. Das ist nur der DOXYGEN-Teil. Die echte Definition
> steht weiter unten:

Ah, ok, danke.
Hab' die erste Fundstelle genommen :D

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.