Hallo,
ich habe grad mit Dev-C++ ein seltsames Verhalten erzeugt, dass ich mir
nicht erklären kann.
Hier das Programm:
______ foo.h ______
1
#ifndef FOO_H
2
#define FOO_H
3
4
intfirst_func();
5
6
#endif
___________________________ foo.c ______
1
intfirst_func()
2
{
3
return111;
4
}
___________________________ main.c ______
1
#include<stdio.h>
2
#include<stdlib.h>
3
4
intmain(intargc,char*argv[]){
5
6
inta=first_func();
7
8
printf("Das Ergebnis aus 'first_func' ist: %d\n",a);
9
return0;
10
}
_____________________
Die Ausgabe liefert nach erfolgreicher Kompilation:
Das Ergebnis aus 'first_func' ist: 111
Das verwundert mich, denn ich dachte, dass foo.h und foo.c ein Modul
seien. In foo.h werden alle Funktionen definiert, die das Modul
bereitstellt, in foo.c werden sie deklariert.
Wenn nun in main.c das Modul genutzt werden soll, so muss foo.h
inkludiert werden.
Aber in dem Programm oben ist dies nicht der Fall, trotzdem kann auf die
Funktion zugegriffen werden.
Warum?
Und noch schlimmer: In kann die Definition in foo.h streichen, selbst
dann kompiliert und läuft das Programm mit korrektem Ergebnis. D.h., in
main.c kann im Prinzip ohne foo.h auf alles in foo.c zugegriffen werden.
Was ist dann der Nutzen des Headers?
Vielen Dank,
Axel
Noch ein Gast schrieb:> Du verwechselst Deklaration und Definition>> Die Deklarationen im Headerfile machen deine Funktionen anderen> Compile-Units bekannt.
So sollte es sein. Aber warum kompiliert das Programm von oben?
Ich mache die Funktion nicht bekannt in main.c, der Linker findet sie
aber trotzdem.
Ist das wohldefiniertes Verhalten?
Addendum:
Indem du Funktionen deklariert, stellst Du sicher, dass der Compiler die
Parametrisierung checkt.
Du kommst auch ohne Deklaration klar, dein Programm wird
-->vielleicht<-- funktionieren. Oder auch nicht.
Ich will meine Frage nicht etwas umformulieren:
In C sind alle Funktionen per default 'extern' (sofern nicht explizit
'static'), also haben sie alle 'external linkage'.
Sind dann nicht alle Funktionen völlig global, also überall im Programm
von jedem Modul aufrufbar, völlig unabhängig, ob der header eingebunden
wird oder nicht?
Noch ein Gast schrieb:> So ist es.>> Die Deklaration in Headerfiles bringt dir aber den added-value, dass Die> Funktionsaufrufe einem type-checking unterliegen.
Wenn also zwei Programmierer an einem großen Projekt arbeiten, dürfen
sie niemals auf den gleichen Namen für zwei unterschiedliche 'extern'
Funktionen kommen, selbst wenn sie in völlig unterschiedlichen Modulen
deklariert und definiert werden?
Das erscheint mit nicht klug.
Nun.
Deine Beobachtung ist absolut korrekt.
Du musst bedenken, dass C schon sehr alt ist. Es hat einen Grund, dass
jede library ihre innewohnenden Funktionen mit einem einheitlichen
prefix...nun.... prefixen.
Heute löst man das z.B. über Namespaces. usw.
Eine letzte Frage:
Folgendes Programm (ist auf die schnelle nicht richtig, am besten
Kommentare lesen und Erklärung unten):
____ foo.h ____
#ifndef FOO_H
#define FOO_H
// mit dieser Funktion kann mit **funktion auf eine beliebige andere
Funktion
// gezeigt werden
void funktion1(int (**funktion)());
#endif
_______________________ foo.c ____
#include <stdio.h>
#include <stdlib.h>
// eine static funktion in foo.c
static voic funktion2()
{
printf("test\n");
};
// nun zeige ich mit **funktion auf die 'static' Funktion 'funktion2'
void funktion1(int (**funktion)(void))
{
*funktion = &funktion2;
}
_______________________ main.c ____
#include <stdio.h>
#include <stdlib.h>
#include "foo.h"
// prototype pointer auf eine Funktion
int (**funktion3)();
int main(int argc, char *argv[]) {
// obwohl funktion2 'static' ist, zeigt nun 'funktion3' auf
'funktion2'
funktion1(funktion3);
// hier kann 'function2' ausgeführt werden, obwohl sie 'static' in foo
ist
(*funktion3)();
return 0;
}
___________________
Die Ausgabe ist: test
Also. Das Modul foo hat eine 'static'-Funktion, sie ist also nur in dem
Modul zugänglich. Wenn ich aber nun über eine zweite 'extern' Funktion
des Modul die Adresse der 'static'-Funktion nach außen weiterreiche,
dann ist die Funktion auch in anderen Modulen anwendbar, obwohl sie
'static' ist.
Kann man damit static nicht gewissermaßen aushebeln?
Ja kannst Du.
Du musst sehen:
C wurde erfunden als eine Art Hochsprachenassembler. Ergo kannst Du
ALLES
machen.
Das ist einerseits gut, wenn du in der Systemprogrammierung zu hause
bist.
Andererseits schlecht in der Anwendungsentwicklung. Wenn ich ehrlich
bin...
man sollte auch in der Systemprogrammierung drauf verzichten.
Man muss vor allem nicht jedes Sprachmittel verwenden nur weil es geht.
Ein spannendes Thema. Was aber mit der verwendeten Programmiersprache
nur marginal zu tun hat. Dieses Prinzip gilt m.E. immer...
Axel221 schrieb:> Wenn also zwei Programmierer an einem großen Projekt arbeiten, dürfen> sie niemals auf den gleichen Namen für zwei unterschiedliche 'extern'> Funktionen kommen, selbst wenn sie in völlig unterschiedlichen Modulen> deklariert und definiert werden?
Nein, wie kommst du darauf? Der Linker sollte eine Fehlermeldung
liefern, daß die Funktion mehrfach definiert ist.
a.c:
c.c:3:5: warning: implicit declaration of function ‘foo’ [-Wimplicit-function-declaration]
4
foo();
5
^
6
/tmp/ccUmCqp2.o: In Funktion `foo':
7
b.c:2: Mehrfachdefinition von `foo'
8
/tmp/ccYWMSXp.o:a.c:2: first defined here
9
collect2: error: ld returned 1 exit status
Axel221 schrieb:> Also. Das Modul foo hat eine 'static'-Funktion, sie ist also nur in dem> Modul zugänglich. Wenn ich aber nun über eine zweite 'extern' Funktion> des Modul die Adresse der 'static'-Funktion nach außen weiterreiche,> dann ist die Funktion auch in anderen Modulen anwendbar, obwohl sie> 'static' ist.>> Kann man damit static nicht gewissermaßen aushebeln?
static bezieht sich nicht auf den Inhalt der Funktion, sondern auf ihren
Namen. Dieser ist von außen nicht zugänglich. Wenn du einen Zeiger auf
die Funktion definierst, kannst du diesen aber natürlich nutzen. Du hast
damit also "static" nicht ausgehebelt. Es ist explizit vorgesehen, daß
das so geht.
[EDIT]:
Das wird z.B. bei Callback-Funktionen gerne genutzt. Nimm z.B. qsort.
Das erwartet einen Zeiger auf eine Vergleichsfunktion, die man selbst
schreiben muß. Du willst aber nicht, daß deren Name den Namensraum
unnötig füllt, und außer über diesen Zeiger soll sie eh nicht aufgerufen
werden. Also machst du die Funktion static und überigbst den Zeiger auf
diese an qsort, so daß sie von dort aufgerufen werden kann.
Axel221 schrieb:> Was ist dann der Nutzen des Headers?
Ist Dir schon aufgefallen, daß der Compiler beim Weglassen des Headers
(bzw. des Funktionsprototypen) Warnungen ausgibt? Hast Du die
abgeschaltet, oder ignorierst Du die?
Axel221 schrieb:> Aber in dem Programm oben ist dies nicht der Fall, trotzdem kann auf die> Funktion zugegriffen werden.>> Warum?
Wie du schon raus gefunden hast, sind alle Funktion (die du nicht static
definiert hast) von allen anderen Programmteilen sichtbar.
Gibt es keinen expliziten Prototypen, dann bastelt sich der Compiler
selbst einen, indem er ihn aus der Verwendung an der Aufrufstelle
ableitet. Dabei gelten Default-Regeln für die Argumente. Wenn etwas ein
Pointer sein kann, dann ist er es auch. Wenn etwas ein double sein kann,
dann ist er es auch. Wenn etwas für einen int zu gross ist, dann ist es
ein long. Alles andere ist ein int.
Aus deiner Verwendung
1
...
2
inta=first_func();
3
...
leitet der Compiler also her, dass die Deklaration der Funktion so
aussehen muss
1
intfirst_func(void);
und zufällig ist das sogar richtig.
Würdest du die Funktion so benutzen
1
inta=first_func(5);
dann würde der Compiler daraus schliessen, dass die Funktion diese
Signatur haben müsste
1
intfirst_func(intarg);
Tja. das wäre falsch, denn deine Funktion sieht ja nicht so aus.
Genauso würde ein C Compiler aus
1
intmain()
2
{
3
first_func(5.0);
4
}
die implizite Funktionsdeklaration
1
intfirst_func(doublearg);
ableiten, was genauso falsch wäre.
Du siehst also: Funktionsdeklarationen haben schon ihren Sinn. Denn
findet sich keine, dann trifft der Compiler eben Standardannahmen,
basierend darauf wie du die Funktion verwendest und welche Argumente du
der Funktion mitgibst (nur der Returntyp der Funktion wird immer als int
angenommen).
Das kann, wenn du sorgfältig bist, sogar zum korrekten Ergebnis führen.
Muss es aber nicht.
Mit einer selbst geschriebenen Funktionsdeklaration bist du hingegen auf
der sicheren Seite. Dann muss niemand was annehmen. Und wenn du in foo.c
dann auch noch den eigenen Header includierst
____ foo.c ____
1
#include"foo.h"
2
3
intfirst_func()
4
{
5
return111;
6
}
dann prüft der Compiler auch gleich noch, ob der Funktionsprototyp mit
der tatsächlichen Funktion übereinstimmt. Und wenn du dann noch die
Funktionsdeklaration im Header als
1
#ifndef FOO_H
2
#define FOO_H
3
4
intfirst_func(void);
5
6
#endif
schreibst, dann wäre auch die noch vollständig korrekt. Denn so wie du
die geschrieben hast, mit lediglich einer leeren Klammerung
1
intfirst_func();
bedeutet das, das über die Argumente der Funktion nichts näheres bekannt
ist.
Funktionsdeklarationen, aka Protoypen, sind also das, was im Zirkus das
Netz unter den Trapezartisten ist. Klar, wenn man es kann und die Regeln
genau kennt, dann kann man darauf verzichten. Aber dann darf auch nichts
schief gehen.
Und die Regeln wiederrum sind so umfangreich und zum Teil nicht intuitiv
bzw. historisch gewachsen, dass man ein C Buch benötigt, das einen da
durchführt um sie zu lernen.
Der Compiler sollte eine Warnung der Art "Implicit declaration of
function foo..." bringen.
Das heißt, die Funktion wurde vorher nicht explizit bekannt gemacht.
Durch den Aufruf geht er aber implizit davon aus, dass es foo() irgendwo
gibt.
Der Linker findet sie auch, deswegen kommt es nicht zu einem
Linkerfehler.
Rufus Τ. Firefly schrieb:> Axel221 schrieb:>> Was ist dann der Nutzen des Headers?>> Ist Dir schon aufgefallen, daß der Compiler beim Weglassen des Headers> (bzw. des Funktionsprototypen) Warnungen ausgibt? Hast Du die> abgeschaltet, oder ignorierst Du die?
Also Dev-C++ gibt keine Warnungen aus, "Error: 0" und "Warnings: 0". Ich
kenne mich mit DEV-C++ nicht aus.
Wenn ich bei GCC eine Warnung sehe, behebe ich sie sofort. Aber wenn man
mal kurz drüber nachdenkt, was sie eigentlich bedeutet, dann stößt man
auf Verständnisprobleme wie das Jetzige.
Rolf Magnus schrieb:>> static bezieht sich nicht auf den Inhalt der Funktion, sondern auf ihren> Namen. Dieser ist von außen nicht zugänglich. Wenn du einen Zeiger auf> die Funktion definierst, kannst du diesen aber natürlich nutzen. Du hast> damit also "static" nicht ausgehebelt. Es ist explizit vorgesehen, daß> das so geht.
Ein Programmbeispiel:
Angenommen ich habe zwei Module, foo.h + foo.c und bar.h + bar.c.
Beide Module liegen in jeweils einem eigenen Ordner auf GLEICHER Ebene.
Zusätzlich liegt main.c ebenfalls auf dieser Ebene.
+-+-main.c
|
|
+-+-FOO
| |
| |
| +-+-foo.h
| |
| +-foo.c
|
+-+-BAR
| |
| |
| +-+-bar.h
| |
| +-bar.c
|
FOO kann bar.h nicht inkludieren und umgekehrt, da ich sonst beim
inkludieren eine Ordnerebene "höher" gehen müsste, was - soweit ich weis
- in C eine schlechte Praxis ist.
Wenn nun aber in bar.c eine Funktion aus foo.c aufrufen werden soll, die
dann auch noch "static" ist, gibt es also folgende Möglichkeit:
Ich übergebe beim Programmstart einfach den Pointer der entsprechenden
"static" Funktion aus foo.c nach bar.c über init()-Funktionen. Dabei
hilft mir main.c mit der init()-Funktion.
In bar.c wird der Pointer gespeichert und so kann die Funktion von dort
aufgerufen werden, wenn beide Module gleiche Funktionsprototypen haben.
Warum liegen beide auf gleicher Ebene?
Weil die Abstraktion des Programms das als beste Lösung ergeben hat.
Angenommen FOO sammelt Daten über reiche Kunden und BAR über arme Kunden
einer Bank. Beide sind gleichwertig, aber ab und an wechseln sie auch
die Seite.
Ist das eine legale und gute Praxis?
Wenn nein, was wäre eine bessere Lösung?
Axel221 schrieb:> FOO kann bar.h nicht inkludieren und umgekehrt, da ich sonst beim> inkludieren eine Ordnerebene "höher" gehen müsste, was - soweit ich weis> - in C eine schlechte Praxis ist.
C hat dazu überhaupt nichts zu sagen.
Ob das sinnvoll ist oder nicht, oder ob eine bessere Lösung darin
bestehen würde, die Ordnerhierarchie zu ändern, muss man am konkreten
Fall festmachen.
Aber C an sich ist das völig egal.
> Wenn nun aber in bar.c eine Funktion aus foo.c aufrufen werden soll, die> dann auch noch "static" ist, gibt es also folgende Möglichkeit:
Wenn in bar.c eine Funktion aus foo.c aufgerufen werden soll, dann ist
die entsprechende Funktion nicht static zu machen, ein entsprechender
Protoyp kommt ins Header File, bar.c inkludiert diesen Header File und
ruft die Funktion auf.
Alles andere sind Sonderfälle, die in vielleicht 1 von 100 Fällen mal
vorkommen.
Weniger künsteln und mehr straight forward arbeiten. Dann programmierst
du automatisch besser.
Gewöhn dir die Sache mit dem Funktionspointer auf static gleich wieder
ab. Du liest da zu viel hinein, nur weil man das machen könnte. Nicht
alles was man machen könnte ist auch sinnvoll.
Wenn ein Modul eine Funktion exportiert, dann ist die entsprechende
Funktion nicht static. Es mag vereinzelt Ausnahmen geben, aber es ist
nicht der Regelfall.
main.c:6:3: warning: implicit declaration of function ‘first_func’ [-Wimplicit-function-declaration]
3
int a = first_func();
4
^
Mit einem
1
#include"foo.h"
in main.c verschwindet die Warnung, wieder, weil der Compiler jetzt eine
explizite Deklaration von first_func sieht.
Die gleiche Include-Zeile solltest du auch am Anfang von foo.c einfügen.
Das gibt dem Compiler die Möglichkeit zu überprüfen, ob die Deklaration
in foo.h mit der Definition in foo.c übereinstimmt. Wenn nicht (weil du
bspw. der Funktion first_func nachträglich noch ein Funktionsargument
verpasst), meckert der Compiler entsprechend.
Man sollte also Header-Files mit Funktionsdeklarationen sowohl dort
includen, wo die Funktionen definiert werden, als auch dort, wo sie
aufgerufen werden. Dann garantiert die modulübergreifende Typsicherheit
für diese Funktionen.
Die Aufgabe von static auf Compilation Unit Ebene ist es, Dinge
innerhalb dieser Compilation Unit insofern soweit zu verstecken, dass
sie im globalen Namensraum nicht mehr auftauchen und es daher bei
unterschiedlichen Modulen nur insofern zu einem Name-Clash kommen kann,
als es ihre externen Interfaces betrifft.
static in einer derartigen Verwendung verhindert nur, dass andere Module
wissen bzw. sich darum kümmern müssen, dass es diese Funktion oder
Variable überhaupt gibt. static markiert ein Implementierungsdetail
eines Moduls, das ausser diesem Modul niemand anderen etwas angeht.
Genau in diesem Sinne sollst du es auch benutzen.
Das man derartiges aushebeln kann, ist eine andere Geschichte. Aber
warum sollst du als Programmierer eines Moduls deine eigene
Namens-Barriere aushebeln? Das ist so, wie wenn du die Haustür
abschliesst, den Schlüssel unter die Matte legst und an die Tür einen
Zettel mit "Schlüssel liegt unter der Matte" hängst. Klar, du kannst das
machen. Du kannst aber auch allen das Leben einfacher machen und ganz
einfach nicht zusperren.
Karl Heinz schrieb:> Gewöhn dir die Sache mit dem Funktionspointer auf static gleich> wieder> ab. Du liest da zu viel hinein, nur weil man das machen könnte. Nicht> alles was man machen könnte ist auch sinnvoll.>> Wenn ein Modul eine Funktion exportiert, dann ist die entsprechende> Funktion nicht static. Es mag vereinzelt Ausnahmen geben, aber es ist> nicht der Regelfall.
Verstehe ich. Nur mir scheint, als gäbe es Fälle, in denen das nicht so
einfach ist. Die Ordnerstruktur mit den Dateien widerspricht der
Programmsturktur bzw. sind nicht voll kompatibel.
Ich denke mir mal ein gutes (unwiderlegbares) Beispiel aus. Wenn mir
eines einfällt, poste ich es.
Karl Heinz schrieb:> (Namens-Barriere aushebeln?) Das ist so, wie wenn du die Haustür> abschliesst, den Schlüssel unter die Matte legst und an die Tür einen> Zettel mit "Schlüssel liegt unter der Matte" hängst. Klar, du kannst das> machen. Du kannst aber auch allen das Leben einfacher machen und ganz> einfach nicht zusperren.
Der ist gut. So gut, dass ich ihm das merke. Wenn mal jemand zu mir mit
dem gleichen Problem kommt, erzähle ich deinen Vergleich als wäre es mir
selbst eingefallen ;-)
Axel221 schrieb:> FOO kann bar.h nicht inkludieren und umgekehrt, da ich sonst beim> inkludieren eine Ordnerebene "höher" gehen müsste, was - soweit ich weis> - in C eine schlechte Praxis ist.
Dafür gibt es den Compiler-Kommadonzeilenparameter -I. Damit gibt man
die Verzeichnisse an, in denen nach Headern gesucht werden soll.
Axel221 schrieb:> FOO kann bar.h nicht inkludieren und umgekehrt, da ich sonst beim> inkludieren eine Ordnerebene "höher" gehen müsste
Musst Du nicht; Du kannst auch beide Verzeichnisse zum Include-Pfad
hinzufügen.
Abgesehen davon: Selbstverständlich ist es legitim,
#include "../bar/bar.h"
zu verwenden.
Ob das eine sinnvolle Quelltextstrukturierung ist, ist eine ganz
andere Frage.
Solche impliziten Deklarationen sind heute nur noch eine Verneigung vor
Dennis Ritchie, der in grauer Vorzeit auf diese dusselige Idee kam, und
man alte Programme zwar gerne mit Warnungen bewerfen, aber nicht über
Bord schmeissen wollte.