Funktionsprototyp und dynamischer Array

Hallo,

heute habe ich an der Uni ein kleines Programm mit dem gcc
kompiliert. Es handelt sich um ein Prgoramm, das ein Komilitone
geschrieben hat. Ich hab mir das angesehen und sofort gesagt,
dass das so nicht funktionieren wird. Er meinte aber es funktioniere
problemlos. Also habe ich es versucht und tatsächlich: es lief.
Der Compiler hat noch nicth mal eine Warnung ausgegeben. Hier der
Code:

#include
#include

//int testfunk(void); //weshalb kann man das weglassen?

int main(int argc, char *argv[])
{
int n;

scanf("%d", &n);
//das sollte doch eigentlich nicht funktionieren
int tab[n];

tab[n-1] = 1;
printf("\n%d %d", tab[n-1], testfunk());
return 0;
}

int testfunk(void)
{
return 5;
}

Versucht man das gleiche mit dem MS Visual Studio passiert folgendes:

Compiling…
test.cpp
P:\test\test.cpp(12) : error C2057: expected constant expression
P:\test\test.cpp(12) : error C2466: cannot allocate an array of constant size 0
P:\test\test.cpp(12) : error C2133: ‚tab‘ : unknown size
P:\test\test.cpp(14) : error C2065: ‚testfunk‘ : undeclared identifier
P:\test\test.cpp(20) : error C2373: ‚testfunk‘ : redefinition; different type modifiers
Error executing cl.exe.

test.obj - 5 error(s), 0 warning(s)

Wieso funktioniert das mit dem gcc (unter Windows XP)?

Gruss,
Michael

Hallo,

Der Compiler hat noch nicth mal eine Warnung ausgegeben.

Versuch mal mit: gcc -O …

Hier :der Code:
//int testfunk(void); //weshalb kann man das weglassen?

Kann man in C (wird als int func(…) angenommen),
aber nicht in C++. Je nach dem, unter welcher
Option (C/C++) man kompiliert, ist es „richtig“ oder
falsch.

int main(int argc, char *argv[])
{
int n;
scanf("%d", &n);
//das sollte doch eigentlich nicht funktionieren
int tab[n];
tab[n-1] = 1;
printf("\n%d %d", tab[n-1], testfunk());
return 0;
}
Wieso funktioniert das mit dem gcc (unter Windows XP)?

Mit gcc erhältst Du eine Warnung bei „-O“, das Fragment
wird tatsächlich in einen „korrekten“ Code übersetzt:

void testfunk(void)
{
 **int n=100** ;
 int tab[n];
 tab[n-1] = 1; // 
}

ergibt unter meinem gcc (3.3.3) folgenden code (gcc -O -S source.c)

testfunk:
 pushl %ebp
 movl %esp, %ebp
**subl $8, %esp**
 andl $-16, %esp
 movl %esp, %eax
**subl $400, %esp**
 movl $1, 396(%esp)
 movl %eax, %esp
 leave
 ret

gcc legt also zuerst 8 Byte für ‚n‘ und ‚tab‘
an, wonach er korrekterweise 100 ints für den Inhalt
von ‚tab‘ auf dem Stack alloziiert.

Grüße

CMБ

Hallo,

danke für die Antwort.
Das mit den Warnungen vertehe ich.
Mit der Definition des Funktionsprototypen ist mir aber
trotzdem nicht ganz klar? Was meinst du mit
„wird als int func(…)“ angenommen? Geht der Compiler
einfach davon aus, dass die Funktion „schon irgendwann
definiert werden wird“? Mit alledem kann ich aber noch
leben.

Was ich überhaupt nicht verstehe ist die Speicherallokierung
für den Array.

Im Falle

int n =100;
int tab[n];

kann ich noch nachvollziehen, dass der Compiler es
freundlicherweise akzeptiert. Die 100 steht ja wenigstens
irgendwo im Code, wenn auch eigentlich an der falschen Stelle.
Aber wieso geht das auch dynamisch? Ersetzt der Compiler das
dann automatisch durch

int *tab = malloc(n*sizeof(int));

Aber wenn das so einfach ginge, wofür gibt es dann überhaupt
noch die malloc-Funktion? Irgendwie hat dieses kleine Stückchen
Code meine 6-Jahre C/C++ Erfahrung auf den Kopf gestellt.

Gruß,
Michael

Hallo Michael,

Mit der Definition des Funktionsprototypen ist mir aber
trotzdem nicht ganz klar? Was meinst du mit
„wird als int func(…)“ angenommen? Geht der Compiler
einfach davon aus, dass die Funktion „schon irgendwann
definiert werden wird“? Mit alledem kann ich aber noch
leben.

Das macht schon jeder C-Compiler so, mache geben zumindest eine Warnung aus.
Wenn der Compiler auf eine nicht definierte Funktion trifft, nimmt er an, dass sie int zurückliefert. DIeses verhalten hat noch hystorische Gründe.

Was ich überhaupt nicht verstehe ist die Speicherallokierung
für den Array.

Im Falle

int n =100;
int tab[n];

kann ich noch nachvollziehen, dass der Compiler es
freundlicherweise akzeptiert. Die 100 steht ja wenigstens
irgendwo im Code, wenn auch eigentlich an der falschen Stelle.
Aber wieso geht das auch dynamisch? Ersetzt der Compiler das
dann automatisch durch

int *tab = malloc(n*sizeof(int));

Aber wenn das so einfach ginge, wofür gibt es dann überhaupt
noch die malloc-Funktion? Irgendwie hat dieses kleine
Stückchen
Code meine 6-Jahre C/C++ Erfahrung auf den Kopf gestellt.

Du musst zwischen C und C++ genau unterscheiden.

In C++ wird malloc() eigentlich nicht mehr verwendet sondern new …
Manche durch C++ implementierte Mechanismen, werden aber bei der Verwendung als C-Compiler nicht abgeschaltet. Hinzu kommt noch was alles als Warnung eingeschaltet ist, da konnte man schon bei einem normalen C-Compiler alles mögliche an fehlerhaften Code ohne Meldung durchjagen.

Ich halte es eigentlich immer so, dass ich alles an Warnungen was der Compiler hergibt einschalte. Wenn ich dann an einer Stelle absichtlich etwas „zusammenwursteln“ muss, schalte ich an dieser Stelle geziehlt mit #pragma diese Warnung aus. Dadurch ist auch gleich im Source-Code dokumentiert, dass dies so gewollt und nicht ein Fehler ist.

MfG Peter(TOO)

Hallo Michael,

Mit der Definition des Funktionsprototypen ist mir aber
trotzdem nicht ganz klar? Was meinst du mit
„wird als int func(…)“ angenommen? Geht der Compiler
einfach davon aus, dass die Funktion „schon irgendwann
definiert werden wird“? Mit alledem kann ich aber noch
leben.

Ja, wie Peter schon andeutete, seit dem alten K&R-C,
sowie IMHO auch im alten Ansi-C ist eine Deklaration einer
Variablen oder einer Funktion „ohne Typ“ für den Compiler
so zu verstehen, dass diese den Typ „vorzeichenbehaftetes
Maschinenwort“ besitzen,z.B.

 const VAR; ==\> const int VAR;

Wird beim Compilieren auf einen Funktionsaufruf gestossen,
der noch nicht (forward-)deklariert wurde, nimmt der Compiler
an, dass es sich um eine Funktion mit obigem Rückgabetyp
handelt (und mit beliebigen Parametern!).
VisualStudio6 warnt dementsprechend:

 warning C4013: 'testfunk' undefined; assuming extern returning int

Du kannst also nach altem Ansi eine
Funktion nicht deklarieren, mit beliebigen
Argumenten versehen und in einem anderen Modul
mit ganz anderen formalen Parametern stehen haben:

Modul_1:

 ...
 ... 
 tab[10] = -1;
 n = 44;
 n = testfunk(tab, n, argc); /\* testfunk() nicht deklariert /\*
 ...

Modul_2:

 void testfunk(int a)
{
 printf("Testfunk, %i\n", a);
}

Das geht!

Was ich überhaupt nicht verstehe ist die Speicherallokierung
für den Array.

Aber wieso geht das auch dynamisch? Ersetzt der Compiler das
dann automatisch durch
int *tab = malloc(n*sizeof(int));

Nein. Der Compiler unterteilt den aktuellen Block (Funktion)
in Unterblöcke, in denen die Variablen eben verwendet werden.
Das kann er (obwohl es nicht „offiziell“ ist) machen, muss
es aber nicht.
Der Schlüssel: lokale Variablen werden auf dem sog.
Stack abgelegt. Der Stack ist lediglich ein Speicher-
segment, das dem Programm zur Verfügung steht und der
von „oben nach unten“ (hinten nach vorn) belegt wird.

Dazu wird bei einer Allokation lediglich der Stack-
Speicherzähler „heruntergezählt“. Am Anfang hat das
Programm z.B. einen Stack von 1MB vom Prozess einge-
richtet bekommen. Der Stack-Zähler steht auf 0x100000
Wenn in C eine Funktion aufgerufen wird, bekommt sie
vom aufrufenden Programmteil die „Argumente“ auf dem
Stack angeordnet, dann wird sie aufgerufen:

 int a = 10;
 int b = 20;

 **testfunk(a, b)  
 :weiter   
 c = a + b;**  
 ...

wird -->

 SUB STACKPOINTER 4 // a-\> stack jetzt bei 1,000,000 - 4
 WRITE\_STACK a
 SUB STACKPOINTER 4 // b-\> stack jetzt bei 1,000,000 - 8
 WRITE\_STACK b
 SUB STACKPONTER 4 // Adresse von Ausführungspunkt nach testfunk()
 WRITE\_STACK ADDR(:weiter)
 CALL \_\_testfunk // Aufruf der Funktion
 ADD STACKPOINTER 12 // nach Aufruf räumt C den Stack auf!
 WEITER: ADD C = A + B
 ...

Aber wenn das so einfach ginge, wofür gibt es dann überhaupt
noch die malloc-Funktion?

Die malloc, free, alloc usw.-Funktionen sind dadurch
charakterisiert, dass sie einen Speicherblock zur
Verfügung stellen, der nicht auf dem Stack des
laufenden Programms liegt. Daher „überdauert“ der
Block auf Funktionsaufrufe beliebiger Tiefe und
gefährdet nicht den überhaupt zur Verfügung stehenden
Stackspeicher durch zu hohes/schnelles „Abzwacken“.

Zu Zeiten von Turbopascal sagte man, der Speicher
kommt vom „Heap“. Das ist eigentlich nur ein Interface
in die Speicherverwaltung des Betriebssystems, das
von der Laufzeitbibliothek des Compilers (eben new/alloc)
„optimiert“ wird (schnelle Bereitstellung, Vermeidung
von Fragmentierung etc. etc.)

Grüße

CMБ