Forum: Mikrocontroller und Digitale Elektronik AVR Comiler Optimazation Crasht den Controller (ATMEGA328P)


von M. W. (Gast)


Lesenswert?

Hallo Liebes Forum,

Ich bin total am Verzweifeln. Bis jetzt hatte ich eigentlich noch keine 
groben Probleme bei der Entwicklung meiner AVR Tiny und Megas aber jetzt 
sitze ich schon seit 2 Wochen ohne Erfolg.

Ziel war die Entwicklung eines kleinen USB-Sticks, der meine 
Funksteckdosen in der Wohnung schalten kann. Den wollte ich an meinen 
Microserver anstöpseln und über Serielle Schnittstelle (CH340) 
ansprechen. So weit so gut. Er schaltet die Dosen aber nach jedem 
Durchlauf Knallt es und der Controller springt wieder in die 
Main-Routine. Gemerkt habe ich es, da mit kein Success ausgegeben wird 
sonder nur folgender String:

>SuHey Dude!

Er stürzt also bei der Ausgabe von Success in der Funktion remotepwr ab.

Nachdem ich einiges Herumprobiert habe ist mir aufgefallen, dass bei 
ausgeschalteter Optimierung im Compiler alles super läuft nur dann ist 
das Programm knapp 200% größer.

Ich würde gerne verstehen warum das so ist und daraus lernen. In der 
Hoffnung hier in dem Forum treibt sich ein Profi rum für den das ein 
Kinderspiel ist.

Viele Lieben Dank für eure Unterstützung,
Michael
1
/*
2
 * USB RemoteSwitch.c
3
 *
4
 * Created: 03.02.2018 14:20:45
5
 * Author : Michael
6
 */ 
7
8
#define F_CPU 16000000UL
9
#include <stdlib.h>
10
#include <avr/io.h>
11
#include <avr/interrupt.h>
12
#include <avr/pgmspace.h>
13
#include <string.h>
14
#include <util/delay.h>
15
#include <ctype.h>
16
17
#include "uart.h"
18
19
/************************************************************************/
20
/* RF USER DEFINITIONS - EDIT HERE                      */
21
22
#define TELEGRAM_REPEAT 8  // minimum 2 (more recommended)
23
#define RF_PORT PORTD    // port used for pin toggle
24
#define RF_PIN PD7      // pin where rf transmitter is connected to
25
26
/* RF USER DEFINITIONS - DO NOT EDIT HERE */
27
28
#define HIGH 1
29
#define LOW 0
30
#define ON 1
31
#define OFF 0
32
33
/************************************************************************/
34
35
/************************************************************************/
36
/* RF FUNCTIONS EDIT HERE                      */
37
38
void ClockPin(int PinState, int Clocks){
39
  
40
  // set pin state
41
  switch(PinState){
42
    case HIGH:
43
    RF_PORT |= (1 << RF_PIN); break;
44
    case LOW:
45
    RF_PORT &= ~(1 << RF_PIN); break;
46
    default:; break;
47
  }
48
  
49
  // keep the pin state for the duration of clocks
50
  for(int i=0; i<= Clocks; i++) _delay_us(342);
51
}
52
53
void BuildQuery(char *query, char *group, char socket, int state){
54
  
55
  strcat(query, group);
56
  
57
  switch(socket){
58
    case 'a': case 'A': strcat(query, "0FFFF"); break;
59
    case 'b': case 'B': strcat(query, "F0FFF"); break;
60
    case 'c': case 'C': strcat(query, "FF0FF"); break;
61
    case 'd': case 'D': strcat(query, "FFF0F"); break;
62
    case 'e': case 'E': strcat(query, "FFFF0"); break;
63
    default:; break;
64
  }
65
  
66
  if(state>0){ strcat(query,"0F");}
67
  else{ strcat(query, "F0"); }
68
}
69
70
void SwitchSocket(char group[5], char socket, int state){
71
  
72
  char query[12] = "";
73
  BuildQuery(query, group, socket, state);
74
  
75
  char c;
76
  
77
  for(int i=0; i < TELEGRAM_REPEAT; i++)
78
  {
79
    for(int k=0; k<12; k++)
80
    {
81
      c = query[k];
82
      
83
      switch(c){
84
        case '0': /* Signal "0" -> dip on -> 1 0 0 0, 1 0 0 0 */
85
        ClockPin(HIGH, 1);
86
        ClockPin(LOW, 3);
87
        ClockPin(HIGH, 1);
88
        ClockPin(LOW, 3); break;
89
        case 'F': /* Signal "float" -> dip off -> 1 0 0 0, 1 1 1 0 */
90
        ClockPin(HIGH, 1);
91
        ClockPin(LOW, 3);
92
        ClockPin(HIGH, 3);
93
        ClockPin(LOW, 1); break;
94
        default:; break;
95
      }
96
    }
97
    
98
    ClockPin(HIGH, 1);
99
    ClockPin(LOW, 31);
100
  }
101
}
102
/************************************************************************/
103
104
105
//Baud-Rate
106
 
107
#define UART_BAUD_RATE  9600
108
109
//Uart Special Characters
110
#define TRUE 1
111
#define FALSE 0
112
#define CHAR_NEWLINE '\n'
113
#define CHAR_RETURN '\r'
114
#define RETURN_NEWLINE "\r\n"
115
116
//Uart Variable
117
unsigned char data_count = 0; //Character Count for Input Data
118
unsigned char data_in[16];    //Input Data will be stored here
119
char command_in[16];      //Final Command for String Compare
120
int tmp =0;
121
122
void remotepwr(char input[16])
123
{
124
  char *pch = NULL;
125
  char cmdValue[16];
126
  char *ptr = NULL;
127
  char rgroup[16];
128
  int  rsocket = 0;
129
  char rstate = 0;
130
  int  error = 0;
131
  // Find the position the equals sign is
132
  // in the string, keep a pointer to it
133
  pch = strchr(input, '=');
134
  if (pch == NULL)
135
  {
136
    error = 1;
137
  }
138
139
  if (error == 0)
140
  {
141
    // Copy everything after that point into
142
    // the buffer variable
143
    strcpy(cmdValue, pch + 1);
144
    cmdValue[9] = '\0';
145
    // Now turn this value into an integer and
146
    // return it to the caller.
147
    ptr = strtok(cmdValue, ",");
148
149
    for (int i = 0; i < 3; i++)
150
    {
151
      if (ptr == NULL)
152
      {
153
        error = 1;
154
        break;
155
      }
156
      if (error != 0); else
157
      {
158
        if (i == 0 && error == 0)
159
        {
160
          //Parameter 0 (1) is group
161
          strcpy(rgroup, ptr);
162
          rgroup[5] = '\0';
163
          if (strlen(rgroup) < 5)
164
          {
165
            error = 1;
166
          }
167
        }
168
        if (i == 1 && error == 0)
169
        {
170
          //Parameter 1 (2) is socket
171
          //Check if Parameter 2 only contains letters from A to E or a to e
172
          if ((ptr[0] >= 'a' && ptr[0] <= 'e') || (ptr[0] >= 'A' && ptr[0] <= 'E'))
173
          {
174
175
            rsocket= ptr[0];
176
            
177
          }
178
          else
179
          {
180
            error = 1;
181
          }
182
        }
183
184
        if (i == 2 && error == 0)
185
        {
186
          //Parameter 2 (3) is state
187
          rstate = atoi(ptr);
188
          if (rstate == 0 || rstate == 1); else
189
          {
190
            error = 1;
191
          }
192
        }
193
        if (error != 0); else
194
        {
195
          ptr = strtok(NULL, ",");
196
        }
197
      }
198
    }
199
200
201
202
    if (error != 0)
203
    {
204
      uart_puts("error");
205
      uart_puts(RETURN_NEWLINE);
206
    }
207
    else
208
    {
209
210
      SwitchSocket(rgroup, rsocket, rstate);
211
      uart_puts("success");
212
      uart_puts(RETURN_NEWLINE);
213
    }
214
215
  }
216
}
217
218
219
220
int parse_assignment (char input[16]) {
221
  char *pch;
222
  char cmdValue[16];
223
  // Find the position the equals sign is
224
  // in the string, keep a pointer to it
225
  pch = strchr(input, '=');
226
  // Copy everything after that point into
227
  // the buffer variable
228
  strcpy(cmdValue, pch+1);
229
  // Now turn this value into an integer and
230
  // return it to the caller.
231
  return atoi(cmdValue);
232
}
233
234
235
void print_value (char *id, int value) {
236
  char buffer[16];
237
  itoa(value, buffer, 16);
238
  uart_puts(id);
239
  uart_putc('=');
240
  uart_puts(buffer);
241
  uart_puts(RETURN_NEWLINE);
242
}
243
244
void uart_ok() {
245
  //uart_puts("OK");
246
  //uart_puts(RETURN_NEWLINE);
247
}
248
249
void copy_command () {
250
  // Copy the contents of data_in into command_in
251
  memcpy(command_in, data_in, 16);
252
  // Now clear data_in, the UART can reuse it now
253
  memset(data_in, 0, 16);
254
}
255
256
void process_command() {
257
  if(strcasestr(command_in,"rfs") != NULL){
258
    if(strcasestr(command_in,"=?") != NULL)
259
      uart_puts_P("Used to Switch RF Socket rfs=GROUP,SOCKET, STATE");
260
    else
261
      remotepwr(command_in);
262
  }
263
} 
264
265
void process_uart(){
266
  /* Get received character from ringbuffer
267
   * uart_getc() returns in the lower byte the received character and 
268
   * in the higher byte (bitmask) the last receive error
269
   * UART_NO_DATA is returned when no data is available.   */
270
  unsigned int c = uart_getc();
271
  
272
  if ( c & UART_NO_DATA ){
273
    // no data available from UART 
274
  }
275
  else {
276
    // new data available from UART check for Frame or Overrun error
277
    if ( c & UART_FRAME_ERROR ) {
278
      /* Framing Error detected, i.e no stop bit detected */
279
      uart_puts_P("UART Frame Error: ");
280
    }
281
    if ( c & UART_OVERRUN_ERROR ) {
282
      /* Overrun, a character already present in the UART UDR register was 
283
       * not read by the interrupt handler before the next character arrived,
284
       * one or more received characters have been dropped */
285
      uart_puts_P("UART Overrun Error: ");
286
    }
287
    if ( c & UART_BUFFER_OVERFLOW ) {
288
      /* We are not reading the receive buffer fast enough,
289
       * one or more received character have been dropped  */
290
      uart_puts_P("Buffer overflow error: ");
291
    }
292
    
293
    // Add char to input buffer
294
    data_in[data_count] = c;
295
    
296
    // Return is signal for end of command input
297
    if (data_in[data_count] == CHAR_RETURN) {
298
      // Reset to 0, ready to go again
299
      data_count = 0;
300
      uart_puts(RETURN_NEWLINE);
301
      
302
      copy_command();
303
      process_command();
304
      uart_ok();
305
    } 
306
    else {
307
      data_count++;
308
    }
309
    //Local echo of command (in terminal mode)
310
    uart_putc( (unsigned char)c );
311
  }
312
}
313
314
315
316
317
318
319
320
321
int main(void) { 
322
  
323
  //########################################INIT+++##########################
324
 
325
  uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) ); //Select Baud Rate and initialize library
326
  sei();                         //Needed for UART Library - Enable global Interrupt
327
  uart_puts("Hey Dude!");
328
  uart_puts(RETURN_NEWLINE);
329
  PORTD |= (1<<RF_PIN);
330
  
331
 
332
 while (1) {
333
  process_uart();
334
335
  } 
336
}

von MaWin O. (Gast)


Lesenswert?

data_in kann durch Nutzerdaten zum Überlauf gebracht werden und 
command_in kann möglicherweise keine NUL-Terminierung haben.

von M. W. (Gast)


Lesenswert?

Hi,

das Kommando ist sehr kurz gehalten.
Es schaltet nur die Steckdosen ein und aus.

Hier ein Beispiel:

rfs=0FF0F,B,1

Das komische ist ja, das wenn ich die Optimazation in AVR Studio 
ausschalte (-O0) funzt alles. Bei (-01) knallt es immer an der Selben 
stelle:

>SuHey Dude!

Der ersten 2 Ziffern von Success werden ausgegeben. Ich habe auch schon 
mit dem ICE-Debugger nanchgeschaut, es schein alles zu passen und mit 
Optimization plötzlich: Crash. Reproduzierbar.

von Theor (Gast)


Lesenswert?

In der Regel liegt so etwas an unentdeckten Fehlern im Code. Denn der 
Compiler macht mit an Sicherheit grenzender Wahrscheinlichkeit nichts 
falsch. Eher hast Du Code geschrieben, der dem Compiler Deutungen offen 
lässt, die Du nicht beabsichtigt hast.

Leider ist der Code teilweise unübersichtlich. Ich sehe nichts 
Offensichtliches, aber eine Menge Stellen, die ich so nicht schreiben 
würde.


Der erste Rat wäre, alle Warnungen einzuschalten -Wall und alle so 
ausgegebenen Warnungen zu beseitigen.

Der zweite wäre, soviel wie möglich defensiv zu programmieren. Etwa 
strncpy anstelle von strcpy und so weiter. Wo das nicht möglich ist, 
Prüfungen auf Array-Längen einzubauen.

Der dritte, dieses ziemlich komplizierte if-geraffel streng zu straffen. 
Verschachtelte Prüfungen auf die selbe Bedingung sind unnötig und 
fehleranfällig.

Falls das dann immer noch auftritt, den Code hier nochmal - als Anhang - 
posten.

von Hubert (Gast)


Lesenswert?

Delay weg optimiert?

von Kaj (Gast)


Lesenswert?

Hat zwar nichts mit dem Problem zu tun, aber was mit auffaellt:
1
if (...); else
2
{
3
    ...
4
}
Das Semikolon hat da nichts zu suchen.

Und auch sonst:
1
if (error != 0); else
Das hast du 2x drin. Wozu soll das gut sein? Das macht den Code nur 
schwerer zu lesen und zu verstehen. Warum machst du das nicht 
ordentlich? Denn was da steht, mit den leeren if und dem else ist nichts 
anderes als das:
1
if (error == 0)
2
{
3
4
}
1
          if (rstate == 0 || rstate == 1); else
2
          {
3
            error = 1;
4
          }
5
        }
6
        if (error != 0); else
7
        {
8
          ptr = strtok(NULL, ",");
9
        }
Sorry, aber der Code ist echt schwer zu lesen. Raeum den mal bitte auf, 
dann ist es auch leichter einen Fehler zu finden...

von neuer PIC Freund (Gast)


Lesenswert?

Du musst spätestens alle (MAGIGNUMBER-1) ein CHAR_RETURN empfangen. 
Ansonsten schredderst du dein RAM.

von Stefan F. (Gast)


Lesenswert?

Wenn du in einem Elektronik Forum schreibst, dass es "Knallt" dann 
verstehe ich das im Sinne des Wortes. Bei Dir hat aber wohl doch nichts 
geknallt.

Du meinst, dass dein Programm ausfällt. Da solltest du mal untersuchen, 
an welcher Stelle das passiert. Deine Aussage, dass es immer die selbe 
Stelle sei, hilft nicht weiter und ist womöglich sogar irreführend.

Füge die Ausgabe von Debug Meldungen hinzu, damit du daran ablesen 
kannst, ab wo das Programm nicht mehr so abläuft, wie geplant. Eventuell 
hilft Dir dabei meine Vorlage: 
http://stefanfrings.de/avr_hello_world/index.html

Aber als aller erstes solltest Du Theors und Kajs Vorschläge umsetzen. 
Danach löst sich dein Problem womöglich schon in Luft auf, oder Dir 
fällt die Fehlerursache selbst auf. Denn gut lesbarer Code enthält 
tendenziell weniger Fehler und die sind dort offensichtlicher.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Kaj schrieb:
> Das Semikolon hat da nichts zu suchen.

Wie ein Compiler so etwas ohne Fehlermeldungen übersetzen kann, ist mir 
gelinde gesagt ein Rätsel.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Rufus Τ. F. schrieb:
> Wie ein Compiler so etwas ohne Fehlermeldungen übersetzen kann, ist mir
> gelinde gesagt ein Rätsel.

warum? das ist doch einfach nur ein leerer if-zweig, kein fehler wo der 
compiler was zu sagen muss.

der coding style hat was von wildwest romantik à la ein cowboy reitet 
durch die wüste der ahnungslosen.

nahezu unlesbar für mein spatzenhirn. ich würde mal strukuren andenken 
für die commando verarbeitung und sinnvolle namen benutzen anstatt 
nahezu sinnlose kommentare.

respekt an alle die da noch so nett bleiben konnten.


mt

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Apollo M. schrieb:
> warum? das ist doch einfach nur ein leerer if-zweig, kein fehler wo der
> compiler was zu sagen muss.

Das stimmt natürlich; für mich ist so etwas so grindig, daß ich es 
automatisch als Fehler betrachte.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

... "grindig" kannte ich noch nicht - danke wieder was dazu gelernt!

meine triggerschwelle ist noch niedriger - ich bekomme schon eine krise, 
wenn ich so etwas sehe

 if (error != 0) ...

oder

 if (error == 0) ...

anstatt

 if (error) ...

und

 if (!error) ...

: Bearbeitet durch User
von M.K. B. (mkbit)


Lesenswert?

Ich kann den anderen Posts nur zustimmen. Warnings sind immer hilfreich 
und Längen sollten geprüft werden, damit man den Fehler erkennt, wenn 
dieser auftritt und nicht erst durch späteres Fehlverhalten.

Meine Vermutung ist, dass sich durch die Optimierung die Verteilung der 
Variablen auf dem Stack ändert. Damit kann sich das Verhalten bei einem 
Zugriff außerhalb des Arrays verändern. Und Zugriffe außerhalb des 
Arrays sind undefiniert, d.h. das Programm darf machen, was es will.

Bei einem kurzen Blick auf den Code ist mir folgendes aufgefallen.

process_uart()
Kommt kein CHAR_RETURN innerhalb von 16 Zeichen, dann schreibst du über 
den Puffer hinaus. Was machst du, wenn Teile eines Kommandos verloren 
gehen?

process_command()
command_in hat nicht zwingend einen '\0' als letztes Zeichen ('\n' ist 
für c-strings eine ganz normales Zeichen). Die Stringfunktionen 
verlassen sich aber darauf, dass ein String mit '\0' endet. strcasestr 
hört damit nie auf, außer es findet zufällig eine null auf seinem Weg 
durch den Speicher. Irgendwann wird es aber einen ungültigen 
Adressbereich lesen wollen, was in der Regel zu einer Exception oder 
einem Reset des Controllers führt.

remotepwr()
Gleiches Problem wie process_command().

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Rufus Τ. F. schrieb:
> Kaj schrieb:
>> Das Semikolon hat da nichts zu suchen.
>
> Wie ein Compiler so etwas ohne Fehlermeldungen übersetzen kann, ist mir
> gelinde gesagt ein Rätsel.
So, jetzt auch wieder als angemeldeter Nutzer, war mir gestern zu spaet 
bzw. zu frueh :D

Ich hab mal folgendes in Eclipse eingehackt:
1
int main(void) {
2
    int i = 5;
3
4
    if (i == 4); else
5
    {
6
        printf("i is not 4\n");
7
    }
8
9
    return 0;
10
}
Da zeigt der Editor immerhin eine Meldung und markiert das Semikolon:
1
Suspicious semicolon

Eine gute IDE (ob Eclipse das ist, darueber kann man streiten) ist durch 
nichts zu ersetzen.

Das das compiliert liegt aber wirklich an dem leeren if. Versucht man 
das if mit Leben zu fuellen gibt es, wie zu erwarten, einen Error:
1
$ make
2
gcc -std=c11 -O0 -g3 -Wall -Wextra -Wunused-variable -Wunreachable-code -Wsign-conversion -Wmissing-braces -Wparentheses -Wsequence-point -pedantic -o c_test.elf src/main.c
3
src/main.c: In function 'main':
4
src/main.c:8:16: warning: suggest braces around empty body in an 'if' statement [-Wempty-body]
5
     if (i == 4); {
6
                ^
7
src/main.c:8:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
8
     if (i == 4); {
9
     ^~
10
src/main.c:8:18: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
11
     if (i == 4); {
12
                  ^
13
src/main.c:10:6: error: 'else' without a previous 'if'
14
     }else {
15
      ^~~~
16
make: *** [Makefile:37: c_test] Error 1

Wie auch immer:
Eine Warnung vom Compiler waere schon schoen gewesen (beim if(...); 
else).

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

off topic - but very important!

... für die, die interesse haben an effektives flag handling mit 
attiny/atmega/... welche extra gpio register im io address space dafür 
haben, die bit orientiert mit sbi/cbi manipuliert werden. das spart dann 
bei vielen flags richtig size und space. der atmega328P hat 3 gpio also 
platz für 24 flags! die nachfolger kommen mit 5 gpio daher.

und noch ein vorteil - nach reset werden die register via hw auf null 
initialisiert!

hier am beispiel der variable "int error" einsetztbar aus der dann 
errorFLAG wird. alles für die 3 gpio's beliebig erweiterbar auf 24 
flags.

enum error_t {OK, NOK};

typedef struct {
  enum error_t eflag:1;
  enum error_t      :7;
} flag_t;

#ifdef DEBUG //flags defined as byte/int structure to get symbol infos
  volatile flag_t flags;
  #define errorFLAG ((flag_t*) &flags)->eflag

#else //flag defined as gpio bit structure
  #define errorFLAG ((flag_t*) &GPIOR0)->eflag

#endif


also flash/ram code space gespart und speed verbessert!

und noch ein link zu einem recht guten program 
(https://github.com/sepich/SynNotes) um code snippets strukturiert 
abzulegen und wieder zu finden.
gerade dann, wenn die ide so etwas nicht mitbringt - das program bekommt 
eine klare empfehlung!


mt

von Egon N. (egon2321)


Lesenswert?

Du benutzt relativ wenige IO Funktionen. Imho würde es reichen, diese 
auszukommentieren und anschließend kannst du deinen Code durch Valgrind 
jagen. Der findet bei C eigentlich immer irgendetwas das man selbst 
übersehen hätte.

Kompilier immer mit -Wall, -Wconversion und den ganzen Optionen um 
möglichst viele Warnungen zu erhalten, generell würde ich dazu raten 
nichts kompilieren zu lassen was auch nur irgendeine Warnung ausspuckt.



An welcher Stelle stürzt dein Programm nun immer ab? Diese Information 
wäre recht hilfreich. ;)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Kaj G. schrieb:
> Wie auch immer:
> Eine Warnung vom Compiler waere schon schoen gewesen (beim if(...);

Na die ist doch da!

> src/main.c:8:16: warning: suggest braces around empty body in an 'if'
> statement [-Wempty-body]
>      if (i == 4); {
>                 ^

D.h. wenn man gerne einen leeren Block hätte dann sollte man {} (braces) 
verwenden statt ;

von devzero (Gast)


Lesenswert?

Egon N. schrieb:
> An welcher Stelle stürzt dein Programm nun immer ab? Diese Information
> wäre recht hilfreich. ;)

Das schrieb er doch bereits mehrmals.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Johann L. schrieb:
> Kaj G. schrieb:
>> Wie auch immer:
>> Eine Warnung vom Compiler waere schon schoen gewesen (beim if(...);
>
> Na die ist doch da!
Ja, aber nur, wenn:
1
if(...); {
2
    ...
3
} else {
4
    ...
5
}

Mein "eine Warnung waere schoen" bezog sich aber auf:
1
if(...); else {
2
    ...
3
}
Da gibt es naemlich keine Warnung. Auch nicht mit -Wall -Wextra.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Kaj G. schrieb:
> Mein "eine Warnung waere schoen" bezog sich aber auf:if(...); else {
>     ...
> }
> Da gibt es naemlich keine Warnung. Auch nicht mit -Wall -Wextra.

warum auch? nur weil es grindig aussieht?!
von der syntax ist alles im grünen bereich.

ich empfehle mal das buch "moderne c-programmierung" von schellong, zu 
finden als pdf!

schon mal was von ko-logik bei c gehört? mit dem buch lernt man, was 
c-programmierung wirklich bedeutet kann.

ich denke, die meisten legen das buch aber schell beiseite. weil das ist 
richtig starker tabak und eher nicht für unser "niveau" hier.

von codeguy (Gast)


Lesenswert?

Das ist absolut verheerender Code. "Hoffnung" scheint Dein Schlüsselwort 
zu sein.

1) Wilde Konstanten werden ohne Begründung (Kommentar!) irgendwo im Code 
verwendet:

> char group[5]
> ...
> char query[12]
> ...
> char command_in[16]
> ...
> char input[16]
> ...
> char rgroup[16]

Für Konstanten gibt es #define, und dann ist ein Kommentar fällig, warum 
die Größe genau die richtige ist!


2) Ein wilder for-if-else-Verhau:

> if (error == 0)
> ...
> for (int i = 0; i < 3; i++)
>   ...
>   if (i == 0 && error == 0)
>   ...
>   if (i == 1 && error == 0)
>   ...
>   if (i == 2 && error == 0)

Schon mal überlegt, dass der Code leichter zu lesen wäre, wenn die 
Schleife eliminiert wird?!


3) Und dann diese vielen kleinen unschönen Details:

- Man schreibt niemals nie:
> if (rstate == 0 || rstate == 1); else
> {
>   error = 1;
> }

sondern:
if ( rstate != 0 && rstate != 1 ) error = 1;

oder:
if ( rstate < 0 || rstate > 1 ) error = 1;


- Wenn du mit if i == 0 bis i == 2 testest, dann sollte die zugehörige 
Schleife auch diese Zahlen verwenden: "for (int i = 0; i <= 2; i++)"

(Aber ich sagte ja schon, ganz weg mit der Schleife!)


4) Schmerzhafte Formatierung:

Es gibt zu viele gute Gründe, folgendes:
> }
> else
> {
zu ersetzen durch "} else {". Z.B. dass weniger Zeilen vollgemüllt 
werden, oder dass ein zu einem Block gehörendes "else" nicht so leicht 
übersehen werden kann.

5) Ach, bitte noch mal neu.

von codeguy (Gast)


Lesenswert?

Jetzt ersthaft: Schreibe das Programm ein zweites Mal und nimm ein gute 
integrierte Entwicklungsumgebung (IDE), die Dir Deinen Code besser 
formatiert und Dir sogar Tips gibt, wie Du redundante Formulierungen 
verbessers. Es wird so viel einfacher!

von codeguy (Gast)


Lesenswert?

Kaj G. schrieb:
> $ make
> gcc -std=c11 -O0 -g3 -Wall -Wextra -Wunused-variable -Wunreachable-code
> -Wsign-conversion -Wmissing-braces -Wparentheses -Wsequence-point
> -pedantic -o c_test.elf src/main.c
> src/main.c: In function 'main':
> src/main.c:8:16: warning: suggest braces around empty body in an 'if'
> statement [-Wempty-body]
>      if (i == 4); {
>                 ^
> src/main.c:8:5: warning: this 'if' clause does not guard...
> [-Wmisleading-indentation]
>      if (i == 4); {
>      ^~
> src/main.c:8:18: note: ...this statement, but the latter is misleadingly
> indented as if it were guarded by the 'if'
>      if (i == 4); {
>                   ^
> src/main.c:10:6: error: 'else' without a previous 'if'
>      }else {
>       ^~~~
> make: *** [Makefile:37: c_test] Error 1

Du hast das Semikolon nicht verstanden!

Es beendet ein Statement. Geschweifte Klammern ermöglichen einen Block 
an einer Stelle, wo sonst nur ein Statement erlaubt ist.

Nach if kommen runde Klammern mit der zu prüfenden Bedingung und danach 
ein Statement:

if ( bedingung ) statement ;

Wie eben geschrieben, kannst Du statt des stements einen Block mit 
mehreren Statements setzen:

if  ( bedingung ) { statement ; statement ; statement ; }

Wenn Du schreibst:

if ( bedingung ) ;

dann hast Du ein leeres Statement definiert, d.h. es passiert nichts im 
if-Zweig. nun kannst Du "else statement ;" anhängen und vielleicht 
dämmert Dir nun, warum der Compiler oben genau das tut, was jeder 
erfahrene C-Programmierer erwarten würde.

Und die von Dir gewünschte Warnung liefert bereits Eclipse!

von Hubert (Gast)


Lesenswert?

Kann denn einer der Semikolon Fritzen was zumThema sagen? Was wird 
kaputt optimiert?

von codeguy (Gast)


Lesenswert?

Nichts wird "kaputt optimiert", das Programm ist schon "kaputt" und wenn 
es scheinbar funktioniert, dann nur "zufällig". ENDE

von Einer K. (Gast)


Lesenswert?

Nichts wird kaputt optimiert.
Der Kot ist kaputt..
Und kaputter Kot verhält sich manchmal anders, als man meint.

Also Aufräumen!
Pingelig auf Array Bereichsüberschreitungen achten.
Alle Zeiger immer gültig?

Ist der Code mal Fehlerfrei, dann tut ihm die Optimierungsstufe auch 
nicht mehr weh.

von codeguy (Gast)


Lesenswert?

Kaj G. schrieb:
>>> Eine Warnung vom Compiler waere schon schoen gewesen (beim if(...);
>>
>> Na die ist doch da!
> Ja, aber nur, wenn:if(...); {
>     ...
> } else {
>     ...
> }
>
> Mein "eine Warnung waere schoen" bezog sich aber auf:if(...); else {
>     ...
> }
> Da gibt es naemlich keine Warnung. Auch nicht mit -Wall -Wextra.

Oh nein, Du weisst auch den Unterschied zwischen warning und error 
nicht. Obiger Fall ist ein error (warum? Hab ich vorhin erklärt: der 
Block nach dem leeren Statement hat nichts mehr mit dem if zu tun, daher 
kann kein else angehängt werden).

Der zweite Fall ist ein warning "leeres statement"

Das hier ist insgesamt der grausamste Fall an Programmierhalbwissen, mit 
dem ich es in den letzten Jahren zu tun hatte. Bitte Kernighan/Ritchie 
2nd Ed. durcharbeiten und verstehen. Ansonsten vielleicht besser 
handwerken.

von codeguy (Gast)


Lesenswert?

if ( Kernighan/Ritchie 2nd Ed. durchgearbeitet && verstanden )
  programmieren;
else
  handwerken;

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

codeguy schrieb:
> Nichts wird "kaputt optimiert", das Programm ist schon "kaputt" und wenn
> es scheinbar funktioniert, dann nur "zufällig". ENDE

korrekt, das war auch mein erster gedanke!

wenn ich lese

"... In der Hoffnung hier in dem Forum treibt sich ein Profi rum für den 
das ein Kinderspiel ist."

dann ist das vom gleichen tenor wie z.b. bei ebay, wenn da schrott 
verkauft werden soll.

"... ich habe keine ahnung von dem gerät, aber für einen profi sicher 
einfach zu reparieren ... vielleicht nur die sicherung, oder eine 
lockere schraube,
aber sonst funktioniert das gerät super!"

Äh?!


mt

von codeguy (Gast)


Lesenswert?

Ich bin direkt etwas ärgerlich, aber es tut mir auch leid, dass jemand 
an dem Programm 2 Wochen verschwendet hat. Ich bin Profi und es ist viel 
einfacher für mich, es neu zu schreiben, als all die Unschönheiten zu 
beseitigen. Denn dann fängt man an, die Fehler zu sehen, die auch noch 
korrigiert werden müssen. Und dann stellt man fest, dass das Konzept 
falsch durchdacht ist, und muss sowieso alles neu programmieren.

Jetzt aber wirklich ENDE

von Hubert (Gast)


Lesenswert?

@codegay
Du bist kein Profi, sondern ne arme Sau, die hier auf dicke Hose macht
 Schreib das doch gerade um und Zeit was du fürs Rakete bist.

Aber das kannst du gar nicht, Troll.

von Gregor M. (Gast)


Lesenswert?

Was mich in diesem Forum hier tatsächlich wundert (in Gegensatz zu 
vielen Englisch-Sprachigen pendants) das hier augenscheinlich 
Reihenweise Profis rumlaufen die eine Dicke Lippe riskieren und "alles 
anders machen" würden.

Ich programmiere selber seit vielen Jahren und im Prinzip stimme ich zu. 
Nur für mich sieht das aus, als lernt da gerade jemand seine ersten 
Schritte was ich durchaus begrüße.

Aber anstatt hier Erfahrungen auszutauschen und zu Helfen wird (leider 
nicht nur in diesem Thread) draufgehauen was das Zeug hält und es wird 
zum Teil weit unter die Gürtellinie geschossen.

Was soll das denn sein? Es gibt weder "Grindigen" noch "Perfekten" Code, 
aber es gibt Erfahrungswerte und "Best Practices" die man unerfahrenen 
doch Vermitteln kann. Auch wenn vieles Hier stimmt, ist der Ton einfach 
brutal unagebracht und jemand der sich versucht mit einem Thema zu 
beschäftigen wird gleich zu Beginn niedergemacht.

Ich frage mich wie viele Leute hier tatsächlich Profis sind und wie 
viele Jahre Ihr alle gebraucht habt um so weit zu kommen. Und wenn ich 
ehrlich bin sehe ich es als große Leistung an auch erst den Schritt in 
die Programmierung zu wagen - Gerade C ist nicht sehr einfach zu lernen 
--> Pointer.

Aus meiner Erfahrung und auch bei meinen Uni-Abgängern im Büro kommt es 
in erster Linie darauf an Erfahrungen auszutauchen und zu HELFEN anstatt 
draufzuhauen und blöde sprüche zu Kloppen.

Traurig. Ich bin in vielen Foren (speziell in USA) unterwegs aber solch 
dummen sprüche sind scheinbar in Deutschland (und auch Österreich --> 
Grindig) üblich.

Mich persönlich würde ja mal interessieren wie die großen Maker hier 
programmieren.

@Michael: Kopf hoch und nicht entmutigen lassen. Der Code gehört 
ordentlich aufgeräumt aber das kommt mit der Erfahrung!

Gute Nacht und Gutes Gelingen - Und dem Rest ein bischen Einsicht und 
die Erkentniss das JEDER mal kein angefangen hat.

Gregor

von codeguy (Gast)


Lesenswert?

Hatte gestern einen schlechten Tag und möchte das wieder gut machen:

  Das hier ersetzt den bisherigen Aufruf von remotepwr:
1
    if ( remotepwr( input ) == false ) {
2
      uart_puts( "error" );
3
      uart_puts( RETURN_NEWLINE );
4
    }
  Das ist die umgeschriebene Kernfunktion:
1
  boolean remotepwr( char input[16] ) {
2
    char cmdValue[16];
3
    char *ptr;
4
    char rgroup[16];
5
    int rsocket = 0;
6
    char rstate = 0;
7
    int error = 0;
8
9
    // Find the position of the equal sign in the string, keep a pointer to it
10
    ptr = strchr( input, '=' );
11
    if ( ptr == NULL ) return false;
12
13
    // Copy everything behind the equal sign into the buffer
14
    strcpy( cmdValue, ptr + 1 );
15
    cmdValue[9] = '\0';
16
17
    // ??? Now turn this value into an integer and return it to the caller
18
    ptr = strtok( cmdValue, "," );
19
    if ( ptr == NULL ) return false;
20
21
    // Parameter 0 (1) is group
22
    strcpy( rgroup, ptr );
23
    rgroup[5] = '\0';
24
    if ( strlen( rgroup ) < 5 ) return false;
25
26
    ptr = strtok( NULL, "," );
27
    if ( ptr == NULL ) return false;
28
29
    // Parameter 1 (2) is socket
30
    // Check if Parameter 2 only contains a letter from A to E or a to e
31
    if ( ( ptr[0] >= 'a' && ptr[0] <= 'e' ) || ( ptr[0] >= 'A' && ptr[0] <= 'E' ) )
32
      rsocket = ptr[0];
33
    else
34
      return false;
35
36
    ptr = strtok( NULL, "," );
37
    if ( ptr == NULL ) return false;
38
39
    // Parameter 2 (3) is state
40
    rstate = atoi( ptr );
41
    if ( rstate != 0 && rstate != 1 ) return false;
42
43
    SwitchSocket( rgroup, rsocket, rstate );
44
    uart_puts( "success" );
45
    uart_puts( RETURN_NEWLINE );
46
    return true;
47
  }

von codeguy (Gast)


Lesenswert?

Offensichtlich wird hier ein String der Form "=ccccc,[a-eA-E],[01]," 
geparst und in seine Einzelteile zerlegt. Das sollte als Kommentar vor 
der Funktion stehen.

Der letzte "ptr = strtok( NULL, "," );" erwartet ein Komma, was ev. ein 
Fehler sein könnte. Aber das kann ja jemand anders herausfinden. Ich 
wollte nur einen Denkanstoß geben und mein schlechtes Verhalten von 
gestern korrigieren.

Nichts für ungut

von Blechbieger (Gast)


Lesenswert?

Generell helfen IDEs und Autoformater wie Astyle bei der 
Codeschachtelung. Liefern sie seltsame Einrückungen stimmt etwas mit den 
Klammern oder Semikolons nicht. Auch kann Astyle erzwingen dass nach if 
immer ein Block kommt statt eines einzelnen Statements.

von codeguy (Gast)


Lesenswert?

Nun hab ich mich daran festgebissen, daher hier die nächste Iteration, 
nämlich das Konzept des Parsings (kein strtok, kein Hilfsbuffer):
1
  boolean remotepwr( char input[16] ) {
2
    char *left, *right;
3
    char rgroup[6];      // The expected string needs exactly 6 chars
4
    int rsocket = 0;
5
    char rstate = 0;
6
    int error = 0;
7
8
    // Find the position of the equal sign in the string
9
    if ( (left = strchr( input, '=' )) == NULL ) return false;
10
11
    // left now points to the first character of rgroup
12
    ++left;
13
14
    // Find the position of the comma behind the = in the string
15
    if ( (right = strchr( left, ',' )) == NULL ) return false;
16
17
    // rgroup should exactly be 5 characters long
18
    if ( right - left != 5 ) return false;
19
20
    strncpy( rgroup, left, 5 );
21
    rgroup[5] = '\0';
22
23
    // Parameter 1 (2) is socket. right now points to it (or whatever)
24
    ++right;
25
26
    // Check if Parameter 2 only contains a letter from A to E or a to e
27
    if ( ( *right >= 'a' && *right <= 'e' ) || ( *right >= 'A' && *right <= 'E' ) )
28
      rsocket = *right;
29
    else
30
      return false;
31
32
    // Find the position of the next comma behind the comma in the string
33
    if ( (right = strchr( right, ',' )) == NULL ) return false;
34
35
    // right now points to the 3rd parameter (or whatever)
36
    ++right;
37
38
    // Parameter 2 (3) is state
39
    switch ( *right ) {
40
    case '0': rstate = 0; break;
41
    case '1': rstate = 1; break;
42
    default: return false;
43
    }
44
45
    // Make sure that the number has only 1 digit and that the
46
    // input string is not longer
47
    if ( right[1] != '\0' ) return false;
48
49
    SwitchSocket( rgroup, rsocket, rstate );
50
    uart_puts( "success" );
51
    uart_puts( RETURN_NEWLINE );
52
    return true;
53
  }

von Markus F. (mfro)


Lesenswert?

M. W. schrieb:

> void BuildQuery(char *query, char *group, char socket, int state){
>
>   strcat(query, group);
>
>   switch(socket){
>     case 'a': case 'A': strcat(query, "0FFFF"); break;
>     case 'b': case 'B': strcat(query, "F0FFF"); break;
>     case 'c': case 'C': strcat(query, "FF0FF"); break;
>     case 'd': case 'D': strcat(query, "FFF0F"); break;
>     case 'e': case 'E': strcat(query, "FFFF0"); break;
>     default:; break;
>   }
>
>   if(state>0){ strcat(query,"0F");}
>   else{ strcat(query, "F0"); }
> }
>
> void SwitchSocket(char group[5], char socket, int state){
>
>   char query[12] = "";
>   BuildQuery(query, group, socket, state);
>

Mir scheint, hier geht vor lauter "Jehova"-Schreien der Blick für's 
Wesentliche verloren.

query ist als char[12] deklariert.

Im oben gezeigten Ausschnitt werden fünf Zeichen Nutzdaten in query 
kopiert, dann kommen (in BuildQuery()) nochmal fünf Zeichen dazu und 
anschliessend werden (mit strcat()) noch zwei Zeichen hinten angehängt.

Macht in der Summe 12.

Der geneigte, unvoreingenommene Leser fragt sich: wo bleibt das 
Stringende-Kennzeichen?

Dass der Rest vom gezeigten Code nicht gerade schön ist, wurde ja schon 
ausführlich durchgehechelt ...

von codeguy (Gast)


Lesenswert?

Das allerwichtigste, nämlich aussagekräftige Fehlermeldungen, will ich 
nicht unterschlagen (übrigens hatte ich auch atoi() eliminiert, da es in 
diesem simplen Fall aus Effizienzgründen (Speicherbedarf, Laufzeit) 
vermieden werden kann):
1
  // Parse a string of the form "=ccccc,[a-eA-E],[01]\n"
2
  // and call SwitchSocket() with the three extracted parameters
3
  boolean remotepwr( char input[16] ) {
4
    char *left, *right;
5
    char rgroup[6];      // The expected string needs exactly 6 chars
6
    int rsocket = 0;
7
    char rstate = 0;
8
9
    // Find the position of the equal sign in the string
10
    if ( (left = strchr( input, '=' )) == NULL ) {
11
      uart_puts( "ERROR: No equal sign!\r\n" );
12
      return false;
13
    }
14
15
    // left now points to the first character of rgroup (1. parameter)
16
    ++left;
17
18
    // Find the position of the comma behind the = in the string
19
    if ( (right = strchr( left, ',' )) == NULL ) {
20
      uart_puts( "ERROR: No comma sign!\r\n" );
21
      return false;
22
    }
23
24
    // rgroup should exactly be 5 characters long
25
    if ( right - left != 5 ) {
26
      uart_puts( "ERROR: 1st parameter does not have 5 chars!\r\n" );
27
      return false;
28
    }
29
30
    strncpy( rgroup, left, 5 );
31
    rgroup[5] = '\0';
32
33
    // 2nd arameter is socket. right now points to it (or whatever)
34
    ++right;
35
36
    // Check if Parameter 2 only contains a letter from A to E or a to e
37
    if ( ( *right >= 'a' && *right <= 'e' ) || ( *right >= 'A' && *right <= 'E' ) )
38
      rsocket = *right;
39
    else {
40
      uart_puts( "ERROR: 2nd parameter is not a-e and A-E!\r\n" );
41
      return false;
42
    }
43
44
    // Find the position of the next comma behind the comma in the string
45
    if ( (right = strchr( right, ',' )) == NULL ) {
46
      uart_puts( "ERROR: No second comma symbol!\r\n" );
47
      return false;
48
    }
49
50
    // right now points to the 3rd parameter (or whatever)
51
    ++right;
52
53
    // 3. parameter is state
54
    switch ( *right ) {
55
    case '0': rstate = 0; break;
56
    case '1': rstate = 1; break;
57
    default:
58
      uart_puts( "ERROR: 3rd parameter is not '0' or '1'!\r\n" );
59
      return false;
60
    }
61
62
    // Make sure that the number has only 1 digit and that the
63
    // input string is not longer
64
    if ( right[1] != '\0' ) {
65
      uart_puts( "ERROR: 3rd parameter is longer than 1 symbol or input continues behind 3rd parameter!\r\n" );
66
      return false;
67
    }
68
69
    SwitchSocket( rgroup, rsocket, rstate );
70
    uart_puts( "success\r\n" );
71
    return true;
72
  }

Der Aufruf kann nun den Rückgabewert ignorieren, da die Fehlermeldungen 
direkt in der Funktion erzeugt und ausgegeben werden.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

codeguy schrieb:
> da die Fehlermeldungen
> direkt in der Funktion erzeugt und ausgegeben werden.
Aber will man das? Ich wuerde aus Sicht der Wartbarkeit lieber den 
Errorstring zusammenbauen, und an einer einzigen Stelle ausgeben.
Also irgendwie so (pseudocode):
1
if (...) {
2
    error_msg = "...";
3
}
4
5
if (...) {
6
    error_msg = "...";
7
}
8
9
if (...) {
10
    error_msg = "...";
11
}
12
13
if (error_msg) {
14
    uart_puts(error_msg);
15
}
Bzw. den String an den Aufrufer zurueckgeben. Dann kann man das ganze so 
gestallten, das es nur genau eine Stelle im Code gibt, wo die 
Schnittstelle bedient wird. Macht das ganze auch einfacher und wenn man 
Interrupts nutzt auch weniger fehleranfaellig (pseudocode):
1
int main(void)
2
{
3
    char error_msg[xxx] = {0};
4
    char status_msg[xxx] = {0};
5
6
    while (1) {
7
        foo(status_msg, error_msg);
8
        bar(status_msg, error_msg);
9
        
10
        if (status_msg[0]) {
11
            uart_puts(status_msg);
12
            memset(status_msg, 0, sizeof(status_msg));
13
        }
14
15
        if (error_msg[0]) {
16
            uart_puts(error_msg);
17
            memset(error_msg, 0, sizeof(error_msg));
18
        }
19
    }
20
}

Auch das staendige manuelle Anhaengen von "\r\n" wuerde ich in einer 
Funktion verstecken. Also irgendwie so (pseudocode):
1
void uart_puts_rn(char* msg)
2
{
3
    uart_puts(msg + "\r\n");
4
}
Ist weniger laestig, und man kann das Anhaengen nicht vergessen.

Auch die Zuweisungen innerhalb eines if-Statements sind nicht wirklich 
schoen, und werden zurecht von genuegend Tools angemeckert.

codeguy schrieb:
> übrigens hatte ich auch atoi() eliminiert, da es in
> diesem simplen Fall aus Effizienzgründen (Speicherbedarf, Laufzeit)
Bevor wir uns hier ueber Effizienz unterhalten, sollte erstmal die 
korrekte Funktion sichergestellt sein. Effizienz bringt nichts, wenn der 
Code immernoch fehlerhaft (im sinne von: "macht nicht was es soll") ist.

Da ich deiner Meinung nach ja aber eh keine Ahnung hab, solls mir auch 
egal sein.

von Marco H. (damarco)


Lesenswert?

ehrlich gesagt ist die Art in deinem Programm wie du mit Strings umgehst 
sehr problematisch. Auch mit Pointern ! Genau das wird dir in die Suppe 
spucken wenn der Compiler anfängt zu optimieren packt er die Sachen 
aneinander womit das in die Hose gehen muß.

: Bearbeitet durch User
von codeguy (Gast)


Lesenswert?

@ Kaj G.

Ja, wunderbar: lauter gute Vorschläge von Dir. Kann man alles umsetzen 
und verbessert das Programm weiter. Worauf ich hinauswollte ist, dass es 
unbedingt Sinn macht, Konzept und Struktur eines Programms nach 
Alternativen zu untersuchen. In obigem Fall ist einfach die 
Schleifenstruktur das falsche Konzept.

Ich würde tatsächlich die Fehler sofort ausgeben, wenn sie passieren, 
sonst kann es vorkommen, dass eine in error_msg gespeicherte 
Fehlermeldung durch einen nachfolgenden Absturz verschluckt wird und 
damit sinnlos wird.


@ Marco H.

Meinst Du mich?

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.