Forum: Compiler & IDEs Übergabe & Rückgabe eines structs


von Micha (Gast)


Lesenswert?

Hallo,

ich möchte an memcmp als 2. Argument den Rückgabewert einer Funktion 
übergeben. Beim Rückgabewert handelt es sich um ein struct.

Folgender Code lässt sich zwar fehlerfrei compilieren (AVRStudio 5), 
aber funktioniert nicht wie ich will:
1
typedef struct
2
{
3
   // mehr code
4
   uint8_t  minute;
5
} SCHEDULE;
6
7
static SCHEDULE   CurrentTime;
8
static SCHEDULE   WTime;
9
static uint8_t    WTimeSpan = 10;
10
11
SCHEDULE* addSpanToTime(SCHEDULE time, uint8_t minutes)
12
{
13
   time.minute += minutes;
14
   // mehr code
15
   return &time;
16
}
17
18
int main(void)
19
{
20
   uint16_t test;
21
   // mehr code
22
   test = memcmp(&CurrentTime, addSpanToTime(WTime, WTimeSpan), sizeof(WTime));
23
   // mehr code
24
}
Die Parameter, die an addSpanToTime übergeben werden, sollen nicht 
verändert werden.

Was ist an obigem Code falsch? Ich habe schon Verschiedenes probiert, 
komme aber auf keine funktionierende Lösung.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

"time" ist in addSpanToTime eine lokale Variable auf dem Stack. Wenn die 
Funktion zurückkehrt, verschwindet die Variable, und an dieser Stelle 
auf dem Stack steht nun irgendetwas anderes. Du gibst aber einen Pointer 
auf eben diese Stelle zurück, und arbeitest damit weiter - das kann nur 
schief gehen.

von Rolf M. (rmagnus)


Lesenswert?

Micha schrieb:
> Was ist an obigem Code falsch?

Du gibt einen Zeiger auf eine lokale Variable zurück (ein Parameter ist 
letztenlich nichts anderes). Wenn du diesen Zeiger nutzen willst, 
existiert die Variable nicht mehr, weil die Funktion verlassen wurde.

von Klaus W. (mfgkw)


Lesenswert?

1. "funktioniert nicht" ist fast die schlechteste mögliche Beschreibung
2. der Parameter einer Funktion wird beim Aufruf auf dem Stack abgelegt 
und ist nur bis zum Ende der Funktion verfügbar.
Du gibst aber dessen Adresse zurück, und die wird verwendet, wenn der 
Speicher schon wieder für etwas anderes benutzt wird.

von Klaus W. (mfgkw)


Lesenswert?

war er doch schon wieder schneller...
Wenigstens widersprechen wir uns nicht :-)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Ha, ich war der schnellste :-P

von (prx) A. K. (prx)


Lesenswert?

Abgesehen von dem bereits erwähnten Problem: Strukturen mit memcmp zu 
vergleichen ist ziemlich gefährlich, weil unsichtbare Löcher mit 
zufälligem Inhalt drin sein können, das Ergebnis also nicht unbedingt 
dem entspricht, was du dir drunter vorstellst.

von Klaus W. (mfgkw)


Lesenswert?

Niklas Gürtler schrieb:
> Ha, ich war der schnellste :-P

sorry, die Kleinen übersieht man schon mal :-)

von Micha (Gast)


Lesenswert?

Vielen Dank für die Berichtigung. Dann muss ich eine Kopie des structs 
in main anlegen, den Inhalt dort rein kopieren und damit arbeiten.

Klaus Wachtler schrieb:
> "funktioniert nicht" ist fast die schlechteste mögliche Beschreibung
Naja es passiert halt Blödsinn. Vor dem return &time steht irgendetwas 
in time.

A. K. schrieb:
> Abgesehen von dem bereits erwähnten Problem: Strukturen mit memcmp zu
> vergleichen ist ziemlich gefährlich, weil unsichtbare Löcher mit
> zufälligem Inhalt drin sein können, das Ergebnis also nicht unbedingt
> dem entspricht, was du dir drunter vorstellst.
Wie kann sichergestellt werden, dass das nicht passiert, oder muss jedes 
Element der Struktur einzeln verglichen werden?

Das struct hat folgende Typen als Member: uint16, uint8, uint8, uint8, 
uint8 in dieser Reihenfolge, allerdings soll CurrentTime darüberhinaus 
noch uint8, uint16, uint16 zusätzlich beinhalten.

Das schöne an memcmp wäre in diesem Fall, dass auch gleich klar ist 
welche Zeit einen früheren/späteren Zeitstempel darstellt.

von Karl H. (kbuchegg)


Lesenswert?

Micha schrieb:
> Vielen Dank für die Berichtigung. Dann muss ich eine Kopie des structs
> in main anlegen, den Inhalt dort rein kopieren und damit arbeiten.

Nicht unbedingt.
Sagt ja keiner, dass du aus der Funktion heraus nicht auch ein 
Strukturobjekt rausgeben darfst. Es darf halt kein Pointer sein, aber 
Strukturobjekte sind in Ordnung.
1
SCHEDULE addSpanToTime(const SCHEDULE time, uint8_t minutes)
2
{
3
  time.minute += minutes;
4
   // mehr code
5
  return time;
6
}
7
8
int main(void)
9
{
10
   uint16_t test;
11
   // mehr code
12
   test = compareTime(&CurrentTime, &addSpanToTime(&WTime, WTimeSpan));
13
   // mehr code
14
}

Allerdings erhebt sich ob der Kopiererzeugung die Frage, ob eine 
Spezialfunktion die genau die Fragestellung löst, die du hier mit 2 
Funktionsaufrufen zu klären versuchst, nicht die bessere Variante ist.


>> dem entspricht, was du dir drunter vorstellst.
> Wie kann sichergestellt werden, dass das nicht passiert, oder muss jedes
> Element der Struktur einzeln verglichen werden?

Genau so wird das gemacht.

> Das schöne an memcmp wäre in diesem Fall, dass auch gleich klar ist
> welche Zeit einen früheren/späteren Zeitstempel darstellt.

solange, bis du aus irgendeinem Grund die Member der Struktur in eine 
andere Reihenfolge bringst. Dann fliegt dein Programm aus heute 
naheliegenden Gründen kräftig auf die Schnauze. Ob der Grund allerdings 
in einem halben Jahr dann auch so naheliegend ist, ist eine ganz andere 
Geschichte. Denn dann denkst du da nicht mehr daran und suchst dir erst 
mal einen Wolf nach dem Problem.

Es hat schon seinen Grund, warum Zeiten normalerweise programmintern 
nicht in der Form Sekunden,Minuten,Stunden,Tag,Monat,Jahr gespeichert 
werden, sondern als Differenz von zb Sekunden zu einer Referenzzeit. 
Denn dann kann man leicht damit rechnen. Und wenn ein Programm oft mit 
Zeiten rechnen muss, dann lohnt es sich die interne Darstellung von der 
externen zu trennen und nur dann in die externe Form zu konvertieren, 
wenn auch wirklich eine Ausgabe ansteht (was in den meisten Programmen 
viel seltener notwendig ist als irgendwelche Rechnungen oder 
Vergleiche).

von (prx) A. K. (prx)


Lesenswert?

Micha schrieb:

> Wie kann sichergestellt werden, dass das nicht passiert,

Man kann Strukturen so definieren, dass weder mittendrin noch am Ende 
irgendwelche vom Compiler automatisch eingefügten Füllbytes auftreten. 
Man kann sie auch gepackt definieren, dann entfallen diese meist 
ebenfalls.

Man kann andererseits auch sicherstellen, dass in den Löchern 0 
drinsteht, indem man lokale Strukturen grundsätzlich mit memset() 
vorbesetzt. Bei globalen ist das sowieso schon der Fall.

Beides sind aber ziemlich unsaubere und fehleranfällige Methoden und 
nicht zu empfehlen.

> oder muss jedes
> Element der Struktur einzeln verglichen werden?

Das ist eindeutig der sicherste Weg.

> Das schöne an memcmp wäre in diesem Fall, dass auch gleich klar ist
> welche Zeit einen früheren/späteren Zeitstempel darstellt.

Ganz böse Falle. Das funktioniert nur bei big endian Byteorder. Denn 
wenn in memcmp byteweise verglichen wird, dann wird bei little endian 
erst das untere dann das obere Byte verglichen, was bei uint16 (oder 
grösser) nicht wirklich zum gewünschten Ergebnis führt.

von Micha (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Allerdings erhebt sich ob der Kopiererzeugung die Frage, ob eine
> Spezialfunktion die genau die Fragestellung löst, die du hier mit 2
> Funktionsaufrufen zu klären versuchst, nicht die bessere Variante ist.
Das Ergebnis von memcmp soll Teil einer komplexeren if-Abfrage werden 
und u.U. ist das Ergebnis vorher schon false und memcmp wird gar nicht 
mehr ausgeführt.

A. K. schrieb:
> Abgesehen von dem bereits erwähnten Problem: Strukturen mit memcmp zu
> vergleichen ist ziemlich gefährlich, weil unsichtbare Löcher mit
> zufälligem Inhalt drin sein können, das Ergebnis also nicht unbedingt
> dem entspricht, was du dir drunter vorstellst.
Gilt das auch für die "Behandlung" von Strukturen mittels memcpy, 
eeprom_block_read und eeprom_block_write wenn Quell- und Zielstruktur 
identisch sind?

von (prx) A. K. (prx)


Lesenswert?

Micha schrieb:

> Gilt das auch für die "Behandlung" von Strukturen mittels memcpy,
> eeprom_block_read und eeprom_block_write wenn Quell- und Zielstruktur
> identisch sind?

Nein, das ist kein Problem.

von Sebastian Hepp (Gast)


Lesenswert?

Füllbytes zwischen den Elementen einer Struktur lassen sich mit einer 
Compiler-Direktive verhindern.
1
struct ... {
2
...
3
} __attribute__((packed));

Aber besser wäre hier vielleicht eine Klasse mit Zuweisungs- und 
Vergleichoperator. =)

von Micha (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Es hat schon seinen Grund, warum Zeiten normalerweise programmintern
> nicht in der Form Sekunden,Minuten,Stunden,Tag,Monat,Jahr gespeichert
> werden, sondern als Differenz von zb Sekunden zu einer Referenzzeit.
> Denn dann kann man leicht damit rechnen. Und wenn ein Programm oft mit
> Zeiten rechnen muss, dann lohnt es sich die interne Darstellung von der
> externen zu trennen und nur dann in die externe Form zu konvertieren,
> wenn auch wirklich eine Ausgabe ansteht (was in den meisten Programmen
> viel seltener notwendig ist als irgendwelche Rechnungen oder
> Vergleiche).
Das leuchtet ein. Reichen würde es mir als uint32 ab 1.1.2000, da ich 
nicht damit rechne, dass ich das Gerätchen 2136 noch benutzen werde. Wie 
sieht es nun aber aus, wenn die Zeit gelegentlich an einen PC geschickt 
und von diesem aktualisiert werden soll? Dort liegt die Zeit ja als 
uint64 vor. Welche Vorgehensweise empfiehlt um einfach umrechnen zu 
können?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Micha schrieb:
> Dort liegt die Zeit ja als uint64 vor

Tut sie das?

Solltest Du damit das windows-interne Zeitformat SYSTEMTIME meinen, so 
ist eine Umrechnung leicht möglich, die zählt 100nsec-Intervalle seit 
1604. Also den SYSTEMTIME-Wert durch 10000 teilen, und schon hast Du die 
Sekunden seit 1604. Dann noch einen geeigneten Offset abziehen, und Du 
landest beim von Dir gewünschten Wert.

Alternativ könntest Du auch die Zeitbehandlungsfunktionen der 
C-Runtime-Library verwenden und den üblichen time_t-Wert bestimmen, der 
Sekunden seit 1970 zählt.

von Micha (Gast)


Lesenswert?

Ich dachte an die Benutzung von DATETIME (ticks). Innerhalb SYSTEMTIME 
liegt die Zeit ja nicht direkt vor, sondern muss erst berechnet werden, 
richtig? Bzw. nach FILETIME umgewandelt laut 
http://msdn.microsoft.com/en-us/library/windows/desktop/ms724950%28v=vs.85%29.aspx

von Vlad T. (vlad_tepesch)


Lesenswert?

A. K. schrieb:
>> oder muss jedes
>> Element der Struktur einzeln verglichen werden?
>
> Das ist eindeutig der sicherste Weg.

sicherlich nicht.
Irgend wann erweitert man seine Struktur und vergisst die 
Vergleichsroutine

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Micha schrieb:
> Innerhalb SYSTEMTIME liegt die Zeit ja nicht direkt vor, sondern
> muss erst berechnet werden, richtig? Bzw. nach FILETIME umgewandelt

Ja, sorry, das hatte ich durcheinandergebracht. Natürlich, FILETIME, den 
Typ meinte ich.

Du könntest aber auch die Funktion time verwenden, die liefert den 
allgemein üblichen Wert in ganzen Sekunden seit 1.1.1970.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Micha schrieb:
> Die Parameter, die an addSpanToTime übergeben werden, sollen nicht
> verändert werden.

Ich sehe Deine Parameter-Wahl jedoch als suboptimal an.

Würdest Du als ersten Parameter einen Pointer auf Deine Struct 
akzeptieren, wäre Dein Problem, dass time eine lokale Variable ist, 
sofort beseitigt. Ausserdem bräuchtest Du den Pointer auf die Struct 
nicht mehr als Return-Wert zurückzugeben, da dieser ja als Argument 
bereits da ist. Du könntest dann den Rückgabewert für etwas sinnvolleres 
benutzen.

Wenn Du tatsächlich eine Kopie der struct brauchst, dann dupliziere die 
struct in der aufrufenden Funktion und gib 2 Pointer runter, der eine 
ist dann Pointer auf die Source-Struct und der andere der Pointer auf 
die Target-Struct.

Meines Erachtens kann in C auf das Feature, structs by value 
heruntergeben zu können, komplett verzichtet werden. Das macht nur 
Ärger.

von (prx) A. K. (prx)


Lesenswert?

Vlad Tepesch schrieb:

> sicherlich nicht.
> Irgend wann erweitert man seine Struktur und vergisst die
> Vergleichsroutine

Was wäre die Alternative? Abgesehen davon, das Programmieren bleiben zu 
lassen und auf Gärtner umzuschulen. Denn dieser Aspekt tangiert saubere 
Programmierung und Modularisierung. Wenn man den Code zur Strukt an 
einer Stelle konzentriert, und man es dann immer noch vergisst, dann 
wärs Zeit für die erwähnte Umschulung.

Zumindest fällt mir keine Lösung ein, die innerhalb der Vorgabe bleibt, 
eine Struct zu verwenden. Und in der du bei Vergesslichkeit nicht auf 
die Schnauze fliegst. Zumal alle memcmp-Varianten aufgrund inhärent 
plattformabhängigen Verhaltens bei Inhalten >8Bit für mich ausscheiden.

Dass man Timestamps, die mit grösser/kleiner verglichen werden, auch als 
geschlossene Integer darstellen kann wurde schon erwähnt. Wenn der 
Vergleich aber das einzige Motiv für diese Darstellung sein sollte, dass 
ist das aufgrund der Konvertierung unverhältnismässig aufwendig.

von Simon K. (simon) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Meines Erachtens kann in C auf das Feature, structs by value
> heruntergeben zu können, komplett verzichtet werden. Das macht nur
> Ärger.

Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit 
struct-By-Value gestolpert. Kannst du ein Beispiel nennen?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Simon K. schrieb:
> Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit
> struct-By-Value gestolpert. Kannst du ein Beispiel nennen?

Abgesehen von der miesen Performance kann Dir durchaus der Stack 
überlaufen, wenn Du Structs mit vielen bzw. großen Struct-Members an 
Funktionen runtergibst. Schließlich ist die Stack-Größe gerade auf 
Mikrocontrollern begrenzt.

Aber selbst auf Unix-/Linux-/Windows-Systemen mache ich das nicht, 
schließlich müssen alle Struct-Members kopiert werden - auch wenn Du nur 
einen Wert aus der Struct in der aufgerufenen Funktion nutzt. Sowas ist 
einfach Ressourcen-Verschwendung ohne Ende.

von Simon K. (simon) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Simon K. schrieb:
>> Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit
>> struct-By-Value gestolpert. Kannst du ein Beispiel nennen?
>
> Abgesehen von der miesen Performance kann Dir durchaus der Stack
> überlaufen, wenn Du Structs mit vielen bzw. großen Struct-Members an
> Funktionen runtergibst. Schließlich ist die Stack-Größe gerade auf
> Mikrocontrollern begrenzt.
> Aber selbst auf Unix-/Linux-/Windows-Systemen mache ich das nicht,
> schließlich müssen alle Struct-Members kopiert werden - auch wenn Du nur
> einen Wert aus der Struct in der aufgerufenen Funktion nutzt. Sowas ist
> einfach Ressourcen-Verschwendung ohne Ende.

Nach allgemeinem Tenor habe ich es so verstanden, dass der Compiler ab 
einer bestimmten Größe automatisch "im Hintergrund" By-Reference 
übergibt.

Nachgeprüft habe ich es noch nicht.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Simon K. schrieb:

> Nach allgemeinem Tenor habe ich es so verstanden, dass der Compiler ab
> einer bestimmten Größe automatisch "im Hintergrund" By-Reference
> übergibt.

"Ab einer bestimmten Größe" klingt ziemlich schwammig, oder? ;-)

Was passiert denn dann, wenn Du einen Struct-Member in der aufgerufenen 
Funktion änderst, wenn "ab einer bestimmten Größe" die Struct "im 
Hintergrund" By-Reference heruntergegeben wird?

> Nachgeprüft habe ich es noch nicht.

Ich kann da nur vermuten, nämlich folgendes:

1. Ab einer bestimmten Größe wird die Struct vor dem Aufruf der Funktion
   bereits kopiert
2. Es wird eine Referenz (sprich Pointer) auf die Kopie heruntergegeben.

Das könnte klappen: Aber die Kopie der Struct liegt trotzdem im SRAM - 
egal ob Stack, Heap oder sonstwo - und kann den µC-Speicher zum platzen 
bringen.

Ich persönlich finde es einfach unschön - weil schlecht kalkulierbar, 
was da passiert. Und wie gesagt: Die Kopiererei kostet einfach nur 
Ressourcen. Wenn ich wirklich eine Kopie brauche, dann sollte ich auch 
selbst die Struct kopieren. Dann weiß ich wenigstens, was da abgeht.

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:

> Meines Erachtens kann in C auf das Feature, structs by value
> heruntergeben zu können, komplett verzichtet werden. Das macht nur
> Ärger.

lustigerweise war das sogar mal so.
In den aller-aller-aller-ersten C Versionen wurden Strukturen genau so 
übergeben, wie es mit Arrays heute noch passiert. Es wurde immer ein 
Pointer übergeben. Bei kleinen Strukturen kann das dann auch schon mal 
kontraproduktiv sein.

Übrigens:
Das mit dem Ärger würde ich nicht unterschreiben.

Das man als Programmierer mitdenken muss, ist unumstritten. Ob eine 
Funktion einen Pointer nimmt oder doch gleich das ganze Objekt verrät 
dem Aufrufer auch, was die Funktion vorhat.

  foo( const struct Obj* pObj )

ich kann mich drauf verlassen, dass die Funktion das Objekt nicht 
verändern wird


  foo( struct Obj* pObj )

ich muss damit rechnen, dass die Funktion das Objekt nicht verändern 
wird

  foo( struct Obj obj )

genau wie der erste Punkt: ich kann mich darauf verlassen, dass die 
Funktion das Objekt nicht verändert, allerdings benötigt die Funktion 
eine Kopie des Objektes (aus welchem Grund auch immer)

von Karl H. (kbuchegg)


Lesenswert?

> Und wie gesagt: Die Kopiererei kostet einfach nur
> Ressourcen. Wenn ich wirklich eine Kopie brauche, dann sollte
> ich auch selbst die Struct kopieren. Dann weiß ich wenigstens,
> was da abgeht.

Damit hast du aber nichts (*) gewonnen.

Wenn eine Kopie notwendig ist, dann ist sie notwendig.
Ob die Kopie durchs Argument Passing erzeugt wird, oder ob sich die 
Funktion selbst die Kopie erzeugt, ist ziemlich egal.

(*) ok. fast nichts
Du kannst den Speicher für die Kopie per malloc erzeugen und dann auf 
'out of memory' reagieren. Mehr Vorteile hast du aber nicht.

von (prx) A. K. (prx)


Lesenswert?

Simon K. schrieb:

> Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit
> struct-By-Value gestolpert.

Ich schon. Anfangs neigten Compiler dazu, struct returns über statischen 
Speicher abzuwickeln. Das war natürlich nicht reentrant. Ist aber schon 
ein paar Jahrzehnte her.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> lustigerweise war das sogar mal so.

Ich kenne das auch noch, ich habe damals C im Jahr 1984 auf einem 
UNIX-SYSTEM-V (VME-BUS-)System auf MC68020-Basis von der Pike auf 
gelernt - natürlich mit einem K&R in der Hand ;-)

Viele der späteren Erweiterungen in C (ANSI, C99) sind zwar sinnvoll, 
einige halte ich aber nachwievor für überflüssig. Ich programmiere meist 
"defensiv", d.h. ich programmiere im Zweifel nach "altem Stil". Dann 
weiß ich wenigstens, was passiert. Und ich käme niemals im Leben auf die 
Idee, eine Struct by-value herunterzugeben.

Kennt jemand eine Funktion aus der libc, die eine struct by-value als 
Argument erwartet? Ich nicht. Für Struct-Übergabe über Pointer kann ich 
aber jede Menge Beispiele nennen ;-)

> Das mit dem Ärger würde ich nicht unterschreiben.

Okay, da habe ich vielleicht etwas übertrieben. Ich will nur 
gewährleisten, dass meine Funktion das erwartete auch tut. Deshalb 
begebe ich mich im Zweifel nicht in irgendwelche Abenteuer. Da weiß ich, 
was ich habe.

Karl Heinz Buchegger schrieb:
> Damit hast du aber nichts (*) gewonnen.

Doch: Sicherheit.

Ausserdem kenne ich kaum Anwendungsfälle, wo ich eine Struct als Kopie 
lediglich temporär in einer Funktion brauche, d.h. eine Struct auf dem 
Stack. Meist brauche ich Structs und deren Inhalte global über mehrere 
Funktionen hinweg.

Gruß,

Frank

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:

> Kennt jemand eine Funktion aus der libc, die eine struct by-value als
> Argument erwartet? Ich nicht. Für Struct-Übergabe über Pointer kann ich
> aber jede Menge Beispiele nennen ;-)

Nichts ist in C so präsent, wie all die Designentscheidungen, die in der 
Frühzeit von C von K&R persönlich getroffen wurden :-)

von Simon K. (simon) Benutzerseite


Lesenswert?

Gerade in professionellen Libraries sieht man auch oft die Übergabe über 
einen Pointer, deshalb meine Frage, warum man das macht.

Die aufgeführten Punkte für und dagegen sind interessant.

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:

> Ausserdem kenne ich kaum Anwendungsfälle, wo ich eine Struct als Kopie
> lediglich temporär in einer Funktion brauche, d.h. eine Struct auf dem
> Stack. Meist brauche ich Structs und deren Inhalte global über mehrere
> Funktionen hinweg.

Ich konstruiere mal
1
typedef struct {
2
  uint8_t red;
3
  uint8_t grn;
4
  uint8_t blu;
5
} RGB;
6
7
RGB blend( RGB color1, RGB color2, double t )
8
{
9
  RGB result;
10
  result.red = color1.red * t + color2.red * ( 1.0 - t );
11
  result.grn = color1.grn * t + color2.grn * ( 1.0 - t );
12
  result.blu = color1.blu * t + color2.blu * ( 1.0 - t );
13
14
  return result;
15
}
16
17
RGB white = { 255, 255, 255 };
18
RGB black = {   0,   0,   0 };
19
RGB blue  = {   0,   0, 255 };
20
21
int main()
22
{
23
  RGB gray;
24
25
  gray = blend( black, white, 0.4 );
26
}

sollte klar sein, was das macht. Zudem ist es straight forward und 
simpel.

Jetzt das ganze komplett mit Pointern
1
typedef struct {
2
  uint8_t red;
3
  uint8_t grn;
4
  uint8_t blu;
5
} RGB;
6
7
void blend( const RGB* color1, const RGB* color2, double t, RGB* result )
8
{
9
  result->red = color1->red * t + color2->red * ( 1.0 - t );
10
  result->grn = color1->grn * t + color2->grn * ( 1.0 - t );
11
  result->blu = color1->blu * t + color2->blu * ( 1.0 - t );
12
}
13
14
RGB white = { 255, 255, 255 };
15
RGB black = {   0,   0,   0 };
16
RGB blue  = {   0,   0, 255 };
17
18
int main()
19
{
20
  RGB gray;
21
22
  blend( &black, &white, 0.4, &gray );
23
}


Ich seh da keinen großen Unterschied in der Sicherheit, ausser das mir 
die 2.ter Version auch relativ leicht ermöglicht das hier zu tun
1
int main()
2
{
3
  RGB* gray;
4
5
  blend( &black, NULL, 0.4, &gray );
6
}

was in der ersten Version syntaktisch schon mal gar nicht möglich ist. 
Die Verantwortung liegt in diesem Fall bei der Funktion, die prüfen muss 
ob die Pointer gültig sind oder nicht.
Und ob der Compiler jetzt ein RGB Objekt an die Funktion übergibt oder 
erst mal einen Pointer erzeugen muss, das schenkt sich gegenseitig 
wirklich nicht viel.
Ganz abgesehen davon, dass mir die Pointer Version das Verwenden der 
Funktion zb in Argumentlisten erschwert.
Gut. ist jetzt bei Farben nicht so das Thema. Man wird eher selten 
schreiben

  dirty_blue = blend( blend( black, white, 0.3 ), blue, 0.7 );

aber möglich wäre es. Und zb bei Vektoren kann das dann schon anders 
aussehen

   distance = vec_len( vec_sub( end, start ) );

Aber wie so oft gilt: "One size fits all" funktioniert bei 
Baseball-Kappen, aber nicht in der Programmierung. 'Das' Patentrezept 
gibt es nicht.

von Karl H. (kbuchegg)


Lesenswert?

Simon K. schrieb:
> Gerade in professionellen Libraries sieht man auch oft die Übergabe über
> einen Pointer, deshalb meine Frage, warum man das macht.

ich würde sagen:

Weil man es meistens nicht mit so kleinen Strukturen zu tun hat, wie die 
die ich da oben (mit Absicht) skizziert habe.

Dazu kommt, dass Libraries dem Compiler normalerweise nicht in 
Source-Form zur Verfügung stehen, d.h. mit inlining ist da dann nicht 
mehr viel zu machen damit der Compiler das Erzeugen der Kopie unter 
Umständen verhindern kann. Denn die blend Funktion von oben kann ein 
Compiler relativ problemlos inlinen und landet dann anstelle von
1
int main()
2
{
3
  RGB gray;
4
5
  gray = blend( black, white, 0.4 );
6
}

bei
1
int main()
2
{
3
  RGB gray;
4
5
  gray.red = black.red * 0.4 + white.red * 0.6;
6
  gray.grn = black.grn * 0.4 + white.grn * 0.6;
7
  gray.blu = black.blu * 0.4 + white.blu * 0.6;
8
}

(in diesem Fall sind black und white Konstante. Nehmen wir mal an, das 
wären sie nicht: Besser gehts nicht mehr. Das ist schon optimaler Code. 
Das erzeugen der Kopie ist komplett weggefallen)

von Micha (Gast)


Lesenswert?

Da hab ich ja was angestossen... ;-)

Ich werde die Zeit jetzt als uint64 implementieren, dann muss ich mich 
nicht mit den einzelnen Membern rumschlagen und kann sie direkt mit 
Windows (und Linux?) austauschen. Außerdem werden Rechnungen, Vergleiche 
und sonstige Operationen vereinfacht, was nicht unbedingt negativ ist.

Danke euch allen für die Anregungen und Tipps.

von Micha (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> so ist eine Umrechnung leicht möglich, die zählt 100nsec-Intervalle seit
> 1604. Also den SYSTEMTIME-Wert durch 10000 teilen, und schon hast Du die
> Sekunden seit 1604.
Du verunsicherst mich: es muss doch durch 10000000 geteilt werden, 
nicht?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Micha schrieb:
> Du verunsicherst mich:

Ist nicht beabsichtigt gewesen. Ja, wenn nicht Millisekunden gewünscht 
sind, ist "Dein" Quotient der richtigere. (Keine Ahnung, warum ich 
ausgerechnet an Millisekunden denken musste)

von Micha (Gast)


Lesenswert?

Ich habe meinen Code jetzt abgeändert, so dass die Zeit in einer uint64 
gespeichert ist. Insgesamt vereinfacht das das Programm, allerdings ist 
aber auch ein Problem neu entstanden.

Im Code ist ebenfalls eine RTC nach 
http://www.mikrocontroller.net/articles/AVR_-_Die_genaue_Sekunde_/_RTC#Beispielprogramm 
implementiert:
1
#define F_CPU                          12000000UL           // cpu frequency
2
#define F_INTERRUPTS                   10000                // interrupts per second
3
#define CPU_CYCLES_BETWEEN_INTERRUPTS  (F_CPU/F_INTERRUPTS)
4
#define ONE_SECOND                     (10000000UL)         // 100nsec steps per second
5
#define ONE_MINUTE                     (60 * ONE_SECOND)    // 100nsec steps per minute
6
7
static uint64_t   CurrentTime       = 0;
8
static int16_t    InterruptTicks    = 0;  // count how often timer ISR was executed each second
9
static int16_t    ClockDeviation    = 0;  // deviation from cpu frequency: +100 means F_CPU is 12000100; +100 means F_CPU is 11999900
10
11
void TIMER1_COMPA_vect(void)
12
{
13
   static int16_t TimeError = 0;    // RTC error compensation
14
15
   // RTC error correction
16
   if ( TimeError >= CPU_CYCLES_BETWEEN_INTERRUPTS )        // RTC too fast
17
   {
18
      //InterruptTicks += 0  // add zero ticks
19
      TimeError -= CPU_CYCLES_BETWEEN_INTERRUPTS;
20
   }
21
   else if ( TimeError <= -CPU_CYCLES_BETWEEN_INTERRUPTS )  // RTC too slow
22
   {
23
      InterruptTicks += 2; // add two ticks
24
      TimeError += CPU_CYCLES_BETWEEN_INTERRUPTS;     
25
   }
26
   else
27
   {
28
     InterruptTicks += 1; // add one tick
29
   }
30
31
   // compute time every second
32
   if ( InterruptTicks >= F_INTERRUPTS )  // one second has passed
33
   {
34
      CurrentTime += ONE_SECOND;          // add one second
35
      TimeError += ClockDeviation;        // accumulate deviation
36
      InterruptTicks -= F_INTERRUPTS;     // subtract number of interrupts per second
37
   }
38
}

Wenn ich nun eine neue Zeitinformation als uint64 vom PC empfange, 
schreibe ich diese 1:1 nach CurrentTime. Für die RTC sollte ich nun aber 
auch InterruptTicks aktualisieren. Folgender Code würde das richtige 
Ergebnis liefern:
1
InterruptTicks = (CurrentTime % ONE_SECOND) / (ONE_SECOND / F_INTERRUPTS);

Mit dem entstehenden Rundungsfehler könnte ich sogar leben, aber der 
Programmspeicher des verwendeten Mega168 ist damit zu 99.9% belegt.

Ich könnte ONE_SECOND auch in einer Schleife von CurrentTime 
subtrahieren um die Modulo-Operation zu umgehen, aber die 
Ausführungszeit wäre wohl trotzdem recht lange, da die Schleife 
möglicherweise sehr oft durchlaufen werden muss.

Wie könnte ich das noch optimieren?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wie wäre es, wenn Du auf dem PC (auf dem wohl offensichtlich von Dir 
selbst geschriebene Software läuft) die Umrechnung erfolgen lässt und 
statt des uint64_t, von dem Du große Teile wegwerfen willst, gleicht den 
richtigen Sekundenwert überträgst? Dann kannst Du ONE_SECOND auf 1 
setzen, was die Rechnerei in Deiner Interruptroutine vereinfacht, und 
außerdem brauchst Du dann schlichtweg auf Deinem µC nicht mehr mit 
uint64_t zu hantieren.

von Micha (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Wie wäre es, wenn Du auf dem PC (auf dem wohl offensichtlich von Dir
> selbst geschriebene Software läuft) die Umrechnung erfolgen lässt und
> statt des uint64_t, von dem Du große Teile wegwerfen willst, gleicht den
> richtigen Sekundenwert überträgst?
Das wäre das einfachste, ja. Ich würde allerdings die Uhr gerne über den 
PC abgleichen können (durch verändern von ClockDeviation), also uC-Uhr 
zu PC-Uhr. Da der Controller über V-USB mit selbstgeschriebener 
C#-Software kommuniziert spielen da noch ordentlich Verzögerungen mit 
rein, das ist mir klar. Nichtsdestotrotz denke ich, dass das Ergebnis 
besser wird je genauer die übertragene Zeit ist.

von Karl H. (kbuchegg)


Lesenswert?

Micha schrieb:

> rein, das ist mir klar. Nichtsdestotrotz denke ich, dass das Ergebnis
> besser wird je genauer die übertragene Zeit ist.

Man kanns auch übertreiben.
Für eine normale Uhr und normalen Uhrenbetrieb reicht 1 Sekunde meistens 
locker aus. Ob eine µC Uhr 1 Sekunde vor oder nach geht, spielt in >98% 
aller Anwendungen überhaupt keine Rolle.

von Micha (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Man kanns auch übertreiben.
> Für eine normale Uhr und normalen Uhrenbetrieb reicht 1 Sekunde meistens
> locker aus. Ob eine µC Uhr 1 Sekunde vor oder nach geht, spielt in >98%
> aller Anwendungen überhaupt keine Rolle.
Das ist klar und darum ging es mir gar nicht. Entschuldigung für die 
missverständliche Erklärung.

Mein Gedankengang war folgender:
- Uhr am PC wird genau gestellt, an das USB Device gesendet und in eine 
XML-Datei geschrieben
- nach (bspw.) 2 Monaten wird die Uhr des PCs erneut genau gestellt und 
die Zeit vom Device gelesen
- nun wird die vergangene Zeit seit dem ersten Abgleich berechnet und 
die daraus resultierende Abweichung der Quarzfrequenz
- dieser Wert wird als Korrekturwert für die RTC im Device benutzt

Angenommen die Verarbeitung und Übertragung der Zeiten dauert je 1 Sek., 
dann sind das 2 Sek. Abweichung in 2 Monaten, bei längerem Abstand 
zwischen den Kalibrierungen entsprechend weniger.

von Karl H. (kbuchegg)


Lesenswert?

Wieviele Schwingungen macht dein Quarz in 1 Sekunde.
Du wirst ja eine Variation des Problems machen: Wieviele 
Quarzschwingungen muss ich abzählen, bis 1 Sekunde vergangen ist. Das 
wiederrum bedeutet ja nichts anderes als dass du eine ganze Zahl 
brauchst, die als Vergleichswert herhalten muss.

Wenn jetzt deine Uhr in 2 Monaten um 2 Sekunden falsch geht, wieviele 
Schwingungen Different sind das dann in 1 Sekunde?
Wie sinnvoll ist es, den Quarz bis auf die letzte Schwingung 
auszumessen, wenn er durch Veränderungen der Umgebungstemperatur in 
seiner genauen Frequenz wesentlich mehr als diese 1 Schwingung sich 
verändert?

von Micha (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Wenn jetzt deine Uhr in 2 Monaten um 2 Sekunden falsch geht, wieviele
> Schwingungen Different sind das dann in 1 Sekunde?
Wenn ich mich nicht verrechnet habe 4,5.

> Wie sinnvoll ist es, den Quarz bis auf die letzte Schwingung
> auszumessen, wenn er durch Veränderungen der Umgebungstemperatur in
> seiner genauen Frequenz wesentlich mehr als diese 1 Schwingung sich
> verändert?
Punkt für dich.

Immerhin könnte aber die fertigungsbedingte Toleranz verringert werden.

von Micha (Gast)


Lesenswert?

Micha schrieb:
> Immerhin könnte aber die fertigungsbedingte Toleranz verringert werden.
Das ist schlecht ausgedrückt. Ersetze verringert durch ausgeglichen.

Aber das meintest du ja gar nicht. Ich werde jetzt machen was Rufus 
vorgeschlagen hat: die Sekunden direkt in einem uint32 ablegen. 
Zusätzlich übertrage ich vielleicht noch die ms als uint16 und 
verheirate die mit InterruptTicks.

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.