Forum: Compiler & IDEs FX3: C-Mehrfachdefinition / Include-Issue


von P. K. (pek)


Lesenswert?

Hallo zusammen

In meinem Header-File definiere ich ein konstantes Array, welches 
Komandoeigenschaften beinhaltet "cyfx3fw.h":
1
#ifndef _INCLUDED_CYFX3FW_H_
2
#define _INCLUDED_CYFX3FW_H_
3
4
...
5
6
typedef struct Stm32Cmd_t
7
{
8
  CyBool_t CmdVald;
9
  CyBool_t LongRun;
10
  uint8_t  ReqDCnt;
11
  uint8_t  RspDCnt;
12
} Stm32Cmd_t;
13
14
const Stm32Cmd_t Stm32Cmd[256] = {{.CmdVald = CyFalse, 
15
                                   .LongRun = CyFalse, 
16
                                   .ReqDCnt = 0x00, 
17
                                   .RspDCnt = 0x00},
18
                         [0x80] = {.CmdVald = CyFalse, 
19
                                   .LongRun = CyFalse, 
20
                                   .ReqDCnt = 0x00, 
21
                                   .RspDCnt = 0x00}};
22
23
...
24
25
#endif /* _INCLUDED_CYFX3FW_H_ */

Beim Build kriege ich folgende Fehlermeldung:
1
./cyfx3fwdscr.o:(.rodata.Stm32Cmd+0x0): multiple definition of `Stm32Cmd'
2
./cyfx3fw.o:(.rodata.Stm32Cmd+0x0): first defined here
3
collect2: ld returned 1 exit status
4
cs-make: *** [Fx3Fw.elf] Error 1

"cyfx3fw.h" steht sowohl inder include-List von "cyfx3fw.c" als auch von 
"cyfx3fwdscr.c", sollte aber wegen des "_INCLUDED_CYFX3FW_H_"
kein Problem sein...

.. wo stehe ich hier auf dem Schlauch?

von Dr. Sommer (Gast)


Lesenswert?

P. K. schrieb:
> "cyfx3fw.h" steht sowohl inder include-List von "cyfx3fw.c" als auch von
> "cyfx3fwdscr.c", sollte aber wegen des "_INCLUDED_CYFX3FW_H_"
> kein Problem sein...
Doch. Wie du richtig festgestellt hast, taucht das Array dann in beiden 
.c Dateien auf, somit in beiden .o Dateien und wäre somit doppelt in der 
Ausgabedatei. Du musst das Array nur in genau einer .c Datei definieren. 
Wenn du von anderen .c Dateien drauf zugreifen willst, musst du es in 
denen (oder im Header) mit "extern" und ohne Initialisierung 
deklarieren.

von Peter II (Gast)


Lesenswert?

P. K. schrieb:
> .. wo stehe ich hier auf dem Schlauch?

die Variabel muss ein einer C Datei zu hause sein. In der Header Datei 
muss ein extern drin sein.

Du legst ja in der Header-Datei eine Variabel an, mit wird sie in jeder 
Übersetzungseinheit auch neu eingebunden.

von Bernhard M. (boregard)


Lesenswert?

Hi,

normaleweise sollte man die definition in ein C-Source packen, und nicht 
in den Header. In den header kann dann die "extern" deklaration machen.

Gruß,
Bernhard

von Karl H. (kbuchegg)


Lesenswert?

P. K. schrieb:

> "cyfx3fw.h" steht sowohl inder include-List von "cyfx3fw.c" als auch von
> "cyfx3fwdscr.c", sollte aber wegen des "_INCLUDED_CYFX3FW_H_"
> kein Problem sein...
>
> .. wo stehe ich hier auf dem Schlauch?

Um da noch nachzuhaken:
Der springende Punkt ist, dass jede C-Datei für sich OHNE ANSEHEN VON 
ANDEREN C DATEIEN übersetzt wird.
Dein Include Guard, der dich davor schützt, das dieselben Definitionen 
mehrfach gesehen werden, wenn EINE C-Datei übersetzt wird (weil zb 
derselbe #include über mehrere ineinander geschachtelte #include 
auftereten kann), kann dir daher nicht helfen, wenn in 2 C-Dateien 
derselbe #include auftaucht.

Jedes C-File ist für sich unabhängig von allen anderen. Oder anders 
gesagt: Wenn der Compiler cyfx3fw.c compiliert, dann interessiert ihn 
nicht welche #include bzw. welche #define vorgenommen wurden als 
cyfx3fwdscr.c compiliert wurde. Sobald er sich cyfx3fw.c vornimmt, 
beginnt alles wieder bei 0. Und natürlich vice verse, wenn cyfx3fwdscr.c 
compiliert wird.

von P. K. (pek)


Lesenswert?

Mein Ziel war es, alle Defines und beschreibenden Konstanten welche das 
System konfigurieren (und gelegentlich angepasst warden müssen) an einem 
Ort zu haben (minimiert das Suchen, wenn man nach zwei Jahren was 
kleines ändern will).
1
static const Stm32Cmd_t Stm32Cmd[256] = { ...

löst für mich das Problem soweit. Irgendwelche Drawbacks?

von Bitflüsterer (Gast)


Lesenswert?

>Irgendwelche Drawbacks?

Das müsste man mal im Kontext sehen. Falls Du nun zwei Dateien mit 
includes hast, wie oben beschrieben, die jeweils "static const 
Stm32Cmd_t ..." enthalten, dann hast Du auch tatsächlich (d.h. physisch) 
zwei gleichnamige Variablen, die jeweils nur von dem Modul zugegriffen 
werden, in denen sie erscheinen. Du hast aber nicht eine Variable 
deren Inhalt von zwei verschiedenen Modulen aus zugegriffen werden. Das 
aber war, nach Deinem ersten Posting Deine Absicht.

von micha (Gast)


Lesenswert?

Hm, wenn's im header steht hast Du IMHO dann x eigenständige Kopien 
(mit den selben Daten) - was Du ja wohl nicht willst

von Bitflüsterer (Gast)


Lesenswert?

Ich mache es so (aber das ist natürlich nicht die einzige Lösung)
[Von den include-guards sehe ich mal ab].

Die H-Datei:
1
EXTERN int a;


Erste C-Datei:
1
// mit dem define wird eine Deklaration erzeugt
2
#define EXTERN extern
3
4
int function1 (...)
5
{
6
   a = ...;
7
}

Zweite C-Datei:
1
// mit dem define wird eine Definition erzeugt
2
#define EXTERN
3
4
int function2 (...)
5
{
6
   a = ...;
7
}

von Bitflüsterer (Gast)


Lesenswert?

Tschuldigung, mir ist das include entschlüpft. Es muss natürlich sein:
Vielleicht kann ein mod meinen post oben editieren und diesen hier 
löschen.


Die H-Datei:

EXTERN int a;



Erste C-Datei:

// mit dem define wird eine Deklaration erzeugt
#define EXTERN extern
#include "hdatei.h"

int function1 (...)
{
   a = ...;
}


Zweite C-Datei:

// mit dem define wird eine Definition erzeugt
#define EXTERN
#include "hdatei.h"

int function2 (...)
{
   a = ...;
}

von Peter II (Gast)


Lesenswert?

Bitflüsterer schrieb:
> Ich mache es so (aber das ist natürlich nicht die einzige Lösung)

und von wem wird dann die Header Datei eingebunden?

von Peter II (Gast)


Lesenswert?

Bitflüsterer schrieb:
> Tschuldigung, mir ist das include entschlüpft. Es muss natürlich sein:

dann hast du die Datei auch mehrfach. In jeder Übersetzungseinheit ist 
dein define wieder weg.

von Bitflüsterer (Gast)


Lesenswert?

>und von wem wird dann die Header Datei eingebunden?
Das habe ich mich auch gefragt. Aber da war es zu spät. :-)
Danke für den Hinweis.

von Bitflüsterer (Gast)


Lesenswert?

>In jeder Übersetzungseinheit ist dein define wieder weg.

Das soll es ja auch. Schauen wir uns mal die Expansion nach dem include 
an.

Erste C-Datei:
1
// mit dem define wird eine Deklaration erzeugt
2
extern int a;
3
4
int function1 (...)
5
{
6
   a = ...;
7
}

Zweite C-Datei:
1
// mit dem define wird eine Definition erzeugt
2
int a;
3
4
int function2 (...)
5
{
6
   a = ...;
7
}

von Peter II (Gast)


Lesenswert?

Bitflüsterer schrieb:
>>In jeder Übersetzungseinheit ist dein define wieder weg.
>
> Das soll es ja auch. Schauen wir uns mal die Expansion nach dem include
> an.

das ist ja umständlich und fehleranfällig.

warum nicht ein Header und eine C Datei nehmen wie es üblich ist?

von Bitflüsterer (Gast)


Lesenswert?

>das ist ja umständlich und fehleranfällig.
Das kann man so oder so sehen. Was der eine für umständlich hält kann 
der andere durchaus handhaben. Ich habe allerdings auch vorausgeschickt, 
das es andere Methoden gibt. Das Grundproblem ist allerdings, das man 
immer getrennte Übersetzungseinheiten hat. Also muss in jeder 
Übersetzungseinheit ein Unterschied vorhanden sein, der kontrolliert, ob 
eine Definition oder eine Deklaration erzeugt wird.

>warum nicht ein Header und eine C Datei nehmen wie es üblich ist?

Es ist genau eine Header-Datei.

Es sind zwei C-Dateien weil es eben zwei C-Dateien sind. Ich weiss 
garnicht, was ich darauf sagen soll, als das es üblich ist, grössere 
Projekte in mehrere C-Dateien zu unterteilen. So hat es der TO ja auch 
beabsichtigt, so mache ich es auch und so machen es viele Andere.

von Peter II (Gast)


Lesenswert?

Bitflüsterer schrieb:
>>das ist ja umständlich und fehleranfällig.
> Das kann man so oder so sehen. Was der eine für umständlich hält kann
> der andere durchaus handhaben. Ich habe allerdings auch vorausgeschickt,
> das es andere Methoden gibt. Das Grundproblem ist allerdings, das man
> immer getrennte Übersetzungseinheiten hat. Also muss in jeder
> Übersetzungseinheit ein Unterschied vorhanden sein, der kontrolliert, ob
> eine Definition oder eine Deklaration erzeugt wird.

nicht für die normalen C Dateien. Es gibt es noch ein 3. C Datei und 
dort steht die variable drin.

>>warum nicht ein Header und eine C Datei nehmen wie es üblich ist?
>
> Es ist genau eine Header-Datei.
gut, aber keine C Datei dazu.

> Es sind zwei C-Dateien weil es eben zwei C-Dateien sind. Ich weiss
> garnicht, was ich darauf sagen soll, als das es üblich ist, grössere
> Projekte in mehrere C-Dateien zu unterteilen. So hat es der TO ja auch
> beabsichtigt, so mache ich es auch und so machen es viele Andere.
ist ja ok, dann leg einfach noch eine 3. Datei an und leg die Variable 
dort ab. Schon können die anderen C Dateien gleich sein.

von P. K. (pek)


Lesenswert?

Auf meine Konstante wird nur in einem .c-File zugegriffen.

Ich gehe davon aus, dass in den anderen .c-Files (welche zwar #defines 
aus dem .h-file kennen, aber die Konstante nicht benutzen) der Compiler 
die (unnötige) Konstante wieder entsorgt (?).

Falls nicht: die 512 Bytes und 512 Bools sind nun ja nicht so ein 
Riesenposten, die Bequemlichkeit, alle Config-Daten (Konstanten & 
Defines) in einem File zu haben, würde den Overhead mehr als 
rechtfertigen.

von micha (Gast)


Lesenswert?

P. K. schrieb:
> Falls nicht: die 512 Bytes und 512 Bools sind nun ja nicht so ein
> Riesenposten, die Bequemlichkeit, alle Config-Daten (Konstanten &
> Defines) in einem File zu haben, würde den Overhead mehr als
> rechtfertigen.

Wenn Du die "Konstanten & Defines" in eine eigene c-Datei auslagerst 
und einen header mit "extern" machst, hast Du auf saubere Weise genau 
das selbe ohne n*1kByte zu verschwenden.

von Bitflüsterer (Gast)


Lesenswert?

>nicht für die normalen C Dateien. Es gibt es noch ein 3. C Datei und
>dort steht die variable drin.

Das kann man machen. Hat aber auch Nachteile. Beide Methoden haben Vor- 
und Nachteile. Denn dann hast Du eine H-Datei mit den Typen und 
extern-Verweisen und eine C-Datei mit den Typen. Willst Du den Typ 
ändern, dann musst Du zwei Dateien editieren. Die C-Datei mit den 
Definitionen und die H-Datei mit den Deklarationen.

                          -------------

Eine andere Methode ist die Verwendung von "Zugriffsfunktionen".

h-Datei:
1
typdef int ATyp;
2
3
void SetA (ATyp x);
4
ATyp GetA (void);

C-Datei mit Zugriffsfunktionen:
1
ATyp a;
2
3
void SetA(ATyp x) { a = x; }
4
ATyp GetA (void) { return a; }

von Karl H. (kbuchegg)


Lesenswert?

Bitflüsterer schrieb:
>>das ist ja umständlich und fehleranfällig.
> Das kann man so oder so sehen. Was der eine für umständlich hält kann
> der andere durchaus handhaben.


Ich hab das vor Jahren auch eine Zeitlang so gemacht, dass ich ein 
EXTERN per Bedarf in einem C-File wegdefiniert habe. Das funktioniert 
auch ganz gut.

Aber

Das Problem taucht genau dann auf, wenn du nicht nur Definition und 
Deklaration in einem einzigen Header File haben willst, sondern wenn 
dann auch noch Initialisierungen ins Spiel kommen. Spätestens dann wirds 
nämlich aufwändig und unübersichtlich.

Das Problem: Bei
1
extern int a = 5;
gewinnt die Initialisierung. Das ist eine Definition und keine 
Deklaration. Womit der ganze Hack rund um

Header
1
#ifndef EXTERN
2
#define EXTERN extern
3
#endif
4
5
EXTERN int a = 5;

File1.C
1
#include "Header"
2
3
...

File2.c
1
#define EXTERN
2
#include "Header"
3
4
int main()
5
{
6
  ...
7
}
ins Wasser fällt. Klar kann man mit zusätzlicher Makro-Magie die 
Initialisierung wieder soweit verpacken, dass man sie gekoppelt an das 
EXTERN Makro erst per Bedarf nur an einer einzigen Stelle bei der 
Definition 'aktiviert', aber der Aufwand wird dann schon so hoch und das 
ganze wird so unübersichtlich, dass es sich nicht mehr lohnt.

von Karl H. (kbuchegg)


Lesenswert?

P. K. schrieb:
> Auf meine Konstante wird nur in einem .c-File zugegriffen.

Dann erhebt sich allerdings die Frage, warum diese Konstanten nicht 
überhaupt in genau diesem .c-File sind und nirgendwo anders.

Header Files haben den Sinn, die Dinge zu kapseln, die ein 
Aussenstehender von einem 'Modul' (= dieses C-File) wissen muss. Wenn 
ein Aussenstehender von diesen Konstanten nichts wissen muss oder wissen 
soll, dann haben die auch nichts im Header File verloren.

> Falls nicht: die 512 Bytes und 512 Bools sind nun ja nicht so ein
> Riesenposten, die Bequemlichkeit, alle Config-Daten
> (Konstanten & Defines) in einem File zu haben, würde den Overhead
> mehr als rechtfertigen.

Aber nicht um jeden Preis.

Leg um die Struktur bzw. die Initialisierung wenigstens noch ein #ifdef 
drumm herum, so dass sich dieses eine C-File diese Definitionen 
'freischalten' kann, und in allen anderen C-Files diese Dinge vom 
Präprozessor gleich mal rausgeworfen werden.


Config.h
1
....
2
3
#ifdef NEED_COMMANDS     // wird nur von File Commands.c benoetigt
4
5
typedef struct Stm32Cmd_t
6
{
7
  CyBool_t CmdVald;
8
  CyBool_t LongRun;
9
  uint8_t  ReqDCnt;
10
  uint8_t  RspDCnt;
11
} Stm32Cmd_t;
12
13
const Stm32Cmd_t Stm32Cmd[256] = {{.CmdVald = CyFalse, 
14
                                   .LongRun = CyFalse, 
15
                                   .ReqDCnt = 0x00, 
16
                                   .RspDCnt = 0x00},
17
                         [0x80] = {.CmdVald = CyFalse, 
18
                                   .LongRun = CyFalse, 
19
                                   .ReqDCnt = 0x00, 
20
                                   .RspDCnt = 0x00}};
21
22
#endif
23
...

Commands.c
1
#define NEED_COMMANDS
2
#include "Config.h"
3
...

alle anderen C-Files
1
#include "Config.h"
2
...

von Bitflüsterer (Gast)


Lesenswert?

@ Karl Heinz

Ich stimme Dir im Grundsatz zu, wobei allerdings auch das mit einfachen 
Mitteln zu ändern ist, die ich, im Gegensatz zu Dir, nicht als "Magie" 
bezeichnen würde.

Wie schon geschrieben: Verschiedene Lösungen haben verschiedene Vor- und 
Nachteile.

Bei der anderen mit einer C-Datei und einer H-Datei kommt man aber man 
ohne "Magie" :-) auch nicht aus, denn man müsste die enthaltenen EXTERNs 
irgendwie deaktivieren, wenn das H-File in der C-Datei mit den 
Definitionen verwendet wird. Die andere Lösung würde voraussetzen das 
man an zwei Stellen Typen nennt, die, falls sie geändert werden sollen 
eben auch an zwei Stellen geändert werden müssen.

Wie gesagt: Eine Kröte muss man schlucken.

von Bitflüsterer (Gast)


Lesenswert?

@ Karl Heinz
>#define NEED_COMMANDS

Genau. Aber das ist dann der nächste gedankliche Schritt.

Für mich war das primäre Problem, erstmal eine Lösung zu beschreiben. 
Das die weitere Umstände erfordert war für mich eine weiteres Thema. Ich 
habe bei solchen Threads das Problem, das ich nicht weiss wie 
fortgeschritten der TO ist. Wenn einer solch eine Frage stellt, dann 
schreibe ich erstmal die einfache Lösung, die weiterentwickelt wird.

Ich sagte ja: Es gibt andere Lösungen. Aber die sind gedanklich etwas 
komplizierter. Bei der Frage ob ich die hier beschreibe, habe ich mich 
eben so entschieden.

von Karl H. (kbuchegg)


Lesenswert?

Bitflüsterer schrieb:

> Bei der anderen mit einer C-Datei und einer H-Datei kommt man aber man
> ohne "Magie" :-) auch nicht aus, denn man müsste die enthaltenen EXTERNs
> irgendwie deaktivieren, wenn das H-File in der C-Datei mit den
> Definitionen verwendet wird.

Da dürfte jetzt ein Missverständnis vorliegen

globals.h
1
extern int a;

globals.c
1
#include "globals.h"
2
int a = 5;

main.c
1
#include "globals.h"
2
3
int main()
4
{
5
  printf( "%d", a );
6
}

keinerlei Makro-Magie notwendig. In globals.c stehen die Definitionen 
der globalen Variablen, die in globals.h deklariert wurden.

(bzw. dann sinngemäss eben immer adaptiert auf C-File + zugehöriges 
Header File)

Zu Fehlern kann es nicht kommen, denn das globals.c inkludiert das 
Header-File ebenfalls, so dass der Compiler die Übereinstimmung in den 
Datentypen kontrollieren kann.

Du kannst jetzt natürlich anführen, dass man dann ja wohl bei der 
Einführung von zusätzlichen globalen Variablen an jeweils 2 Stellen 
editieren muss und du hast recht damit. Allerdings ist das aber auch 
keine Operation, die man jeden Tag 300 mal macht. Von daher sind die 30 
Sekunden zusätzliche Editieroperation alle 3 Wochen verschmerzbar und 
kein echtes Problem.

von Bitflüsterer (Gast)


Lesenswert?

@ Karl Heinz

Da gebe ich Dir recht. K&R 2. Aufl. A.10.2., S. 224

Aber:
>Du kannst jetzt natürlich anführen, dass man ...

was ich dann jetzt hier mal anführe. :-)


Unschön ist das aber auch, wenn ich die Initialisierung so ändere das 
ein anderer Typ nötig ist. Dann muss ich auch an zwei Stellen ändern.
Es gab da vor einigen zig Jahren bei mir mal so einen Fall, in dem ich 
einige Dutzend Initialisierungen und Typen ändern musste. Irgendwie ist 
das hängengeblieben. Aber gut, das ist ein Sonderfall.

von Karl H. (kbuchegg)


Lesenswert?

Bitflüsterer schrieb:

> Unschön ist das aber auch, wenn ich die Initialisierung so ändere das
> ein anderer Typ nötig ist. Dann muss ich auch an zwei Stellen ändern.

Nicht ganz elegant, gebe ich zu.
Aber solange es nicht passieren kann, dass etwaige Fehler an dieser 
Stelle unentdeckt bleiben, ist es ein Schönheitsproblem.
Wichtig ist immer, das der Compiler die Dinge kontrollieren kann. Also 
... für mich ist das immer das wichtigste und hat oberste Priorität. 
Denn ich mache Fehler und da bin ich schon ganz froh drüber, wenn mir 
der Compiler im Falle des Falles auf die Finger klopft. Ein bischen 
Tippaufwand hingegen stört mich weniger.

von Bitflüsterer (Gast)


Lesenswert?

@ Karl Heinz
> ... dass etwaige Fehler an dieser Stelle unentdeckt bleiben, ...

Das ist nun wieder ein Argument, dass auf seinen drei Beinen auch recht 
sicher steht. :-)


Als gut. Ich hoffe der TO hat was von dieser Diskussion gehabt.

von P. K. (pek)


Lesenswert?

Bitflüsterer schrieb:
> Als gut. Ich hoffe der TO hat was von dieser Diskussion gehabt.

Ja, merci. Gibt einen schönen Überblick. Werde mir noch ein paar 
Gedanken machen, wie ichs dann wirklich implementiere (momentan 
quick&dirty mit dem Speicherverbraucher "static"...).

Wie gesagt: Ursprüngliches Ziel war, alles was vom System gegeben ist 
(e.g. STM32 command Struktur, PC command Struktur, etc.) für den FX3 in 
einem_ _einzigen File zu definieren, um später, wenn aussen was 
ändert, nicht suchen zu müssen.
Noch eleganter (um alles an einem Ort und nichts doppelt zu haben) wären 
natürlich gemeinsame zentrale Definitionsstrukturen für alle Beteiligten 
(PC, FX3, FPGA, eMMC und STM32) aber das ist mir im Moment doch zu etwas 
zu haarig...

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.