Forum: Compiler & IDEs Include Datei "richtig" benutzen


von HeaderFile (Gast)


Lesenswert?

Hi,

ich bin gerade dabei ein etwas größeres C Projekt richtig zu 
strukturieren.
Dabei bin ich leider auf ein paar Probleme gestoßen die mir noch nicht 
ganz klar sind.

In vielen Projekten habe ich gesehen, dass in den Header Files #includes 
gemacht werden.

Unter welchen Voraussetzungen ist es üblich #includes in eine Header 
Datei anstelle der entsprechenden C Datei zu schreiben ?

Ein weiteres "problem" betrifft globale Variablen. Es geht momentan 
darum den Code in übersichtliche Teilbereiche zu untergliedern. Es sind 
also keine unabhängigen Module.

Die globalen Variablen sind in den entsprechenden C Files definiert. Ist 
es nun üblich ein großes globales Headerfile anzulegen z.B. "global.h" 
und dort komplett alle globalen variablen mit extern davor aufzulisten ? 
Anschließend würde die "global.h" in alle C Files eingebunden die 
Zugriff auf eine der globalen Variablen benötigen. Oder ist es eher die 
übliche Art die extern definitionen wirklich nur in der jeweils 
zugehörigen header Datei zu setzen ?

Ich habe versucht mir ein paar open source C Projekte anzusehen um dort 
eventuell ein paar Tricks / Möglichkeiten abschauen zu können dabei bin 
ich oft auf folgenden Aufbau gestoßen:

test.h
1
test.h
2
#ifndef _TEST_H
3
#define _TEST_H
4
5
int varA
6
7
#endif

test.c
1
#include "test.h"
2
3
int varA

Wozu steht da int varA ohne extern in der Header Datei ? Ich war immer 
der Meinung das die Header Files später an der #include stelle in die C 
Datei eingesetzt werden. Ist dann nicht varA doppelt definiert was nicht 
erlaubt sein dürfte ?

von Peter II (Gast)


Lesenswert?

HeaderFile schrieb:
> Unter welchen Voraussetzungen ist es üblich #includes in eine Header
> Datei anstelle der entsprechenden C Datei zu schreiben ?

wenn du in einer Header Datei z.b. Datentypen aus einer andere Header 
Datei brauchst.

von Augen offen (Gast)


Lesenswert?

Ein Header beschreibt das Interface für ein C(++) Modul. Da kommt alles 
rein, was für die Nutzung des Modul gebraucht wird. Variablen, 
Funktionen & Co. sind "extern" zu deklarieren und im C(++) Modul global.

http://openbook.galileocomputing.de/c_von_a_bis_z/010_c_praeprozessor_001.htm#mjf374f026d04c7badd6109bab24ff1114

von heiner (Gast)


Lesenswert?

Und globale Variablen sind per Default extern, wenn also nicht static 
dabei steht.

von Fabian O. (xfr)


Lesenswert?

HeaderFile schrieb:
> Unter welchen Voraussetzungen ist es üblich #includes in eine Header
> Datei anstelle der entsprechenden C Datei zu schreiben ?

Wenn in der Header-Datei auf Datentypen verwiesen wird, die dort nicht 
definiert sind. Wenn also z.B. eine Struktur an eine Funktion übergeben 
werden muss und die Struktur in einer anderen Header-Datei definiert 
ist.

Jede Header-Datei sollte also alles inkludieren, was ein nichts-ahnender 
Benutzer der Datei benötigt, um die Schnittstelle benutzen zu können. Ob 
das erfüllt ist, kann man ganz einfach sicherstellen, indem man die 
H-Datei in der dazugehörigen C-Datei als allererstes inkludiert. Das 
darf keine Fehler geben.

Darüber hinaus sollte sie nichts weiter inkludieren, um möglichst 
eigenständitg zu sein.

> Ein weiteres "problem" betrifft globale Variablen. Es geht momentan
> darum den Code in übersichtliche Teilbereiche zu untergliedern. Es sind
> also keine unabhängigen Module.

Das ist schon mal suboptimal. Code sollte immer nach dem Prinzip "Lose 
Kopplung, starke Bindung" in Module aufgeteilt sein. Das heißt möglichst 
unabhängige Teilmodule mit klar definierten Schnittstellen. Wenn Module 
dagegen wechselseitig voneinander abhängen und gemeinsame Variablen 
nutzen (müssen), spricht das stark dafür, dass dieser Code nicht an der 
Stelle aufgeteilt gehört.

> Die globalen Variablen sind in den entsprechenden C Files definiert. Ist
> es nun üblich ein großes globales Headerfile anzulegen z.B. "global.h"
> und dort komplett alle globalen variablen mit extern davor aufzulisten ?
> Anschließend würde die "global.h" in alle C Files eingebunden die
> Zugriff auf eine der globalen Variablen benötigen. Oder ist es eher die
> übliche Art die extern definitionen wirklich nur in der jeweils
> zugehörigen header Datei zu setzen ?

Die Deklaration (mit extern) gehört in die H-Datei mit dem gleichen 
Namen wie die C-Datei, nirgendwo anders hin. Außerdem ist dringend zu 
empfehlen, dass der Variablenname als Prefix den Modulnamen enthält.

Grundsätzlich sollten globale Variablen wo immer möglich vermieden 
werden. Außer in wirklich performancekritischen Situationen gibt es 
keinen Grund, globale Variablen zu verwenden. Statische Variablen mit 
get/set-Funktion erfüllen den gleichen Zweck. Wobei reine 
get/set-Funktionen ohne weitere Bedeutung oft ebenfalls schon ein Indiz 
für schlecht gekapselten Code sind.

> Ich habe versucht mir ein paar open source C Projekte anzusehen um dort
> eventuell ein paar Tricks / Möglichkeiten abschauen zu können dabei bin
> ich oft auf folgenden Aufbau gestoßen:
>
> [,..]
>
> Wozu steht da int varA ohne extern in der Header Datei ? Ich war immer
> der Meinung das die Header Files später an der #include stelle in die C
> Datei eingesetzt werden. Ist dann nicht varA doppelt definiert was nicht
> erlaubt sein dürfte ?

Völlig richtig, daher ist das Beispiel einfach nur Murks. Nicht 
nachmachen.

von Karl H. (kbuchegg)


Lesenswert?


: Bearbeitet durch User
von Fabian O. (xfr)


Lesenswert?

Fabian O. schrieb:
> Darüber hinaus sollte sie nichts weiter inkludieren, um möglichst
> eigenständitg zu sein.

Das lässt sich übrigens auch leicht testen: Wenn man ein "include <xy>" 
in einer H-Datei vom Anfang der H-Datei ganz ans Ende (also unter alle 
Funktionsdeklarationen etc.) verschieben kann, ohne dass sich der 
Compiler beschwert, dann wird dieser Include in der H-Datei nicht 
benötigt und gehört dort dementsprechend nicht hin.

Gilt auch für Includes in C-Dateien. ;-)

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

Fabian O. schrieb:
> Das lässt sich übrigens auch leicht testen: Wenn man ein "include <xy>"
> in einer H-Datei vom Anfang der H-Datei ganz ans Ende (also unter alle
> Funktionsdeklarationen etc.) verschieben kann, ohne dass sich der
> Compiler beschwert, dann wird dieser Include in der H-Datei nicht
> benötigt und gehört dort dementsprechend nicht hin.
>
> Gilt auch für Includes in C-Dateien. ;-)

Ja, Nein, vielleicht.

Was ist, wenn eine andere Headerdatei schon die benötigten Informationen 
einbindet?
1
foo.h:
2
# include <limits.h>
3
4
5
bar.h:
6
# include <limits.h>  // diese Zeile könntest du löschen. Ist aber nicht sinnvoll
7
# include "foo.h"
Gilt auch für Includes in C-Dateien.

von HeaderFile (Gast)


Lesenswert?

Vielen Dank das hat schonmal ein ganzes Stück geholfen :)

Zwei kleine Fragen kamen jetzt allerdings doch noch dazu.

Der allgemeine trend geht in Richtung globale Variablen vermeiden. Okay 
das ist soweit nachvollziehbar.

Bei mir sind momentan die meisten der globalen Variablen aus den 
folgenden Gründen global.

- Es sind flags die aus einem Interrupt gesetzt werden um dem 
Hauptprogramm Timeouts, Zeitabläufe, Freigabesignale usw. zu erteilen. 
Diese müssen meiner Meinung nach zwingend global sein oder gibt es da 
doch bessere Lösungen ?

- Es sind relativ große Arrays bei denen ich ein schlechtes Gefühl habe 
wenn sie bei einem Methodenaufruf auf dem Stack laden. Hier habe ich 
jetzt allerdings Zweifel. Wenn ein Array innerhalb einer methode mit 
static deklariert wird landet das Array dann überhaupt auf dem Stack ? 
Bzw unterscheidet sich ein static Array innerhalb einer Methode von der 
Speicherorganisation überhaupt von einem global definierten Array ?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

HeaderFile schrieb:
> Diese müssen meiner Meinung nach zwingend global sein oder gibt es da
> doch bessere Lösungen ?

Müssen sie nicht. Es genügt, wenn sie lokal innerhalb des Moduls 
verfügbar sind, in dem die ISR definiert ist. Der modulübergreifende 
Zugriff auf die Variablen kann durch eine im gleichen Modul 
untergebrachte Zugriffsfunktion erfolgen (die natürlich nicht als 
static definiert sein darf).

> Wenn ein Array innerhalb einer methode mit
> static deklariert wird landet das Array dann überhaupt auf dem Stack ?

Nein, statische Variablen landen nicht auf dem Stack.

> Bzw unterscheidet sich ein static Array innerhalb einer Methode von der
> Speicherorganisation überhaupt von einem global definierten Array ?

Von der Speicherorganisation her nicht, nur ist ein Zugriff auf das 
Array von außerhalb der das Array definierenden Funktion nicht möglich.

Im übrigen spricht man in C von Funktionen, nicht von "Methoden".

von HeaderFile (Gast)


Lesenswert?

Hey super dann ist es doch recht einfach ohne globale Variablen zwischen 
Modulen zurecht zu kommen.

Vielen Dank :)

von Fabian O. (xfr)


Lesenswert?

Dirk B. schrieb:
> Ja, Nein, vielleicht.
>
> Was ist, wenn eine andere Headerdatei schon die benötigten Informationen
> einbindet?
>
> foo.h:
> # include <limits.h>
>
> bar.h:
> # include <limits.h>  // diese Zeile könntest du löschen. Ist aber nicht
> sinnvoll
> # include "foo.h"
>
> Gilt auch für Includes in C-Dateien.

Stimmt, den Fall habe ich nicht bedacht.

Da hat man etwas Entscheidungsfreiraum. Bei Standarddateien wie limits.h 
ist es natürlich sinnvoll, sie in jede H-Datei, die etwas daraus 
braucht, einzubinden. Wenn der doppelte Include in bar.h allerdings 
ausschließlich in Zusammenhang mit einer Funktion aus foo.h benötigt 
wird, bräuchte man ihn nicht nochmal in bar.h einbinden.

HeaderFile schrieb:
> Bei mir sind momentan die meisten der globalen Variablen aus den
> folgenden Gründen global.
>
> - Es sind flags die aus einem Interrupt gesetzt werden um dem
> Hauptprogramm Timeouts, Zeitabläufe, Freigabesignale usw. zu erteilen.
> Diese müssen meiner Meinung nach zwingend global sein oder gibt es da
> doch bessere Lösungen ?

Wie schon erwähnt: Statische Modulvariable, also eine "globale" Variable 
mit static. In dem Modul (C-Datei) ist dann sowohl die ISR (z.B. 
UART-ISR) als auch die dazugehörigen Funktionen (z.B. uart_send, 
uart_receive etc.) enthalten, die auf die Variable Zugriff benötigen.

Von außerhalb des Moduls ist die Variable dagegen nicht erreichbar, d.h. 
es darf problemlos eine Variable mit gleichem Namen in einem anderen 
Modul geben. Und man kann sich sicher sein, dass niemand außerhalb des 
eigenen Moduls auf die Variable zugreift, außer vielleicht über einen 
wildgewordenen Zeiger ...

> - Es sind relativ große Arrays bei denen ich ein schlechtes Gefühl habe
> wenn sie bei einem Methodenaufruf auf dem Stack laden. Hier habe ich
> jetzt allerdings Zweifel. Wenn ein Array innerhalb einer methode mit
> static deklariert wird landet das Array dann überhaupt auf dem Stack ?
> Bzw unterscheidet sich ein static Array innerhalb einer Methode von der
> Speicherorganisation überhaupt von einem global definierten Array ?

Du kannst das Array ebenfalls als statische Modulvariable anlegen. Dann 
haben sogar mehrere Funktionen (eben alle in dem Modul) darauf Zugriff. 
Falls ein anderes Modul mit dem Array arbeiten muss, übergibst Du beim 
Funktionsaufruf nur einen Zeiger auf das Array (bzw. auf dessen erstes 
Element) sowie die Länge des Arrays. Dann landet der Arrayinhalt nicht 
auf dem Stack.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Fabian O. schrieb:
> Falls ein anderes Modul mit dem Array arbeiten muss, übergibst Du beim
> Funktionsaufruf nur einen Zeiger auf das Array (bzw. auf dessen erstes
> Element)

Das ist in C sowieso nur so möglich. Einen "call by value" gibt es bei 
Arrays nicht.

von Karl (Gast)


Lesenswert?

Nur aus Interesse:

kann man Arrays nicht mittels eines Structs "by value" übergeben?

also so etwa:
1
typedef struct
2
{
3
    uint8_t data[10];
4
} data_s;
5
6
void foo(data_s bar)
7
{
8
    bar.data[3] = ...
9
}

Wüsste zwar keinen praktischen Anwendungsfall, aber könnte man so, wenn 
man denn wollte?

von Karl H. (kbuchegg)


Lesenswert?

Ja, könnte man.

Denn dann wird ja ein Strukturobjekt übergeben und nicht in erster Linie 
ein Array. Und wie bei allen anderen Datentypen auch, mit Ausnahme eines 
Arrays, bedeutet das, dass eine Wertkopie erzeugt wird, die an die 
Zielvariable in der Funktion gebunden wird.

Ab und an kommt sowas schon mal vor, wenn die Funktion sowieso eine 
Kopier der Daten machen müsst um mit ihnen zu arbeiten.

von Fred (Gast)


Lesenswert?

Karl Heinz schrieb:
> Und wie bei allen anderen Datentypen auch, mit Ausnahme eines
> Arrays, bedeutet das, dass eine Wertkopie erzeugt wird, die an die
> Zielvariable in der Funktion gebunden wird.

Wie bei allen Datentypen.

Arrays werden nicht by-reference übergeben, Arrays werden gar nicht 
übergeben. Und die Zeiger auf Arrays, die C in einer etwas quirkigen 
syntaktischen Verrenkung tatsächlich übergibt, sind dann wieder 
by-value.

Ich weiß, du weißt das.

von HeaderFile (Gast)


Lesenswert?

Hi,

ich muss das Thema doch nochmal ausgraben.

Folgende Frage. Wenn man wirklich zwingend globale Variablen benötigt 
aber das Risiko der doppelten Namensgebung möglichst weit minimieren 
möchte würde dann sowas gehen ?

main.h
1
#include <inttypes.h>
2
3
typedef struct {
4
  uint8_t timer_flag;
5
  uint8_t timeout_flag;
6
  uint8_t update_flag;
7
} FLAGS;
8
9
extern volatile FLAGS status_flags;

main.c
1
#include "main.h"
2
3
volatile FLAGS status_flags = {0,0,1};
4
5
int main(void)
6
{
7
  while(1) {
8
    if (status_flags.timer_flag) {
9
      status_flags.timer_flag = 0;
10
    }  
11
    
12
    if(status_flags.update_flag) {
13
      status_flags.update_flag = 0;
14
      // mach was
15
    }
16
  }  
17
}
18
19
ISR(TIMER0_COMPA_vect) {
20
  status_flags.timer_flag = 1;
21
}

test.c
1
#include "main.h"
2
3
void foo() {
4
   if (status_flags.timer_flag) {
5
     // mach was
6
   }
7
}

So hätte man die Flags schonmal alle schön kompakt in einer Struktur 
zusammengefasst was zum einen übersichtlicher ist und zum anderen die 
Anzahl der globalen Variablennamen reduziert.

Folgende Fragen dazu. Funktioniert das volatile so wie oben verwendet 
bei einem struct ? Oder anders werden so wie oben verwendet jetzt alle 
variablen im struct automatisch volatile ?

von Karl H. (kbuchegg)


Lesenswert?

Ja.

Ist gut so, wie es jetzt ist.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Der Zugriff auf einzelne Flags muß atomar sein, ansonsten gibt's 
Glitches, wenn in der ISR Flag gesetzt wird während in main gerande ein 
anderes Flag verändert wird.

Außerdem ist FLAGS unglücklich, sieht aus wie ein Makro...

von HeaderFile (Gast)


Lesenswert?

stimmt FLAGS als Name ist wirklich unglücklich. War ja aber auch nur 
eine schnelle Idee.

Zum atomaren Zugriff. Bytezugriffe sollten meines Wissens automatisch 
atomar sein oder ändert sich durch das Struct etwas daran ?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ja, Bytezugriffe sind atomar, aber das Setzen / Löschen / Invertieren 
eines Bits beinhaltet i.d.R mehr als ein Speicherzugriff und ist damit 
nicht mehr atomar.

Schau dir einfach mal die vom Compiler erzeugte Codesequenz an.

von HeaderFile (Gast)


Lesenswert?

Okay hier mal Beispielcode.
1
#include <avr/io.h>
2
3
typedef struct {
4
  uint8_t timer_flag;
5
  uint8_t timeout_flag;
6
  uint8_t update_flag;
7
} FLAGS;
8
9
volatile FLAGS status_flags = {0,0,1};
10
11
int main(void)
12
{
13
  while(1) {
14
    if(status_flags.update_flag) {
15
      status_flags.update_flag = 0;
16
      // mach was
17
    }
18
  }  
19
}
20
21
ISR(TIMER0_COMPA_vect) {
22
  status_flags.timer_flag = 1;
23
}

Da kommt assabliert folgendes raus.
1
ISR(TIMER0_COMPA_vect) {
2
  status_flags.timer_flag = 1;
3
00000088  LDI R24,0x01    Load immediate 
4
00000089  STS 0x0200,R24    Store direct to data space 
5
}
6
0000008B  RET     Subroutine return 
7
{
8
  while(1) {
9
    if(status_flags.update_flag) {
10
0000008C  LDS R24,0x0202    Load direct from data space 
11
0000008E  TST R24    Test for Zero or Minus 
12
0000008F  BREQ PC-0x03    Branch if equal 
13
      status_flags.update_flag = 0;
14
00000090  STS 0x0202,R1    Store direct to data space 
15
00000092  RJMP PC-0x0006    Relative jump

if(status_flags.update_flag) endet in einem LDS Befehl der atomar ist. 
Anschließend ist der Wert eh "sicher" im Register 24. Bei der if Abfrage 
brennt also schonmal nichts an.

status_flags.update_flag = 0; endet in einem atomaren STS.

Würde zwischen der If Abfrage und dem zurücksetzen die ISR aufegrufen, 
dann wäre zwar das flag wieder gesetzt, würde durch den STS aber 
überschrieben. Also das was man an der Stelle ja auch bezwecken möchte.

Ich sehe da akut keine Probleme ?

Oder bezog sich deine Antwort auf den Fall das man nicht eine direkte 
Zuweisung wie flag = 1 oder flag = 0 macht, sondern das Flag Byte als 
Byte über 8 Flags sieht und dann mit |= bzw &= einzelne Bits des Flags 
setzt / löscht. Dann sieht das natürlich anders aus.

Aber bei direkten Zuweisungen dürfte so wie ich das sehe eigentlich 
nichts passieren oder ?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

HeaderFile schrieb:

> Aber bei direkten Zuweisungen dürfte so wie ich das sehe eigentlich
> nichts passieren oder ?

Ah, stimmt.  Aus irgendwelchem Grund bin ich davon ausgegangen, daß es 
sich um Bitfelder handelt...

von HeaderFile (Gast)


Lesenswert?

Ich bin gerade etwas verunsichert wie ich feststellen muss.

Kann man das wirklich so pauschalisieren, dass bei Bytevariablen und 
einem direkten Zugriff wie lesen oder einer direkten Wertzuweisung 
atomares Verhalten gegeben ist unabhängig davon ob sich um eine einzelne 
Bytevariable, um eine Byte Variable in einem Struct oder aber um eine 
Byte Variable in einem Array handelt ?

Wenn ich mich richt erinner gab/gibt es bei manchen AVRs ja auch Port 
Register die nicht mehr im 8 Bit Adressraum liegen und daher nicht mehr 
über SBI oder CBI Befehle direkt angesprochen werden können.

Kann unter irgendwelchem Umsatänden sowas auch bei Variablen passieren ?

von W.S. (Gast)


Lesenswert?

HeaderFile schrieb:
> - Es sind flags die aus einem Interrupt gesetzt werden um dem
> Hauptprogramm Timeouts, Zeitabläufe, Freigabesignale usw. zu erteilen.
> Diese müssen meiner Meinung nach zwingend global sein oder gibt es da
> doch bessere Lösungen ?

Ja.

Arbeite einfach ohne irgendwelche Flags, die von mehreren 
Programmteilen geschrieben werden. Wenn es nur eine einzige Instanz 
gibt, die Flags setzt oder löscht und alle anderen nur zuschauen und 
sich informieren dürfen, gibt es keine Probleme.

Für eine einseitige Verständigung arbeitet man besser mit (salopp 
gesagten) Events, die in eine Event-Warteschlange kommen. Das ist 
schlichtweg ein Ringpuffer. Dazu gibt es einen einzigen Modul im System, 
der nach außen hin nur ganz wenige Funktionen aufweist (EVENTTYPE ist, 
was dir grad paßt, z.B. int, long, dword, byte,.. oder was auch immer):
 bool AddEvent(EVENTTYPE Event);
 bool EventAvailable(void);
 EVENTTYPE GetEvent(void);
und alle anderen Interna dieses Moduls braucht der Rest der Welt nicht 
zu kennen. Will ein Modul nem anderen eine Botschaft zukommen lassen, 
dann ruft er AddEvent(meineBotschaft) auf. Ein Verteiler in der 
Grundschleife von main holt später all diese Event der Reihe nach aus 
der Warteschlange und schmeißt sie allen vorhandenen Modulen in den 
Rachen und welcher Modul sich dafür interessiert, der macht halt was 
draus. Die anderen ignorieren ihn.

Wenn dir das zu aufwendig erscheint und es nur ne bilaterale Abstimmung 
zwischen zwei Partnern sein soll (z.B. Exceptionhandler und 
Grundprogramm), dann bietet sich ein simpleres Verfahren an:
Beide Partner haben eine Variable, z.B. ein char. Parallel dazu gibt es 
(falls benötigt) eine Ergebnis-Variable, quasi einen Topf irgendeiner 
Art. Wenn der eine was in den Topf getan hat, inkrementiert er seine 
Variable. Der andere kann sie sich hereinladen und mit der seinigen 
vergleichen und aus einer Ungleichheit ersehen, daß was Neues im Topf 
liegt. Wenn er es dort herausgenommen hat, speichert er die geladene 
Kopie des anderen in seiner Variablen. Daraus kann der andere wiederum 
ersehen, daß die Beute abgeholt wurde. Und das Schöne ist, daß keiner 
von beiden schreibend auf die Variable des anderen zugreifen muß. 
Datenknatsch ist also ausgeschlossen. Lediglich das Überlaufen des 
Topfes kann da noch passieren, aber das ist ne andere Sache.

W.S.

von Michael (Gast)


Lesenswert?

Dirk B. schrieb:
> Was ist, wenn eine andere Headerdatei schon die benötigten Informationen
> einbindet?

Dann ist das Zufall und man kann sich nicht drauf verlassen.

von HeaderFile (Gast)


Lesenswert?

@W.S

Der Ansatz ist gut. Allerdings für die angedachte Aufgabe zu 
überdimensioniert.

So viele unabhängige Module sind es in wirklichkeit garnicht. Das 
arbeiten über einfache Byte Flags ist da definitiv die einfachste Lösung 
voraussgesetzt das diese bytezugriffe halt wirklich atomar sind.

Das eigentliche Problem besteht an folgender Stelle. Ein Sd Karten Modul 
will eine Variable hochgezählt haben um die Dateizeit im Fat System 
setzen zu können. Diese wird per extern in der Headerdatei nach außen 
gereicht. (SD Lib ist eine Fremdlib).

Eins meiner eigenen Module benötigt einen Timeoutflag falls der Serielle 
Datenempfang abreißen sollte.

Ein andere Modul benötigt ein Flag das alle x ms gesetzt wird um 
signalisiert zu bekommen das LCD Inhalte erneuert werden sollen.

Ein letztes Modul behandelt Tasterabfragen und benötigt eine Variable 
die im Timerinterrupt inkrementiert wird um die Druckdauer ermitteln zu 
können.

Alle diese Flags könnten in dem jeweiligen Modul auch static deklariert 
werden. Es findet kein wechselseitiger Zugriff auf diese Flags zwischen 
den einzelnen Modulen statt.

Was mich doch dazu bewegt sie ebend global sichtbar zu machen ist die 
Tatsache das ich alle diese Flags von einem einzigen Timer steuern 
lasse. Ich sehe es irgendwo nicht ein für so einen "pipifax" unnötig 
viele Timer zu verschleudern. Würden Sie nicht global sichtbar sein so 
müsste ja jedes Modul einen eigenen Timer mit eigener ISR innerhalb des 
Moduls zwecks Sichtbarkeit haben. Um das zu umgehen sitzt der Timer halt 
in der Main und alle nötigen Flags aus den anderen Modulen werden per 
extern eingebunden und in der Timer ISR der Main gesetzt/behandelt.

von Fabian O. (xfr)


Angehängte Dateien:

Lesenswert?

Du brauchst also einfach nur mehrere Software-Timer, die auf einem 
gemeinsamen Hardware-Timer basieren.

Als Anregung, wie so etwas aussehen kann (oder auch gerne zum direkt 
wiederverwenden), hänge ich mal die Timer-Implementierung aus dem 
Contiki-Betriebssystem an, die ich in einem Projekt mit 
AVR-Mikrocontrollern eingesetzt habe.

Anpassen muss man nur die Datei "clock_config.h". Dort muss die 
Initialisierung des Hardware-Timers rein, der Name der ISR definiert 
werden und wie viele Ticks (ISR-Aufrufe) pro Sekunde stattfinden.

Die Verwendung sieht dann z.B. so aus:
1
#include "timer.h"
2
3
static timer_t timer1;
4
static timer_t timer2;
5
6
int main(void)
7
{
8
  clock_init();
9
  sei();
10
11
  timer_set(&timer1, (CLOCK_SECOND / 50));
12
  timer_set(&timer2, (CLOCK_SECOND / 2));
13
  
14
  while (1)
15
  {
16
    if (timer_expired(&timer1)) {
17
      timer_reset(&timer1);
18
      led1_toggle();
19
    }
20
    
21
    if (timer_expired(&timer2) {
22
      timer_reset(&timer2);
23
      led2_toggle();
24
    }
25
  }
26
}
Der Code lässt die erste LED 50 Mal pro Sekunde (also alle 20 ms) und 
die zweite LED zwei Mal pro Sekunde (also alle 500 ms) blinken. Man kann 
so einen Software-Timer sowohl für regelmäßige Intervalle als auch für 
einmalige Timeouts benutzen, und zwar beliebig viele an beliebigen 
Stellen im Programm, ganz ohne globale Variablen.

: Bearbeitet durch User
von HeaderFile (Gast)


Lesenswert?

Sehr schicke Lösung nahezu Ideal für meinen Zweck :)

Aber eine kurze Frage dazu.
1
while(1) {
2
    if (timer_expired(&timer1)) {
3
      timer_reset(&timer1);
4
      led1_toggle();
5
    }
6
}

Das ganze ist ja nichts andere als Polling der vergangenen Zeit. Mit 
jedem timer_expired() Aufruf wird intern ja clock_time() aufgerufen.

clock_time() sperrt für dem Atomaren Zugriff kurze Zeit die Interrupts.

Pollt man nun in einer Dauerschleife permanent timer_expired() führt das 
doch zu einer ziemlich großen Zeitspanne in der Interrupts deaktiviert 
sind. Könnte das nicht dazu führen das Interrupts anderer Module 
verloren gehen ?

Oder ist das eher als unkritisch anzusehen ?

von Fabian O. (xfr)


Lesenswert?

Ein Interrupt geht ja nicht sofort verloren, nur weil das auslösende 
Ereignis auftritt, während die Interrupts deaktiviert sind. Das 
Interrupt-Flag wird trotzdem im Hintergrund gesetzt. Sobald die 
Interrupts wieder aktiviert werden, werden die ISRs aller aufgelaufenen 
Interrupts angesprungen. Erst wenn alle abgearbeitet sind, geht es im 
Hauptprogramm weiter.*

Ein Problem entsteht erst, wenn die Interrupts so lange am Stück 
deaktiviert sind, dass zwei (oder mehr) Interrupts einer Sorte 
auftreten. Also wenn z.B. der Timer zwei Mal überläuft. Dann bekommt man 
den zweiten Überlauf nicht mehr mit, da das Interrupt-Flag halt nur ein 
Bit ist und sich nicht die Anzahl der Interrupts merken kann.

Da bei dem Timer die Interrupts nur für das Lesen von zwei Bytes (oder 
vier, wenn Du einen uint32_t nimmst) deaktiviert sind, ist das völlig 
unkritisch. So schnell hintereinander treten keine zwei gleichen 
Interrupts auf.

Es kommt also nicht darauf an, wie viel Prozent der Zeit das Programm 
mit deaktiverten Interrupts verbringt, sondern wie lang (absolut 
gemessen) die einzelnen Phasen maximal sind.

Du hast aber natürlich recht, dass in dem Beispielprogramm quasi die 
ganze Zeit nur der Counter gepollt wird. Aber irgendwas muss der 
Prozessor nun mal machen, wenn er sonst nichts zu tun hat. Wenn es Dich 
stört, könntest Du ihn auch am Ende der Hauptschleife in den Sleepmodus 
schicken. Dann wacht er erst beim nächsten Interrupt wieder auf und 
spart in der Zwischenzeit Energie. In einem realen Programm gibt es in 
der Hauptschleife aber ja auch noch andere Dinge zu tun. Und wenn nicht, 
sei froh, dann hast Du zumindest kein Performanceproblem. ;-)


*) Beim AVR wird afaik zwischen zwei ISRs noch (mindestens) ein 
Maschinenbefehl des Hauptprogramms ausgeführt. Ist für die Erklärung 
aber egal.

von HeaderFile (Gast)


Lesenswert?

Was mir gerade noch auffällt. Wieso werden eigentlich immer oder besser 
so oft einfach alle Interrupts global gesperrt freigegeben.

Würde es nicht auch reichen immer nur den Interrupt zu 
aktivieren/deaktivieren der für den entsprechenden Zugriff wirklich von 
Relevanz ist ?

Oder spielen andere Interrupts da intern eventuell doch noch zwischen ?

Wenn man einen Timinghandler wie oben beschrieben implementiert. Ist die 
Genuigkeit dann nicht immer zufällig ungenau ? Nimmt man mal an der 
Timer hat ein Intervall von 10 ms in dem die counter variable 
incrementiert wird.

Wartet man jetzt auf counter == 2, dann könnte das doch so ziemlich 
alles zwischen 10 und 20 ms sein. Man weiß ja nicht genau wo sich TCCNT 
intern gerade befindet. Sprich für eine möglichst hohe Genauigkeit 
müsste das Timerintervall möglichst hoch sein richtig ? Bei einem 1 ms 
Intervall hätte man dann eine maximale Abweichung von 1ms aber halt auch 
eine höhere Auslastung des systems durch höhere Anzahl an Interrupts.

von Fabian O. (xfr)


Lesenswert?

HeaderFile schrieb:
> Was mir gerade noch auffällt. Wieso werden eigentlich immer oder besser
> so oft einfach alle Interrupts global gesperrt freigegeben.
>
> Würde es nicht auch reichen immer nur den Interrupt zu
> aktivieren/deaktivieren der für den entsprechenden Zugriff wirklich von
> Relevanz ist ?

Ist halt die einfachste Möglichkeit, weil man überall den gleichen
Befehl nutzen kann. Wenn man nur genau den einen Interrupt ausschaltet,
ist das fehleranfälliger und der Code nicht mehr so leicht in
verschiedenen Projekten einsetzbar. Im Beispiel oben bräuchte z.B. man
in clock_config.h eigene Funktionen/Makros, die den richtigen Interrupt
aktivieren/deaktiveren.

Wenn man die Phasen, in denen die Interrupts ausgeschaltet sind, kurz
hält, ist das ja auch kein Problem. Wozu also unnötigen Aufwand
betreiben ...

HeaderFile schrieb:
> Wenn man einen Timinghandler wie oben beschrieben implementiert. Ist die
> Genuigkeit dann nicht immer zufällig ungenau ? Nimmt man mal an der
> Timer hat ein Intervall von 10 ms in dem die counter variable
> incrementiert wird.
>
> Wartet man jetzt auf counter == 2, dann könnte das doch so ziemlich
> alles zwischen 10 und 20 ms sein. Man weiß ja nicht genau wo sich TCCNT
> intern gerade befindet. Sprich für eine möglichst hohe Genauigkeit
> müsste das Timerintervall möglichst hoch sein richtig ? Bei einem 1 ms
> Intervall hätte man dann eine maximale Abweichung von 1ms aber halt auch
> eine höhere Auslastung des systems durch höhere Anzahl an Interrupts.

Bei einmaligen Wartezeiten ja. Da weißt Du beim Starten des Timeouts 
nicht, wo zwischen zwei Ticks sich der Timer gerade befindet. Das 
Timer-Intervall muss also mindestens so hoch wie die benötigte 
Genauigkeit sein.

Bei gleichmäßigen Intervallen (z.B. eine LED alle 50 ms blinken lassen) 
hängt die Genauigkeit dagegen nur von der Ausführungsgeschwindigkeit der 
Hauptschleife ab, sofern das Intervall ein Vielfaches des 
Timer-Intervalls ist. Wenn die Timer-ISR (z.B. alle 10 ms) ausgeführt 
wird, bekommt man es ja spätestens im nächsten Durchgang der 
Hauptschleife mit.

Typischerweise nutzt man so einen Software-Timer aber ja auch nur für 
Zeiten im Millisekundenbereich. Eine Timer-ISR pro Millisekunde ist für 
den Mikrocontroller kein Problem. Für kürzere Zeiten sollte man eher 
einen dedizierten Hardware-Timer oder delay_us() verwenden.

von HeaderFile (Gast)


Lesenswert?

Hi,

ja klingt plausibel und ist für zwecke vie einfache Timeouts, 
Aktualisierungsevents usw. wirklich eine schicke Sache wenn es nicht auf 
die letzte ms Genauigkeit ankommt.

Habe die Software Timer geschichte gerade nochmal selber nach einem 
etwas anderen Prinzip gestrickt.

Mag jemand da eventuell mal drüber sehen ob das so passt ?

timer.h
1
#ifndef TIMER_H_
2
#define TIMER_H_
3
4
#include <avr/io.h>
5
6
void timer0_init(void);
7
8
// defines to set / reset - interrupt flags
9
10
#define ENABLE_TIMER0_INT  (TIMSK0 |= (1<<OCIE0A))
11
#define DISABLE_TIMER0_INT  (TIMSK0 &= ~(1<<OCIE0A))
12
13
#endif /* TIMER_H_ */

timer.c
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include "timer.h" 
4
5
void timer0_init(void) {
6
  // 1ms - 8bit
7
  uint8_t sreg = SREG; // Save SREG because we have to restore the I bit
8
  cli();
9
  
10
  TCNT0 = 0;
11
  OCR0A = 0xE5;
12
  TCCR0A = (1<<WGM01); // CTC Mode
13
  TCCR0B = (1<<CS01) | (1<<CS00); // Prescale 64
14
  
15
  SREG = sreg; // Restore SREG  
16
}

timerHandler.h
1
#ifndef TIMERHANDLER_H_
2
#define TIMERHANDLER_H_
3
4
#include <inttypes.h>
5
6
#define TIMEHANDLER_ISR TIMER0_COMPA_vect
7
8
typedef struct {
9
  int16_t delay;
10
  int16_t timeExpired;
11
} timer_event;
12
13
void setTimerEvent(timer_event* timerEvent, int16_t delay);
14
uint8_t getTimerEvent(timer_event* timerEvent);
15
16
#endif /* TIMERHANDLER_H_ */

timerHandler.c
1
#include "timerHandler.h"
2
#include <avr/interrupt.h>
3
4
static volatile int16_t time_count;
5
6
void setTimerEvent(timer_event* timerEvent, int16_t delay) {
7
  cli();
8
  timerEvent->delay = delay;
9
  timerEvent->timeExpired = time_count + timerEvent->delay;
10
  sei();
11
}
12
13
uint8_t getTimerEvent(timer_event* timerEvent) {
14
  int16_t timeDiff = 0;
15
  uint8_t tmp_result = 0;
16
  
17
  cli();
18
  timeDiff = timerEvent->timeExpired - time_count;
19
  sei();
20
    
21
  if (timeDiff <= 0) {
22
    tmp_result = 1;
23
    setTimerEvent(timerEvent, timerEvent->delay);
24
  }      
25
  
26
  return tmp_result;
27
}
28
29
ISR(TIMEHANDLER_ISR) {
30
  time_count++;  
31
}

Demo main:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include "timerHandler.h"
4
#include "timer.h"
5
6
int main(void) {
7
  timer_event ledTimerEvent;
8
9
  timer0_init();  
10
  ENABLE_TIMER0_INT;
11
  sei();
12
  
13
  setTimerEvent(&ledTimerEvent, 100);
14
  
15
  while(1) {
16
    if (getTimerEvent(&ledTimerEvent)) {
17
      PORTA ^= (1<<0); // Dummy toggle Led
18
    }      
19
  }  
20
}

von Karl H. (kbuchegg)


Lesenswert?

HeaderFile schrieb:

> Mag jemand da eventuell mal drüber sehen ob das so passt ?

Ich finde deine Wahl der Datentypen ungeschickt.
Bei Timern hat man es praktisch immer mit unsigned Typen zu tun. Denn 
man wird sich einen Timer immer nur für die Zukunft stellen und nicht 
für die Vergangenheit. D.h. niemand wird je einen Timer anfordern, der 
alle -100ms seinen Event auslöst.

Das Problem ist nämlich 2-teilig.
Zum einen verschenkst du so Timerauflösung.
Gut, das wär noch nicht so schlimm.
Aber: dein 16 Bit Zähler wird irgendwann überlaufen. Wenn das 
Millisekunden sind, dann läuft der nach 32 Sekunden über. Und dann wird 
das hier

>   cli();
>   timeDiff = timerEvent->timeExpired - time_count;
>   sei();
>
>   if (timeDiff <= 0) {
>     tmp_result = 1;
>     setTimerEvent(timerEvent, timerEvent->delay);
>   }

nämlich grauslich in die Hose gehen.
Darüber solltest du nochmal nachdenken, was da alles passiert, wenn der 
time_count von 32767 auf -32768 überläuft.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Wenn du deine Interrupts so kurz wie es nur geht sperren möchtest, dann 
mach dir eine Zugriffsfunktion für den time_count, und nur für den 
time_count. In der Funktion sperrst du die Interrupts, holst den Wert 
und gibst die Interrupts wieder frei
1
static int16_t getTimeCount() __attribute__((always_inline))
2
{
3
  int16_t wert;
4
  cli();
5
  wert = time_count;
6
  sei();
7
  return wert;
8
}

und diese Funktion benutzt du dann an allen Stellen, an denen du den 
aktuellen time_coutn abfrägst.
1
uint8_t getTimerEvent(timer_event* timerEvent) {
2
  int16_t timeDiff = 0;
3
  uint8_t tmp_result = 0;
4
  
5
  timeDiff = timerEvent->timeExpired - getTimeCount();
6
...
auf die Art hast du dann die Interruptsperre auf die kürzest mögliche 
Zeit gedrückt. Es wird zwar bei dir mit den Timern nicht die große Rolle 
spielen, aber Programme entwickeln sich ja auch weiter. Und irgendwann 
kommen dann auch mal andere Interrupts ins Spiel.
Man muss ja nicht mutwillig, die Dinge länger sperren als nötig. Zumal 
das ja auch kein Aufwand ist.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

1
  OCR0A = 0xE5;

wo kommen die 0xE5 her?
Die hängen ja sicherlich irgendwie von der Taktfrequenz ab. Es spricht 
nichts dagegen, hier die tatsächliche Berechung, basierend auf F_CPU 
einzufügen. Dann kannst du erstens bei einer Umstellung der Taktfrequenz 
(vielleicht auch auf einem anderen Prozessor), nicht darauf vergessen 
und zum anderen ist auch nachvollziehbar wie sich der Wert berechnet 
(und zwar auf bessere Art als durch einen Kommentar).

Wie auch immer, ist aber dieser Wert mit Sicherheit ein Wert, den man 
besser dezimal anschreibt und nicht hexadezimal. Denn um den Wert in 
Beziehung zu deinen Millisekunden zu setzen und verstehen zu können, 
muss man ihn erst mal auf dezimal umformen um dann nachrechnen zu 
können.
Jetzt ist es aber so, dass du den Wert ausgerechnet hast, auf Hex 
umgeformt hast. Jemand der dein Programm verstehen will, muss als erstes 
den Wert wieder von Hex auf Dezimal zurück umformen.
Wozu?
Schreibs doch gleich dezimal hin.
Dann kannst du beim Umformen auf Hex keinen Fehler machen, und der der 
das alles wieder entziffern muss, kann beim Zurückumwandeln auf Dezimal 
keinen Fehler machen.
Benutze immer die Schreibweise, die im gegebenen Fall die zweckmässigste 
ist. Das kann Dezimal sein, das kann Hex sein, das kann Binär sein, das 
kann die Shift-Bitnamen Schreibweise sein. Aber es ist immer nur eine 
Schreibweise. Das Ergebnis (wenn richtig angeschrieben), ist sowieso 
immer das selbe: Ein Bitmuster wird erzeugt und mit dem wird irgendwas 
gemacht.
1
  OCR0A = 229;

macht genau dasselbe die Zuwiesung von 0xE5.
Aber es ist zumindest ein bischen besser zu lesen und in Beziehung zur 
Taktfrequenz bzw. dem Vorteiler zu setzen.
Am besten ist natürlich, den Wert vom Compiler ausrechnen zu lassen.

von HeaderFile (Gast)


Lesenswert?

Zwei sehr gute Tipps. Timer Berechnung mache ich jetzt über ein Makro 
das war so etwas ungeschickt das stimmt.

Zum Übrlauf nehme wir mal den Fall time_count ist gerade 32767 und das 
Delay bzw Intervall ist 100ms.

Aus timerEvent->timeExpired = time_count + timerEvent->delay;

wird dann timeExpired = -32569

Nehmen wir an noch im selben Zeitzyklus würd die Abfrage

timeDiff = timerEvent->timeExpired - time_count;

erfolgen. Dann würde das wie zu erwarten timeDiff = -32569 - 32569 => 
timeDiff = 200 ergeben.

Nehmen wir an der Timer ist jetzt ein wenig gelaufen und time_count 
hatte nun auch seinen Overflow. Sagen wir er ist 50ms gelaufen. Dann ist 
der aktuelle time_count Wert jetzt -32719.

Jetzt wieder die Berechnung der Differenz.

timeDiff = -32569 - -32719 => timeDiff = 150. Auch das war so zu 
erwarten. An welcher Stelle könnte da etwas passieren ? Ich dachte da 
ist gerade das schöne an der Signed Overflowrechnung. Sobald man die 
Differenz berechnet spielt der Overflow keine Rolle mehr.

von Karl H. (kbuchegg)


Lesenswert?

HeaderFile schrieb:

> timeDiff = -32569 - -32719 => timeDiff = 150. Auch das war so zu
> erwarten. An welcher Stelle könnte da etwas passieren ? Ich dachte da
> ist gerade das schöne an der Signed Overflowrechnung. Sobald man die
> Differenz berechnet spielt der Overflow keine Rolle mehr.

UNsigned.
Bei unsigned Berechnungen kannst du den Overflow ignorieren, solange das 
Ergebnis nicht größer als (in deinem Fall) 65535 sein kann. Erst dann 
hast du das Problem, dass du den Overflow berücksichtigen müsstest. ABer 
bis dahin hast du durch unsigned die Gewissheit, dass du einfach 
Subtrahieren kannst, ohne dich um den Wrap-Around kümmern zu müssen.

von HeaderFile (Gast)


Lesenswert?

Hi,

meine obige Beispielrechnung hatte ich leider ein paar Dreher drin.
Hier nochmal die Richtige Beispielrechnung:

Aus timerEvent->timeExpired = time_count + timerEvent->delay;

wird dann timeExpired = -32669

Nehmen wir an noch im selben Zeitzyklus würd die Abfrage

timeDiff = timerEvent->timeExpired - time_count;

erfolgen. Dann würde das wie zu erwarten timeDiff = -32669 - 32767 =>
timeDiff = 100 ergeben.

Nehmen wir an der Timer ist jetzt ein wenig gelaufen und time_count
hatte nun auch seinen Overflow. Sagen wir er ist 50ms gelaufen. Dann ist
der aktuelle time_count Wert jetzt -32719.

Jetzt wieder die Berechnung der Differenz.

timeDiff = -32669 - -32719 => timeDiff = 50. Auch das war so zu
erwarten.

An welcher Stelle siehst du denn konkret das Problem ? Die obige 
Rechnung provozierte ja exakt den Overflow wechsel.

von HeaderFile (Gast)


Lesenswert?

Und da sich aus dem Text heraus das ganze etwas schlecht rauslesen lässt 
nochmal ein Testcode inklusive überprüfter Ergebnisse:
1
int main(int argc, char* argv[]) {
2
    int16_t time_count, delay, time_expired, resu;
3
    
4
    time_count = 32767; // Timer Variable kurz vor Überlauf
5
    delay = 100; // Gewünschtes Delay
6
    time_expired = time_count + delay; // Zeitpunkt des Timerablaufs berechnen
7
    
8
    resu = time_expired - time_count; // Restzeit bis Ablauf ermitteln
9
    printf("%d\n", resu); // resu = 100
10
    
11
    time_count += 50; // Timer läuft 50ms weiter
12
    
13
    resu = time_expired - time_count; // Restzeit bis Ablauf ermitteln
14
    printf("%d\n", resu); // resu = 50
15
    
16
    time_count += 60; // Timer läuft 60ms weiter 
17
    
18
    resu = time_expired - time_count; // Restzeit bis Ablauf ermitteln
19
    printf("%d\n", resu); // resu = -10 Zeit ist abgelaufen !!    
20
}

von HeaderFile (Gast)


Lesenswert?

@kbuchegg

wäre super wenn du dich nochmal zu der Geschichte melden könntest. Und 
ob du eine konkrete Situation im Kopf hattest die sich einfach an einer 
kleinen Beispielrechnung nachvollziehen lässt.
Habe gestern noch den halben Tag drüber gegrübelt aber keine potentiell 
gefährlichen Fälle in der unsigned Differenzbildung gefunden.

Ich bin gestern noch auf ein paar ältere Unterlagen aus der Uni 
gestoßen. Dort wurden anfangs "Zahlenkreise" verwendet um das Thema 
Over-/Underflow sichtbar zu machen.

http://www.gk-informatik.de/hw/grafik/zahlkreis_z.png

Die blauen Zahlen außen entsprechen der unsigned Denkweise. Die roten 
und grünen Zahlen innen der signed Denkweise.

Der Over und Underflow dürfte also unahängig von signed oder unsigned 
gefahrlos ignoriert werden können wenn die folgenden Voraussetzungen 
erfüllt sind.

TimerEndwert (unsigned) = TimerAktuell(unsigned) + Delay(unsigned)
Differenz (unsigned) = TimerEndwert(unsigned) - TimerAktuell(unsigned)

oder

TimerEndwert (signed) = TimerAktuell(signed) + Delay(signed)
Differenz (signed) = TimerEndwert(signed) - TimerAktuell(signed)

Sprich solange alle an der Zeitrechnung beteiligten Variablen den selben 
Typ (unsigned / signed) und die gleiche Bitweite haben, spielt der 
Overflow weder für signed noch unsigned eine Rolle.

Dann noch der Grund wieso ich signed für die Differenzbildung 
praktischer finde.

Ich kann mit der signed Rechenweise bei der Differenzbildung auf <= 0 
also negative Differenzwerte prüfen. Gleichzeitig habe ich die 
"negative" Zeit also die Zeit wie lange der Counter bereits abgelaufen 
ist. Hierfüh finde ich ist signed auch die natürliche Denkweise.

Würde ich die Differenzbildung so wie oben auf unsigned durchführen 
würde die Differenzbildung nicht mehr sicher funktionieren. Die 
Differenz müsste aufjedenfall signed bleiben um negative Werte bei Zeit 
bereits abgelaufen aufnehmen zu können. Unsigned hätte ich da nur 
positive Werte und könnte einen Ablauf des Timers so nicht mehr 
bestimmen.

Würde man Den Timer auf Unsigned laufen lassen und lediglich die 
Differenzvariablen Signed ausführen, dann wäre das ganze nicht mehr 
Überlaufsicher, da die maximal mögliche Zeitdifferenz bei unsigned ja 
doppelt so groß ist wie bei signed.

Dann noch zur Frage wieso der Delay parameter signed ist obwohl negative 
Delaywerte nicht der menschlichen Denkweise entsprechen und somit 
unsinnig sind. Das Problem dabei ist das selbe wie im Absatz hier drüber 
nur andersherum. Wäre der Delay parameter unsigned aber die komplette 
interne Rechnung signed, dann ließe sich ein zu großer Delaywert 
übergeben, da der Parameter eine größere positive weite hätte als die 
Rechenvariablen.

Oder nochmal kurz zusammengefasst solange alles an der Rechnung 
konsequent signed mit identischer Bitweite oder unsigned mit identischer 
Bitweite ist kann man sicher sein das die komplette Rechnung in sich 
Überlaufsicher ist.

Da negative Differenzen so nur mit unsigned möglich ist finde ich 
unsigned für die komplette Rechnung am praktischsten.

Kann jemand bestätigen/dementieren ob die Ausführung so schlüssig ist ? 
Ich hätte bei der Geschichte wirkoich gerne die Sicherheit zu wissen was 
ich da tue, bevor später schwer debugbare und sporadische Fehler 
auftreten.

von Oliver (Gast)


Lesenswert?

HeaderFile schrieb:
> Oder nochmal kurz zusammengefasst

Der C-Standard definiert signed-overflow als "undefined behaviour". Das 
bedeutet, es kann alles passieren, und alles ist erlaubt.

Und ja, gcc nutzt daß aus, und erzeugt im Falle eines Falles Code, der 
garantiert nicht das macht, was du erwartest. Der darf das, wegen 
"undefined behaviour". Da gabs erst letztens hier einen nettes Beispiel 
dazu.

Also egal, ob kurz oder lang:
NUR unsigned-Datentypen haben ein definiertes overflow-Verhalten. Ob das 
jetzt jemand gut oder schlecht findet, spielt keine Rolle.

Oliver

von Oliver (Gast)


Lesenswert?

Nachtrag:

Hier ist der Thread dazu:
Beitrag "Hilfe - AVR-GCC "optimiert" Schleife zur Endlosschleife"

Oliver

von Le X. (lex_91)


Lesenswert?

Oliver schrieb:
> Und ja, gcc nutzt daß aus, und erzeugt im Falle eines Falles Code, der
> garantiert nicht das macht, was du erwartest.

Was macht denn der gcc bei einem signed Überlauf? Würd mich mal 
interessieren, dachte eigentlich der läuft so über "wie erwartet" (tm).

Oder fliegen mir Dämonen aus der Nase? :-)

von Oliver (Gast)


Lesenswert?


von HeaderFile (Gast)


Lesenswert?

Okay das war mir nicht bewusst. Ich ging bisher immer fest davon aus das 
im C Standard das Overflowverhalten für signed genauso fest definiert 
wurde wie für unsigned.

Hat jemand zufällig eine Idee wie ich meinen Code von oben so umbauen 
könnte das er unsigned arbeitet und ich dennoch eine differenz bestimmen 
kann die mein gewolltes verhalten hat, sprich eine negative differenz 
bilden kann um sehen zu können wie lange ein timer schon abgelaufen ist 
?

Und dann noch eine zu dem von Fabian geposteten code.
1
/* Note: Can not return diff >= t->interval so we add 1 to diff and return
2
     t->interval < diff - required to avoid an internal error in mspgcc. */
3
  clock_time_t diff = (clock_time() - t->start) + 1;
4
  return t->interval < diff;

Bezieht sich der obige Hinweis nur auf ein Eigenverhalten des mspgcc ? 
Oder anders gefragt kann ich unter dem gcc problemlos folgendes 
schreiben:
1
clock_time_t diff = (clock_time() - t->start);
2
return diff > t->interval;

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.