Forum: Compiler & IDEs GCC: Welches (globale) Symbol nimmt der Compiler bei gleichem Namen und Typ?


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


Lesenswert?

Hallo Leute.

Da wir in der Firma mit dem ASF (Atmel Software Framework) auf dieses 
Problem gestoßen sind, wollte ich hier nachfragen, ob Ihr das hier mal 
näher beleuchten könnte, warum dass so ist.

Konkretes Beispiel:
1
// include datei: test.h
2
typedef struct
3
{
4
    int x;
5
}FOO_T;
6
7
void bar(void);
8
--------------------------------
9
10
// test.c
11
#include "test.h"
12
13
FOO_T var;
14
15
void bar(void)
16
{
17
    var.x = 5;
18
}
19
--------------------------------
20
21
// main.c
22
#include <stdio.h>
23
#include "test.h"
24
25
FOO_T var;
26
27
int main(void)
28
{
29
    bar();
30
    printf("%i\n", var.x);
31
}
Bei dem printf() in main wird nun 5 ausgegeben, da das var aus test.c 
genommen wird und nicht var aus main.

Frage 1:
Warum gibt es hier keine warnung, nach dem Motto: Hey, du hast da zwei 
variablen, vom selben typ, mit dem selben namen, die global sind und 
nicht static und auch nicht als extern deklariert, ist das wirklich das 
was du willst?

Denn genau das hatten wir nun:
Code aus einem Atmel Quick-Start-Guide kopiert und gewundert das nichts 
geht... bis wir dann mal raus gefunden haben, das es in den tiefsten 
tiefen des ASF nunmal globale variablen vom selben Typ mit dem selben 
Namen gab.
Wäre z.B. FOO_T var in main als extern deklariert, könnte ich das 
verhalten ja voll und ganz nachvollziehen. Wenn aber der compiler das eh 
so macht, erschließt sich mir die Sinnhaftigkeit von extern nicht so 
ganz :-/

Frage 2:
Gibt es eine festgelegte reihenfolge in der die symbole genommen werden, 
oder wird aus den beiden symbolen einfach ein symbol gemacht?

Frage 3:
Ist das Verhalten gewollt? Ist es "undefined behavior"? Ist es 
"Implementation-defined behavior"?

Das verhalten lässt sich mit dem ARM-GCC aus dem Atmel Studio 7 (müsste 
der 4.9.3 sein) als auch mit einem aktuellen GCC 6.1.1 20160501 unter 
Linux reproduzieren.

Um das noch kurz klar zu stellen:
Ich möchte nur verstehen warum das so ist. Dass es kein Compiler-Bug 
ist, da bin ich mir sicher. Es ist wie immer ein Layer 8 Problem, das 
ist klar. :o)

Grüße

von Max (Gast)


Lesenswert?

Dennoch wäre eine Warnung schön, da liegst du richtig.

von (prx) A. K. (prx)


Lesenswert?

Probier mal -fno-common

Nicht initialisierte Variablen werden in manchen Compilern als 
sogenannte "commons" ausgeworfen. Eine gleichlautende initialisierte 
Definition hat gerät dann nicht in Konflikt mit nicht initialisierten 
Definitionen, sondern ersetzt sie kommentarlos.

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

Stichwort: Tentative Definition

Einige interessante Ergebnisse:
http://stackoverflow.com/questions/1490693/tentative-definitions-in-c99-and-linking
http://stackoverflow.com/questions/3095861/about-tentative-definition

Bis gerade war mir das auch völlig unbekannt. Mit einem C++ Compiler 
gibts auf jeden Fall einen Fehler, wie es meiner Meinung nach sein 
sollte.

von lalala (Gast)


Lesenswert?

Um das zu vermeiden gibt es bei globalen Variablen das Schlüsselwort 
'static'. Damit wird ein exportieren der Variable verhindert.

von Sultan (Gast)


Lesenswert?

lalala schrieb:
> Um das zu vermeiden gibt es bei globalen Variablen das Schlüsselwort
> 'static'. Damit wird ein exportieren der Variable verhindert.

Mit 'static' klappt es. Nur das Verhalten ist nicht korrekt. Denn die 
beiden Variablen sollten auch ohne 'static' nur im jeweiligen Modul 
gelten.

von (prx) A. K. (prx)


Lesenswert?

Sultan schrieb:
> Nur das Verhalten ist nicht korrekt.

Nein. Das Verhalten ist verbreitet.

> Denn die
> beiden Variablen sollten auch ohne 'static' nur im jeweiligen Modul
> gelten.

Das wäre definitiv nicht korrekt.

Etwas Hintergrund zum Thema gibts unter 6.2.2 in
www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf

: Bearbeitet durch User
von mar IO (Gast)


Lesenswert?

1
Ld DerivedData/test_extern/Build/Products/Debug/test_extern normal x86_64
2
    cd /Users/myName/Projects/Temp/test_extern
3
    export MACOSX_DEPLOYMENT_TARGET=10.11
4
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch x86_64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -L/Users/myName/Projects/Temp/test_extern/DerivedData/test_extern/Build/Products/Debug -F/Users/myName/Projects/Temp/test_extern/DerivedData/test_extern/Build/Products/Debug -filelist /Users/myName/Projects/Temp/test_extern/DerivedData/test_extern/Build/Intermediates/test_extern.build/Debug/test_extern.build/Objects-normal/x86_64/test_extern.LinkFileList -mmacosx-version-min=10.11 -Xlinker -no_deduplicate -Xlinker -dependency_info -Xlinker /Users/myName/Projects/Temp/test_extern/DerivedData/test_extern/Build/Intermediates/test_extern.build/Debug/test_extern.build/Objects-normal/x86_64/test_extern_dependency_info.dat -o /Users/myName/Projects/Temp/test_extern/DerivedData/test_extern/Build/Products/Debug/test_extern
5
6
duplicate symbol _var in:
7
    /Users/myName/Projects/Temp/test_extern/DerivedData/test_extern/Build/Intermediates/test_extern.build/Debug/test_extern.build/Objects-normal/x86_64/main.o
8
    /Users/myName/Projects/Temp/test_extern/DerivedData/test_extern/Build/Intermediates/test_extern.build/Debug/test_extern.build/Objects-normal/x86_64/test.o
9
ld: 1 duplicate symbol for architecture x86_64
10
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Wenn ich (genau) das Beispiel in Xcode zu übersetzen versuche, dann 
meckert der Linker. Wenn ich das einfach im Terminal übersetze - ohne 
extra Flags, dann funkt. es tadellos. Da wird wohl noch ein Linker-Flag 
gesetzt.

von (prx) A. K. (prx)


Lesenswert?

Es gibt schlicht beides. Das strikte Modell, wie es hier clang zeigt, 
aber auch gcc mit -fno-common, und die verbreitete Erweiterung aus 
J.5.11 (C99).

Da ziemlich viel Code die Erweiterung verwendet, hat der Standard darauf 
verzichtet, das pauschal zu verdammen.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Kaj G. schrieb:
> Ist das Verhalten gewollt?
Ja.

> Ich möchte nur verstehen warum das so ist.

Das Zauberwort heißt "Linkage" (C99, §6.2.2).

Zunächst ein paar andere Begriffe, die damit zusammenhängen

Scope of Identifiers (C99, §6.2.1)
1
1 An identifier can denote an object; a function; a tag or a member
2
  of a structure, union, or enumeration; a typedef name; a label name; a
3
  macro name; or a macro parameter. The same identifier can denote
4
  different entities at different points in the program. [...]
5
6
2 For each different entity that an identifier designates, the identifier
7
  is visible [...] only within a region of program text called its scope.
8
  [...]  There are four kinds of scopes: function, file, block, and
9
  function prototype.

Aus .2 lernen wir: Der größte Scope in C ist "file scope"! Es gibt 
keinen Scope in C, der größer ist als der einer "translation unit"!

Die Verbindung zwischen gleichnamigen Objekten (entities) in 
unterschiedlichen Translation Units wird in C (und C++) durch deren 
Linkage hergestellt (C99, §6.2.2):
1
1 An identifier declared in different scopes or in the same scope more
2
  than once can be made to refer to the same object or function by a
3
  process called linkage.  There are three kinds of linkage: external,
4
  internal, and none.
5
6
2 In the set of translation units and libraries that constitutes an
7
  entire program, each declaration of a particular identifier with
8
  external linkage denotes the same object or function.  [...]
9
10
4 For an identifier declared with the storage-class specifier extern in
11
  a scope in which a prior declaration of that identifier is visible, if
12
  the prior declaration specifies internal or external linkage, the
13
  linkage of the identifier at the later declaration is the same as the
14
  linkage specified at the prior declaration. If no prior declaration is
15
  visible, or if the prior declaration specifies no linkage, then the
16
  identifier has external linkage.
17
18
5 [...] If the declaration of an identifier for an object has file scope
19
  and no storage-class specifier, its linkage is external.

In deinem Beispiel sind var (test.c) und var (main.c) also file-scope 
Identifier, die qua "external Linkage" ein und dasselbe Objekt 
bezeichnen.

Innerhalb der GNU Compiler Collection (GCC) u.a.m. ist der Linker für 
die Linkage verantwortlich (formal kein Teil von GCC sondern der GNU 
Binutils), und hier ist auch das Object-Format von Bedeutung, denn nicht 
jedes Object-Format kann gleich viel, insbesondere was Sections angeht.

Zu Declarations (C99 §6.7):
1
5 A declaration specifies the interpretation and attributes of a set
2
  of identifiers.  A definition of an identifier is a declaration for that
3
  identifier that:
4
  — for an object, causes storage to be reserved for that object;
5
  — [...]

Für var wird also Speicher allokiert (qua "FOO_T var"), für dessen 
Storage Duration (C99 $6.2.4) gilt:
1
3 An object whose identifier is declared with external or internal
2
  linkage, or with the storage-class specifier static has static storage
3
  duration.  Its lifetime is the entire execution of the program and its
4
  stored value is initialized only once, prior to program startup.

Das "initialized only once" bezieht sich auf das Objekt, d.h. die 
Toolchain muss sicherstellen, dass var nur 1x initialisiert wird — auch 
dann, wenn mehrere (tentative) Definitions vorhanden sind.  Für 
"tentative Definitions" siehe C99 §6.9.2 External Object Definitions.

Anstatt einer tentative Definition verwendet man oft besser eine 
explizit extern Deklaration, d.h. mit Schlüsselwort "extern".  Falls es 
keine Definition des Objekts gibt, beschwert sich der Linker.  Falls es 
hingegen mehrer Definitionen gibt, wird ein Standard foo-gcc das Objekt 
nach COMMON legen, d.h. es ist keiner Input-Section zugeordnet 
("Section" ist ein Konzept, das nicht Gegenstand des C-Standard ist, 
dito für "Symbol").

Erst wenn dem Objekt eine Input-Section zugeordnet ist (z.B. per 
-fno-common), kann sich der Linker über Mehrfachdefinitionen beschweren.

Allerdings ist anzumerken, dass mehrere tentative Definitionen keine 
Mehrfachdefinition in diesem Sinne sind, siehe obigen Auszug aus dem 
Standard.  Die "Mehrfachdefinition" entsteht da erst im Kopf des 
Anwenders, wenn dieser mehrfach vorhandene tentative Definitions 
unterschiedlichen Objekten zuordnet...

Selbst wenn in einem Modul "int x" und in einem anderen "float x" steht, 
beschwert sich der Linker (mit -fcommon) nicht, selbst wenn wie bei 
avr-gcc sizeof(int) != sizeof(float) ist:  Der Linker reserviert einfach 
das Maximum an benötigtem Platz.  Erst Linken mit -flto liefert eine 
Diagnostic, welche dann allerdings vom Compiler kommt, der die globale 
Sicht oder zumindest Modulgrenzen überschreitende Sicht hat.

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


Lesenswert?

Vielen Dank fuer alle Antworten, ganz besonders an Johann. :)

Gruesse

von Sultan (Gast)


Lesenswert?

Kaj G. schrieb:

> Vielen Dank fuer alle Antworten, ganz besonders an Johann. :)

Schließe mich an :)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Da ziemlich viel Code die Erweiterung verwendet, hat der Standard darauf
> verzichtet, das pauschal zu verdammen.

Die common-Überlagerung ist einfach das historisch ältere Konzept
gewesen, daher ist es als implementierungsabhängige Option nach wie
vor vorhanden.  Wenn man die älteren C-Handbücher von Dennis Ritchie
liest, war es wohl ursprünglich die Annahme, dass man nur in der
C-Implementierung auf der PDP-11 das "extern" weglassen könne, offenbar
ein Tribut an den damaligen Linker.

von (prx) A. K. (prx)


Lesenswert?

Jörg W. schrieb:
> Die common-Überlagerung ist einfach das historisch ältere Konzept
> gewesen, daher ist es als implementierungsabhängige Option nach wie
> vor vorhanden.

Dem C99 Rationale kann man entnehmen, dass Ritchie das nicht einfach 
bloss übernahm weil eh schon vorhanden, sondern direkt beabsichtigte. 
Und zwar in noch krasserer Form als heute üblich, mit einem common 
record unabhängig davon ob mit "extern" oder ohne:

6.2.2 zum Common Modell: "Every object declaration with external 
linkage, regardless of whether the keyword extern appears in the 
declaration, creates a definition of storage. When all of the modules 
are combined together, each definition with the same name is located at 
the same address in memory. (The name is derived from common storage in 
Fortran.) This model was the intent of the original designer of C, 
Dennis Ritchie."

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Dem C99 Rationale kann man entnehmen, dass Ritchie das nicht einfach
> bloss übernahm weil eh schon vorhanden, sondern direkt beabsichtigte.

Wirkliche Aussagen von ihm selbst finde ich nicht dazu.  Allerdings
fällt auf, wenn man sich die Beschreibung von BCPL durchliest, dass
dort "extrn" noch verpflichtend war.  Das lässt darauf schließen, dass
es Absicht war, es in C nicht mehr verpflichtend zu benötigen.
Andererseits sagt das alte C-Manual ausdrücklich, dass das nur bei
"PDP-11 C" so sei:
1
11.2 Scope of externals
2
[…]
3
   In PDP-11 C, it is explicitly permitted for (compatible) external
4
definitions of the same identifier to be present in several of the
5
separately-compiled pieces of a complete program, or even twice within
6
the same program file, with the important limitation that the
7
identifier may be initialized in at most one of the definitions. In
8
other operating sys- tems, however, the compiler must know in just
9
which file the storage for the identifier is allocated, and in which
10
file the identifier is merely being referred to. In the
11
implementations of C for such systems, the appearance of the ex- tern
12
keyword before an external definition indicates that storage for the
13
identifiers being declared will be allocated in another file. Thus in
14
a multi-file program, an external data definition without the extern
15
specifier must appear in exactly one of the files. Any other files
16
which wish to give an external definition for the identifier must
17
include the extern in the definition. The identifier can be
18
initialized only in the file where storage is allocated.

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.