PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Seltsamer Unterschied zwischen a=a+1 und a++



M1.R
26.03.2010, 22:12
Hallo,

Code (auf einem Atmega16):

int8_t a=0;
while(1)
{
usart_put_int (a);
usart_puts("\r\n");
pause(1,10);
a=a+1;
}
Wenn Variable int8_t a mit a=a+1 hochgezählt wird sieht die Ausgabe wie erwartet so aus:
.
.
.
120
121
122
123
124
125
126
127
-128
-127
-126
-125
-124
-123
-122
-121
-120
.
.
.wird die Variable a aber mit a++ hochgezählt
int8_t a=0;
while(1)
{
usart_put_int (a);
usart_puts("\r\n");
pause(1,10);
a++;
} wird sieht es so aus:
.
.
.
125
126
127
128
129
130
.
.
.
32765
32766
32767
-32768
-32767
-32766
-32765
.
.
.
ich bin verwirrt! :-k

Gruss
M.

radbruch
26.03.2010, 22:34
Hallo

Bei mir scheint es wie erwartet zu funktionieren (auf einem Mega32):

#include "RP6RobotBaseLib.h"

int8_t a=0, b=0;

int main(void)
{
initRobotBase();

while(1)
{
writeInteger(a, 10);
writeString(" ");
writeInteger(b, 10);
writeString("\n");
a=a+1;
b++;
}
return 0;
}

Die Ausgabe dazu:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
11 11
12 12
13 13
14 14
15 15
16 16
17 17
18 18
19 19
20 20
21 21
22 22
23 23
24 24
25 25
26 26
27 27
28 28
29 29
30 30
31 31
32 32
33 33
34 34
35 35
36 36
37 37
38 38
39 39
40 40
41 41
42 42
43 43
44 44
45 45
46 46
47 47
48 48
49 49
50 50
51 51
52 52
53 53
54 54
55 55
56 56
57 57
58 58
59 59
60 60
61 61
62 62
63 63
64 64
65 65
66 66
67 67
68 68
69 69
70 70
71 71
72 72
73 73
74 74
75 75
76 76
77 77
78 78
79 79
80 80
81 81
82 82
83 83
84 84
85 85
86 86
87 87
88 88
89 89
90 90
91 91
92 92
93 93
94 94
95 95
96 96
97 97
98 98
99 99
100 100
101 101
102 102
103 103
104 104
105 105
106 106
107 107
108 108
109 109
110 110
111 111
112 112
113 113
114 114
115 115
116 116
117 117
118 118
119 119
120 120
121 121
122 122
123 123
124 124
125 125
126 126
127 127
-128 -128
-127 -127
-126 -126
-125 -125
-124 -124
-123 -123
-122 -122
-121 -121
-120 -120
-119 -119
-118 -118
-117 -117
-116 -116
-115 -115
-114 -114
-113 -113
-112 -112
-111 -111
-110 -110
-109 -109
-108 -108
-107 -107
-106 -106
-105 -105
-104 -104
-103 -103
-102 -102
-101 -101
-100 -100
-99 -99
-98 -98
-97 -97
-96 -96
-95 -95
-94 -94
-93 -93
-92 -92
-91 -91
-90 -90
-89 -89
-88 -88
-87 -87
-86 -86
-85 -85
-84 -84
-83 -83
-82 -82
-81 -81
-80 -80
-79 -79
-78 -78
-77 -77
-76 -76
-75 -75
-74 -74
-73 -73
-72 -72
-71 -71
-70 -70
-69 -69
-68 -68
-67 -67
-66 -66
-65 -65
-64 -64
-63 -63
-62 -62
-61 -61
-60 -60
-59 -59
-58 -58
-57 -57
-56 -56
-55 -55
-54 -54
-53 -53
-52 -52
-51 -51
-50 -50
-49 -49
-48 -48
-47 -47
-46 -46
-45 -45
-44 -44
-43 -43
-42 -42
-41 -41
-40 -40
-39 -39
-38 -38
-37 -37
-36 -36
-35 -35
-34 -34
-33 -33
-32 -32
-31 -31
-30 -30
-29 -29
-28 -28
-27 -27
-26 -26
-25 -25
-24 -24
-23 -23
-22 -22
-21 -21
-20 -20
-19 -19
-18 -18
-17 -17
-16 -16
-15 -15
-14 -14
-13 -13
-12 -12
-11 -11
-10 -10
-9 -9
-8 -8
-7 -7
-6 -6
-5 -5
-4 -4
-3 -3
-2 -2
-1 -1
0 0

Vielleicht ist deine usart_put_int()-Funktion der entscheidende Faktor?

Gruß

mic

M1.R
26.03.2010, 22:44
Hallo mic,

danke für denTest.

Hier der komplette Code, vielleicht kannst du einen Fehler entdecken?


//test

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h> // itoa etc.


#define UART_BAUD_RATE 9600L //baudrate definieren
#define UART_BAUD_CALC ((F_CPU)/((UART_BAUD_RATE)*16L)-1)

volatile uint8_t zaehler;

void usart_init (void)
{
UCSRC |= (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1); // Asynchron, 8N1
UBRRH=(uint8_t)(UART_BAUD_CALC>>8); // Baudrate wählen
UBRRL=(uint8_t)UART_BAUD_CALC;
}

void usart_start (void)
{
UCSRB |= (1 << TXEN); // UART TX (senden) einschalten
UCSRB |= (1 << RXEN); // UART RX (empfangen) einschalten
}


void usart_putc(unsigned char c) // Ein Zeichen senden
{
while(!(UCSRA & (1 << UDRE))); // warte, bis UDR bereit
UDR = c; // sende Zeichen
}

void usart_puts (char *s) // Einen String senden
{
while (*s) // so lange *s != NULL
{
usart_putc(*s);
s++;
}
}


void usart_put_int (int wert)
{
char text [7];
itoa (wert, text, 10);
usart_puts (text);
}

void timer0_init (void)
{

TCCR0 |= (1<<CS01);
TCCR0 |= (1<<CS00);
TCCR0 |= (1<<WGM01);
OCR0=124;
}

void timer0_start (void)
{
TIMSK |= (1 << OCIE0); //Timer0 Interrupt freigegeben
}

void timer0_stop (void)
{
TIMSK &= ~(1 << OCIE0); //Timer0 Interrupt sperren
}

ISR(TIMER0_COMP_vect) // wird jede ms ausgeführt
{

zaehler++;

}

void pause(uint8_t faktor, uint8_t ms) //millisek eingeben max 255
{
uint8_t i;
for (i=1; i<=faktor; i++)
{
zaehler=0;
while (zaehler<ms);
}
}


int main(void)
{

sei();
usart_init();
usart_start();
timer0_init ();
timer0_start();

int8_t a=0;
while(1)
{
usart_put_int (a);
usart_puts("\r\n");
pause(1,10);
//a=a+1;
a++;
}


while(1);
return 0;
}
Gruss
M.

yaro
26.03.2010, 22:57
Dein usart_put_int erwartet ein 16bit Integer... Versuch mal, das a vorher in diesen zu casten. Das Problem könnte daran liegen, dass der Compiler optimiert und das a++ gleich in die Klammer packt. Vielleicht denkt er, a sei eine 16bit Variable, weil dort ja eine 16bit Variable erwartet wird und behandelt sie auch so.
Ein Cast sollte das Problem beheben. Wenn nicht, schalte mal den Optimizer aus und guck, ob es dann geht.
Und wenn das alles nicht geht, dann schreib eine Funktion usart_put_int8, die ein int8_t erwartet.

Wenn das alles nicht funktioniert, kannst du erstmal dein Compiler updaten.

Und wenn das alles nicht ging, dann hilft nur noch ein ordentlicher Schluck Bier...
Und dann gucken wir weiter =)

Gruß, Yaro

radbruch
26.03.2010, 22:59
Hallo

Ich habe dein Programm unverändert (bis auf die Baudrate) übernommen, hier die Ausgabe:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
-128
-127
-126
-125
-124
-123
-122
-121
-120
-119
-118
-117
-116
-115
-114
-113
-112
-111
-110
-109
-108
-107
-106
-105
-104
-103
-102
-101
-100
-99
-98
-97
-96
-95
-94
-93
-92
-91
-90
-89
-88
-87
-86
-85
-84
-83
-82
-81
-80
-79
-78
-77
-76
-75
-74
-73
-72
-71
-70
-69
-68
-67
-66
-65
-64
-63
-62
-61
-60
-59
-58
-57
-56
-55
-54
-53
-52
-51
-50
-49
-48
-47
-46
-45
-44
-43
-42
-41
-40
-39
-38
-37
-36
-35
-34
-33
-32
-31
-30
-29
-28
-27
-26
-25
-24
-23
-22
-21
-20
-19
-18
-17
-16
-15
-14
-13
-12
-11
-10
-9
-8
-7
-6
-5
-4
-3
-2
-1
0
Wie geschrieben, das macht ein Mega32. Ich teste es morgen mal mit einem Mega16.

Gruß

mic

yaro
26.03.2010, 23:05
Teste es lieber mit seinem Compiler......Mit dem Controller wird es kaum was zutun haben.

Gruß, Yaro

radbruch
26.03.2010, 23:14
Mein WinAVR-Source-File:

WinAVR 20060119
---------------

Sources for specific versions can be found at these locations.

GNU Binutils 2.16.1
<http://sources.redhat.com/binutils/>

GCC 3.4.5
<http://gcc.gnu.org/>

avr-libc 1.4.2
<http://savannah.nongnu.org/projects/avr-libc>

avrdude 5.0cvs
<http://savannah.nongnu.org/projects/avrdude/>

avrdude-gui 0.2.0
<http://sourceforge.net/projects/avrdude-gui>

GDB 6.4
<http://sources.redhat.com/gdb/>

Insight 6.4
<http://sources.redhat.com/insight/>

AVaRICE 2.4
<http://sourceforge.net/projects/avarice>

SimulAVR 0.1.2.1
<http://savannah.nongnu.org/projects/simulavr/>

Tofrodos 1.6
<http://www.thefreecountry.com/>

Cygwin DLLs
<http://www.cygwin.com/>

Any patches against these sources can be found in the \source directory
of your installation. These are organized into different subdirectories, one
for each project. All patches have a .patch file extension. Any additional
files (such as .h files) are also in their respective directories.

M1.R
26.03.2010, 23:28
Hallo,

danke für eure Mühe!

dieser Test mit LEDs hat ergeben, dass die usart_put_int unschuldig ist.

int8_t a=0;
while(1)
{
//usart_put_int (a);
//usart_puts("\r\n");
pause(1,10);
//a=a+1;
a++;
if (a>=0)
{
kontrollled(gruen,an);
kontrollled(rot,aus);
}
else
{
kontrollled (gruen,aus);
kontrollled(rot,an);
}
}Dann habe ich die Optimierung ausgeschaltet und siehe da - es geht! O:)
Aber leider ist mein eigentliches Programm so gross, dass es ohne Optimierung nicht auf den ATmega16 passt :(
Nun ja, heute gebe ich erstmal auf...

Gute Nacht!
M.

M1.R
27.03.2010, 10:48
Hallo,

mittlerweile habe ich die neueste WinAVR (20100110) und AVRStudio 4.18 installiert. Es ändert sich nichts.
Bei Optimierungseinstellung 00 und 01 bleibt die Variable 8 Bit, bei Optimierung 02 03 und 0s wird sie 16 Bit groß. :cry:

Gruss
M.

yaro
27.03.2010, 11:11
Solche Fehler, die vom Optimizer verursacht sind, sind immer superschwer zu finden! Vielleicht wird dieser Bug ja irgendwann behoben.
Kann man eigentlich den Leuten solche Bugs berichten? Wenn ja, dann wo?

Gruß, Yaro

sternst
27.03.2010, 11:13
Mir ist nicht ganz klar, worum es dir hier überhaupt geht. Der Überlauf von signed Integers ist dem C-Standard nach undefiniert. Selbst wenn die Ausgabe so aussehen würde

...
126
127
666
-999
...
wäre das vollkommen in Ordnung.
Nur der Überlauf von unsigned Integers ist definiert.

M1.R
27.03.2010, 11:32
Der Überlauf von signed Integers ist dem C-Standard nach undefiniert. ...Nur der Überlauf von unsigned Integers ist definiert.
Das habe ich nicht gewusst!
Vielen Dank!

M.

Besserwessi
27.03.2010, 14:16
Auch wenn der Überlauf nicht definiert ist, sollte die Variabel a dennoch nicht auf 16 Bit erweitert werden.

Wenn man sich daran stört, sollte es vermutlich reichen die Variabel a als volatile zu deklarieren. Dann kann der Compiler nicht mehr so viel Optimieren, und im RAM wird wohl kaum ein 2 tes Byte reserviert.

Das ist zwar nicht der eigentliche Zweck, aber Volative hilft machmal um den Optimierer zu bremsen.

sternst
27.03.2010, 14:54
Auch wenn der Überlauf nicht definiert ist, sollte die Variabel a dennoch nicht auf 16 Bit erweitert werden.
... und im RAM wird wohl kaum ein 2 tes Byte reserviert. Nicht die Variable an sich wird auf 16 Bit erweitert (und daher auch kein 2tes Byte im Speicher reserviert), sondern nur die Schleifen interne Registerkopie der Variable. Und das ist völlig legitim. Der Standard schreibt sogar vor, dass die eigentliche Operation (a++) mindestens in int (also 16 Bit) gemacht werden muss. Und da die dann mit dem Wert aufgerufene Funktion als Parameter ebenfalls ein int erwartet, entscheidet sich der Compiler halt, die lokale Registerkopie gleich ganz als 16-Bit-Wert zu führen. Und weil er sich bei einem signed Wert keine Gedanken um einen Überlauf zu machen braucht, spart er sich halt das Ausnullen des High-Bytes zwischen den beiden Operationen.

Ein volatile wird die Situation tatsächlich verändern, denn dann wird ja die lokale Kopie (16-Bit) zwischendurch in die eigentliche Variable (8-Bit) geschrieben und wieder gelesen, was das High-Byte der Kopie nullt. Aber wozu sollte man den gewünschten Überlauf "hintricksen"? Code zu schreiben, der sich bei einem signed Wert auf einen bestimmten Überlauf verlässt, ist böse, denn wie gesagt, ein solcher Überlauf ist undefiniert.

Sternthaler
27.03.2010, 22:16
Hallo zusammen.

Na M1.R, da hast du ja was grauenvoll teuflisches entdeckt :twisted: .

Ich habe mal alle Varianten zu folgendem ausprobiert mit Optimizermode "-Os" (Größe): (Versionen: avr-gcc (GCC) 4.1.2 (WinAVR 20070525))
-- Als Variable
int8_t a=0;
char a=0;

-- Als Funktion
usart_put_int (a);
usart_put_int ((int8_t) a);
usart_put_int8_t (a);

-- Als Rechnung
a=a+1;
a+=1;
a++;

Es gibt nur genau eine Funktion, die zu jeder Variablen/Rechnungs-Kombination das richtige Ergebnis liefert.
==> Das ist die Funktion usart_put_int8_t() mit dem Parametertyp int8_t

- Das casten hatte nichts gebracht und lieferte beim a++ genau das gleiche falsche Ergebnis.
- Zu jeder anderen Variablen/Funktions-Kombination, also eigentlich mit falschem Parametertyp, hatte es keinerlei Warning vom Compiler gegeben. Auch dann nicht, wenn alle Funktionen als Prototypen angegeben waren.
- In allen Fällen wurde die Variable a als 16-Bit-Zahl immer in 2 Registern gehalten.

Hier der kommentierte Output vom Compiler im Fehlerfall mit a++:

Variante 03: int8_t a++;
int8_t a=0;
158: c0 e0 ldi r28, 0x00 ; 0 <<== Byte 1 für a
15a: d0 e0 ldi r29, 0x00 ; 0 <<== Byte 2 für a
usart_put_int (a);
15c: ce 01 movw r24, r28 <<== Ein WORT wird bewegt
15e: c6 df rcall .-116 ; 0xec <usart_put_int>
a++;
166: 21 96 adiw r28, 0x01 ; 1 <<== Ein WORT wird in a addiert


Hier der kommentierte Output vom Compiler im zufällig funktionierenden Fall mit a=a+1:

Variante 01: int8_t a=a+1
int8_t a=0;
158: c0 e0 ldi r28, 0x00 ; 0 <<== Byte 1 für a
15a: d0 e0 ldi r29, 0x00 ; 0 <<== Byte 2 für a
usart_put_int (a);
15c: 8c 2f mov r24, r28 <<== EIN Byte von a für Parameter
15e: 99 27 eor r25, r25 <<== Vorbelegung Byte 2 für Parameter
160: 87 fd sbrc r24, 7 <<== Vorzeichen Low-Byte a prüfen
162: 90 95 com r25 <<== Vorzeichen vom Parameter auf - setzen
164: c3 df rcall .-122 ; 0xec <usart_put_int>
a=a+1;
16c: 21 96 adiw r28, 0x01 ; 1 <<== Ein WORT wird in a addiert



Weiterer Versuch mit:
volatile int8_t a=0;

Nur jetzt wird die Variable als ein Byte im Speicher gehalten. Das 2.te Byte zum Aufrufen der INT-Parameter-Funktion wird immer mit korrektem Vorzeichen erzeugt.
- Soll heissen: Es kommt immer das richtige Ergebnis zustande.


Es bleibt: Parameter müssen passen.
Es verwundert: Kein Warning vom Compiler.
Es staunt, grüßt und trinkt nun ein Bier:

Sternthaler

P.S.: Ein Versuch, so wie von Radbruch, dass alle Varianten in einem Programm getestet werden, hat vollkommen andere Verhältnisse gebracht.
Selbst mehrere Variablen mit unterschiedlichen Startwerten konnte den Optimierer meistens nicht dazu bewegen nicht doch nur immer eine Variable zu erzeugen.
Bei Radbruch wird die "falsche" Variable b wegoptimiert.

Und dann wäre da noch die "fähige" Funktion:
void usart_put_int8_t (int8_t wert)
{
usart_put_int ((int)wert);
}

sternst
28.03.2010, 06:14
Es gibt nur genau eine Funktion, die zu jeder Variablen/Rechnungs-Kombination das richtige Ergebnis liefert.
...
- Das casten hatte nichts gebracht und lieferte beim a++ genau das gleiche falsche Ergebnis.
Nur weil das Ergebnis und die Vorgehensweise des Compilers nicht deinen Erwartungen entsprechen, ist das nicht automatisch fehlerhaft. In diesem Fall liegt der Fehler einzig in deinen Vorstellungen, der Compiler verhält sich völlig korrekt.
Ich wiederhole es gerne nochmal: der Überlauf eines vorzeichenbehafteten Integers wird vom C-Standard ausdrücklich als "undefined" festgelegt. Das einzige, was hier also fehlerhaft ist, ist der C-Code, weil er einen solchen Überlauf verursacht.


- Zu jeder anderen Variablen/Funktions-Kombination, also eigentlich mit falschem Parametertyp, hatte es keinerlei Warning vom Compiler gegeben. Auch dann nicht, wenn alle Funktionen als Prototypen angegeben waren.
Wovor sollte da denn auch gewarnt werden? Die Übergabe eines kleineren Integers, als eigentlich von der Funktion erwartet, ist ausdrücklich erlaubt (impliziter Cast). Und von diesem Feature wird auch reger Gebrauch gemacht (oft unbewusst). Es dürfte kaum ein (nicht triviales) Programm geben, in dem das nicht vorkommt.


Es bleibt: Parameter müssen passen.
Was tatsächlich bleibt, ist die Tatsache, dass das Ergebnis aus der Kombination "erlaubter impliziter Cast" und "provozieren eines nicht erlaubten signed Overflows" hier manchen verwundern mag, es aber kein Fehler des Compilers ist.

Sternthaler
28.03.2010, 10:42
Mir ist nicht ganz klar, worum es dir hier überhaupt geht.
Es geht darum:
Wenn Variable int8_t a mit a=a+1 hochgezählt wird sieht die Ausgabe wie erwartet so aus:

ich bin verwirrt!

sternst
28.03.2010, 11:08
Es geht darum:
Wenn Variable int8_t a mit a=a+1 hochgezählt wird sieht die Ausgabe wie erwartet so aus:

ich bin verwirrt!
Und was willst du mir damit jetzt sagen? Dass es dich verwirrt, dass mit unterschiedlichem Code nicht immer das selbe unerwartete Ergebnis entsteht?
"undefiniert" heißt "alles Mögliche kann passieren". Das schließt auch mit ein, dass nach einer kleinen Änderung am Code das Ergebnis auf einmal ganz anders aussieht.