PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : printf nimmt immer das erste argument



TrollSlayer
09-09-2003, 14:56
Hallo!
Wie arbeitet printf denn die liste der argumente ab? bei follgedem code wird zwei mal das erste agrument genommen, obwohl zwei verschiedene angegeben sind:

printf("now: %s last access: %s", ctime(&t_now), ctime(&t_file));
Gibt beide male dasselbe datum aus (t_now), die beiden daten unterschiedlich sind!!! :confused:
Wenn man aber die ausgabe auf zwei zeilen verteilt:

printf("now: %s", ctime(&t_now));
printf("last access: %s", ctime(&t_file));

ist alles wieder in ordnung (es werden zwei unterschiedliche daten ausgegeben)!

Hier nochmal das ganze programm:


#include <stdio.h>
#include <time.h>
#include <sys/stat.h>

int main() {
int stat_retval;
char *sf_name = "sayhello.c";
struct stat sf_stat;
time_t t_now, t_file;

stat_retval = stat(sf_name, &sf_stat);
t_file = sf_stat.st_atime;
t_now = time(NULL);

printf("Now: %s last access: %s", ctime(&t_now), ctime(&t_file));
printf("now: %s", ctime(&t_now));
printf("last access: %s", ctime(&t_file));
return(0);
}

TrollSlayer
09-09-2003, 15:13
Ich habs! Es liegt an ctime()! Das ergebnis des zweiten aufruffs wird einfach überschrieben(C arbeitet die funktions-parameter doch von rechts nach links oder?). es heißt nähmlich im manual:
"The return value points to a statically allocated string which might be overwritten by subsequent calls to any of the date and time functions."

wraith
09-09-2003, 15:26
Original geschrieben von TrollSlayer
Ich habs! Es liegt an ctime()! Das ergebnis des zweiten aufruffs wird einfach überschrieben(C arbeitet die funktions-parameter doch von rechts nach links oder?).
Nein,die Reihenfolge der Abarbeitung ist undefiniert bei Funktionsargumenten.
Es gibt keinen Sequenzpunkt,der nächste ist erst wieder beim ;

Das Problem mit der statischen Variablen hast du ja selber gelöst :).

TrollSlayer
09-09-2003, 15:44
Doch es gibt sicher eine bestimmte reihenfolge bei der funktionsparameter-abarbeitung. Bei c war das glaube ich von rechts nach links und bei pascal umgekehrt.

TrollSlayer
09-09-2003, 15:46
Sorry! habe mich vielleicht falsch ausgedrückt. nicht abarbeitung sondern die reihenfolge der übergabe an die funktion. :)

wraith
09-09-2003, 15:51
Original geschrieben von TrollSlayer
Sorry! habe mich vielleicht falsch ausgedrückt. nicht abarbeitung sondern die reihenfolge der übergabe an die funktion. :)
Nein,es ist so wie ich sage.Trust me ^^.

Test Code


#include <stdio.h>

int n;
int f(int (*g)());
int f(int (*g)())
{
int m;
m = n;
if(m == 0)
return 1;
else
{
n--;
return m * g(g);
}
}

int main()
{
n = 5;
printf("%d %d\n",n,f(f));
n = 5;
printf("%d %d\n",f(f),n);
return 0;
}

überlege dir was rauskommt,und dann lass es laufen.

Die Abarbeitung von links nach rechts gibt es bei || und &&,und beim Kommaoperator (das , beim Funktionsaufruf ist kein Kommaoperator)

TrollSlayer
09-09-2003, 16:03
ok. das sieht überzeugend aus. aber im "Assembler Buch" 3. Auflage steht follgendes:
"Offensichtlich arbeitet sich also der Compiler vor rechts nach links durch die Parameter, also genau anders herum als Pascal. C arbeitet die Parameter, die Routinen übergeben werden, von rechts nach links ab."
:confused:

TrollSlayer
09-09-2003, 16:12
Nein! Das stimmt schon mit der reihenfolge: von rechts nach links)! Schau die den assembler code den gcc aus deinem programm generiert:
beim ersten printf:
die funktion f wird zuerst ausgeführt, dann das ergebnis davon auf den stack gelegt, dann das n.
beim zweiten printf:
es wird wieder zuerst f ausgeführt, dann jedoch das n auf den stack gelegt und dann erst der rückgabewert von f!

wraith
09-09-2003, 16:16
Original geschrieben von TrollSlayer

"Offensichtlich arbeitet sich also der Compiler vor rechts nach links durch die Parameter, also genau anders herum als Pascal. C arbeitet die Parameter, die Routinen übergeben werden, von rechts nach links ab."
:confused:
Ja,der Grund,daß das dort so steht ist der,daß früher (also in der IT-Steinzeit),Funktionparameter auf den Stack gepusht wurden.
Pascal machte das von links nach rechts,und C von rechts nach links.
C macht das von rechts nach links,damit bei Funktionsaufrufen mit Variablerargumentenliste (zb. printf),der Formatstring ganz oben auf dem Stack lag.

So,aber erstens werden heutezutage (die Rechner haben heute viele Register),die meisten Parameter in Registern an die Funktion übergeben.
Und zweitens,hat auch die Reihenfolge in der die Parameter gepusht wurden nichts damit zutun in welcher Reihenfolge sie ausgewertet wurden.

Das sind alles Freiheit die ein Compilerbauer hat,um die für sein System optimale Variante zu basteln.



Nein! Das stimmt schon mit der reihenfolge: von rechts nach links)! Schau die den assembler code den gcc aus deinem programm generiert:
beim ersten printf:
die funktion f wird zuerst ausgeführt, dann das ergebnis davon auf den stack gelegt, dann das n.
beim zweiten printf:
es wird wieder zuerst f ausgeführt, dann jedoch das n auf den stack gelegt und dann erst der rückgabewert von f!

Also im Zweifel für den Standard ^^,und der legt ganz klar keine Reihenfolge in der Auswertung der Parameter fest.
Wenn du dich auf das verlässt was 'dein' Compiler darausmacht,dann siehst du alt aus,wenn du den Compiler wechselst.

wraith
09-09-2003, 16:27
O.k ich hab' mal den C Standard rausgekramt


3.4.4
1 unspecified behavior
behavior where this International Standard provides two or more possibilities and
imposes no further requirements on which is chosen in any instance
2 EXAMPLE An example of unspecified behavior is the order in which the arguments to a function are
evaluated.


Klar :).

TrollSlayer
09-09-2003, 16:34
Ja,der Grund,daß das dort so steht ist der,daß früher (also in der IT-Steinzeit),Funktionparameter auf den Stack gepusht wurden.
Nicht nur früher! Es ist heute genau so.

Und zweitens,hat auch die Reihenfolge in der die Parameter gepusht wurden nichts damit zutun in welcher Reihenfolge sie ausgewertet wurden.

Da stimme ich dir völlig zu! Das habe ich auch nicht gemeint. Vielleicht habe ich mich nur schlecht ausgedrückt. Ich meine die reihenfolge der übergabe von parametern.

Wenn du dich auf das verlässt was 'dein' Compiler darausmacht,dann siehst du alt aus,wenn du den Compiler wechselst.
Ich wage es mal zu sagen, dass es auch heute jeder c-compiler so macht (die reihenfolge meine ich). Schau dir doch das compilat, das dein compiler produziert. Ohne eine bestimmte reihenfolge wäre es doch garnicht möglich für die funktion selbst zu wissen wo den die parameter sind.

TrollSlayer
09-09-2003, 16:38
An example of unspecified behavior is the order in which the arguments to a function are
evaluated.
Ja. wie die parameter ausgewertet werden ist sch**ß egal. Aber nicht wie der compiler die parameter-übergabe realisiert.

wraith
09-09-2003, 19:31
Original geschrieben von TrollSlayer
Ja. wie die parameter ausgewertet werden ist sch**ß egal. Aber nicht wie der compiler die parameter-übergabe realisiert.
Aber,wie oben auch bereits geschrieben,werden (native Datentypen auf jedenfall),per Register übergeben,nicht per Stack.
Und wenn die Funktion weiß,dass ihr erster Parameter im Register R10 liegt,dann ist wohl auch egal,in welcher Reihenfolge es dahin gelangt ist ^^.

Und Parameter per Stack übergeben ist Standard,in Deep C Secrets von Peter van der Linden ist das für den sun Compiler schön beschrieben,

TrollSlayer
09-09-2003, 19:39
Du sagst doch selbst:

Und Parameter per Stack übergeben ist Standard :confused:
So wird das auch gemacht. Würde man sie über register übergeben, was macht der compiler, wenn die cpu z.B. x register hat und die funktion aber x+1 parameter?

wraith
09-09-2003, 19:48
Original geschrieben von TrollSlayer
Du sagst doch selbst:
:confused:


Ne,sorry das sollte Register heißen.Sollte aus dem Kontext klar werden,



Würde man sie über register übergeben, was macht der compiler, wenn die cpu z.B. x register hat und die funktion aber x+1 parameter?
Ne,es ist schon Register (nicht Stack ^^,diesmal richtig).
Kommentar aus Deep C Secrets


Some C books make statements
like "parameters are passed to a called function by pushing them on the stack from right to left." This
is oversimplification—if you own such a book, tear out that page and burn it. If you own such a
compiler, tear out those bytes. Parameters are passed in registers (for speed) where possible.

und noch einer


Although we talk about a "stack frame" being "pushed on the stack," an activation record need not be
on the stack. It's actually faster and better to keep as much as possible of the activation record in
registers. The SPARC architecture takes this to the limit with a concept called "register windows" in
which the chip has a set of registers solely dedicated to holding parameters in procedure activation
records.

TrollSlayer
09-09-2003, 20:02
Parameters are passed in registers (for speed) where possible.
Es wäre natürlich besser für die geschwindigkeit. Du hast meine frage aber nicht beantwortet:
Angenommen, man würde die parameter über die register übergeben. Was macht der compiler, wenn die cpu x register hat und die funktion aber x+1 parameter? Und wieso macht der gcc auf einem pc pentium4 die parameterübergabe über den stack?

wraith
09-09-2003, 20:12
Original geschrieben von TrollSlayer
Angenommen, man würde die parameter über die register übergeben. Was macht der compiler, wenn die cpu x register hat und die funktion aber x+1 parameter?

Dann wird ein Teil per Stack übergeben,klar,aber wo ist das Problem :)?
Ich dachte die Grundfrage war die,daß du sagtest es gäbe einen festen Standard,daß immer die Parameter von rechts->links auf den Stack gepusht werden.
Mir ging es nur darum,daß man sich nur auf die Semantik verlassen kann,was unter der Haube passiert ist nicht spezifiziert.



Und wieso macht der gcc auf einem pc pentium4 die parameterübergabe über den stack?
Schalte mal die Optimierungen (-O2 oder -O3) ein,dann macht er das nicht mehr.
Zumindestens bei mir nicht,wo ich das letztemal getestet hatte.

TrollSlayer
09-09-2003, 20:35
Dann wird ein Teil per Stack übergeben
Woher weiß dann so eine function wie printf, die eine variable anzahl an parametern hat, daß sie bereits alle parameter hat, wenn sie die x register gelesen hat oder ob da noch was wichtiges auf dem stack liegt?

Schalte mal die Optimierungen (-O2 oder -O3) ein
Habe ich gemacht. Und der compiler macht die parameterübergabe über den stack.
Nach dem ich 'gcc -O3 -S -o fp.S fp.c' augerufen habe (wobei fp.c dein beispiel-programm ist.) enthällt fp.S trotzdem follgenden code z.B.:


movl $f, (%esp) /* <--- hier wird f also der parameter auf den stack gelegt. */
movl %eax, n
call f /* <--- und hier kommt dann der funktionsaufruf. */
movl $.LC0, (%esp)
imull $5, %eax, %edx
movl n, %ecx
movl %ecx, 4(%esp) /* genau wie hier. zuerst parameterübergabe */
movl %edx, 8(%esp)
call printf /* dann der funktionsaufruf. */

Du hast schon recht. es wäre besser (schneller) in der ersten code zeile oben einfach das f in z.B. ecx legen und dann f aufruffen. Aber dann müßte das für die funktion f festgelegt werden, genauso wie für jede andere funktion. da es jedoch nicht immer so geht (zu viele parameter oder nicht alle register frei), macht es sinn, genau festzulegen wie parameterübergabe gemacht wird. und zwar über den stack. Deswegen wird es auch einheitlich gemacht.

Zumindestens bei mir nicht,wo ich das letztemal getestet hatte
Poste mal bitte das compilat. Es würde mich echt interressieren. Wenn du ein beispiel hast wo die parameterübergabe anders gemacht wird, hast du mich überzeugt. ;)

ps: das zitat aus "deep c secrets" verwirrt mich doch ein bißchen. wenn das stimmt muß ich mein "Assembler buch" echt verbrennen! ;)

wraith
09-09-2003, 21:50
Original geschrieben von TrollSlayer
Woher weiß dann so eine function wie printf, die eine variable anzahl an parametern hat, daß sie bereits alle parameter hat, wenn sie die x register gelesen hat oder ob da noch was wichtiges auf dem stack liegt?

Tja,wie würde ich das machen.Wahrscheinlich die festen Parameter Register,und den Rest Stack.
Ein Grund diese Art von Funktionen zu meiden (keine Typprüfung,schlechtere Performance) ;).
Bei deinem Bsp mit den x Parametern war ich auch nicht von einer Funktion mit variabler Argumentenliste ausgegangen.



Nach dem ich 'gcc -O3 -S -o fp.S fp.c' augerufen habe (wobei fp.c dein beispiel-programm ist.)

Uh,das Beispiel ist dreckig und gemein (übrigens aus dem Drachenbuch),da darf man nicht viel erwarten.

Ein normales Beispiel,was auch üblicher ist,Funktion mit drei Parametern,Compiler Intel (die Syntax ist mir auch lieber wie AT&T ;) )


@foo@12 PROC NEAR
; parameter 1: ecx
; parameter 2: edx
; parameter 3: 12 + esp
.B2.1: ; Preds .B2.0
$LN3:

;;; {

sub esp, 8 ;4.1
$LN4:

;;; return i + d + f;

add ecx, edx ;5.13
$LN5:
add ecx, DWORD PTR [esp+12] ;5.17
mov DWORD PTR [esp], ecx ;5.17
fild DWORD PTR [esp] ;5.17
add esp, 8 ;5.17
ret 4 ;5.17
ALIGN 4
; LOE

2 in Registern,einer Stack.

Aber ich würde ja gerne mal den Sun Compiler testen an dem der Autor des Buches mitgeschrieben hat :).

TrollSlayer
10-09-2003, 09:02
Kannst du bitte noch den zugehörigen c-code posten und den teil wo die funktion aufgerufen wird (an der stelle findet die parameter übergabe statt.) und die version des intel-compilers. Den sun compiler würde ich auch gerne mal testen aber der ist nur für die sun-maschinen geschrieben, oder?
ps: die intel syntax finde ich persönlich auch besser (wahrscheinlich weil ich das so gelern habe :)).

wraith
10-09-2003, 09:36
Original geschrieben von TrollSlayer
Kannst du bitte noch den zugehörigen c-code posten und den teil wo die funktion aufgerufen wird (an der stelle findet die parameter übergabe statt.)

O.k ich mußte den Code umschreiben,damit er die Übergabe nicht wegoptimiert.
Dazu hab' ich die Funktion in eine extra Übersetzungseinheit gepackt
foo.c


int foo(int a,int b,int c)
{
return a + b + c;
}

main.c


#include <stdio.h>

extern int foo(int,int,int);

int main()
{
int a,b,c,res;
scanf("%d%d%d",&a,&b,&c);
res = foo(a,b,c);
return res;
}

(ohne Data-Segmente undso)
foo.asm


PUBLIC @foo@12
@foo@12 PROC NEAR
; parameter 1: ecx
; parameter 2: edx
; parameter 3: 4 + esp
.B1.1: ; Preds .B1.0
$LN1:

;;; {

$LN2:

;;; return a + b + c;

add ecx, edx ;3.13
$LN3:
add ecx, DWORD PTR [esp+4] ;3.17
mov eax, ecx ;3.17
ret 4 ;3.17
ALIGN 4
; LOE
; mark_end;
@foo@12 ENDP
;@foo@12 ENDS

main.asm


_main PROC NEAR
.B1.1: ; Preds .B1.0
$LN1:

;;; {

push ebx ;6.1
mov ebx, esp ;6.1
and esp, -16 ;6.1
sub esp, 16 ;6.1
push eax ;6.1
push eax ;6.1
stmxcsr [esp] ;6.1
pop eax ;6.1
or eax, 32768 ;6.1
push eax ;6.1
ldmxcsr [esp] ;6.1
pop eax ;6.1
pop eax ;6.1
$LN2:

;;; int a,b,c,res;
;;; scanf("%d%d%d",&a,&b,&c);

lea ecx, DWORD PTR [esp+8] ;8.17
$LN3:
lea edx, DWORD PTR [esp+4] ;8.20
$LN4:
lea eax, DWORD PTR [esp] ;8.23
push eax ;8.23
push edx ;8.23
push ecx ;8.23
push OFFSET FLAT: ??_C@_06A@?$CFd?$CFd?$CFd?$AA@ ;8.23
call DWORD PTR __imp__scanf ;8.23
; LOE ebp esi edi
.B1.6: ; Preds .B1.1
add esp, 16 ;8.23
; LOE ebp esi edi
.B1.2: ; Preds .B1.6
$LN5:

;;; res = foo(a,b,c);

push DWORD PTR [esp] ;9.16
mov ecx, DWORD PTR [esp+12] ;9.16
mov edx, DWORD PTR [esp+8] ;9.16
call @foo@12 ;9.16
; LOE eax ebp esi edi
.B1.3: ; Preds .B1.2
$LN6:

;;; return res;

mov esp, ebx ;10.9
pop ebx ;10.9
ret ;10.9
ALIGN 4
; LOE
; mark_end;
_main ENDP



und die version des intel-compilers.

7.1.


Den sun compiler würde ich auch gerne mal testen aber der ist nur für die sun-maschinen geschrieben, oder?

Ja leider ist der auch nicht bei der Solaris Version für x86 dabei.

TrollSlayer
10-09-2003, 09:43
Ok... :( Du hast recht!
Es gibt nähmlich zwei konventionen (vielleicht gibt es noch andere ;) ): _cdecl (übergabe über den stack) und __fastcall (übergabe über die register). Der intel compiler z.B bietet die letztere methode an. Diese ist auch (laut c't) "gerade bei einem RISC-Prozessor mit vielen registern" (sun) "durchgehend angebracht". Ich versuch's mal __fastcall mit dem gcc hinzukriegen. Aber zuerst gehe ich "Das Assembler Buch" verbrennen. :D

peecee
10-09-2003, 16:20
Uh,das Beispiel ist dreckig und gemein (übrigens aus dem Drachenbuch),da darf man nicht viel erwarten.

Blöde frage aber wie heisst das "Drachenbuch" wirklich und um welches programmier Thema gehts darin ???

mfg peecee

wraith
10-09-2003, 16:38
Original geschrieben von peecee
Blöde frage aber wie heisst das "Drachenbuch" wirklich und um welches programmier Thema gehts darin ???

Compilers. Principles, Techniques, and Tools.
http://www.amazon.de/exec/obidos/ASIN/0201100886/qid=1063208238/sr=2-1/ref=sr_aps_prod_1_1/028-6426425-7738143
Klar,warum es das Drachenbuch heißt ;) ?

peecee
10-09-2003, 17:16
Original geschrieben von wraith
Compilers. Principles, Techniques, and Tools.
http://www.amazon.de/exec/obidos/ASIN/0201100886/qid=1063208238/sr=2-1/ref=sr_aps_prod_1_1/028-6426425-7738143
Klar,warum es das Drachenbuch heißt ;) ?

Eine "Hardcore" Buch also :rolleyes: