Parameterübergabe bei Funktion - welcher Stil?



  • Hallo,

    ich frage mich gerade folgendes: Wann nimmt man Zeiger, wann nimmt man Referezen und wann nutzt man den Stack wenn man einer Funktion Parameter übergeben will.

    Ich habe z.B. folgenden Code (MSVC++ 6):

    #include "stdafx.h"
    #include "windows.h"
    #include "stdlib.h"
    
    struct old_OMExport
    {
    	int ProtocolVersion;
    	long numElements;
    	float sValue;
    };
    
    struct new_OMExport
    {
    	int ProtocolVersion;
    	long numElements;
    	double sValue;
    	float hrtValue;
    };
    
    new_OMExport GetNewerOMExportAndConvert_1 ( old_OMExport ex1, old_OMExport ex2)
    {
    	new_OMExport ret;
    
    	ret.hrtValue = 1.0f; //default
    	ret.ProtocolVersion = 14; // neues protokoll
    
    	if ( ex1.ProtocolVersion > ex2.ProtocolVersion)
    	{
    		ret.sValue = ex1.sValue;
    		ret.numElements = ex1.numElements; 
    	}
    	else
    	{
    		ret.sValue = ex2.sValue;
    		ret.numElements = ex2.numElements; 
    	}
    
    	return ret;
    }
    
    new_OMExport GetNewerOMExportAndConvert_2 ( old_OMExport* ex1, old_OMExport* ex2)
    {
    	new_OMExport ret;
    
    	ret.hrtValue = 1.0f; //default
    	ret.ProtocolVersion = 14; // neues protokoll
    
    	if ( ex1->ProtocolVersion > ex2->ProtocolVersion)
    	{
    		ret.sValue = ex1->sValue;
    		ret.numElements = ex1->numElements; 
    	}
    	else
    	{
    		ret.sValue = ex2->sValue;
    		ret.numElements = ex2->numElements; 
    	}
    
    	return ret;
    }
    
    new_OMExport GetNewerOMExportAndConvert_3 ( old_OMExport& ex1, old_OMExport& ex2)
    {
    	new_OMExport ret;
    
    	ret.hrtValue = 1.0f; //default
    	ret.ProtocolVersion = 14; // neues protokoll
    
    	if ( ex1.ProtocolVersion > ex2.ProtocolVersion)
    	{
    		ret.sValue = ex1.sValue;
    		ret.numElements = ex1.numElements; 
    	}
    	else
    	{
    		ret.sValue = ex2.sValue;
    		ret.numElements = ex2.numElements; 
    	}
    
    	return ret;
    }
    
    int main(int argc, char* argv[])
    {
    
    	int i;
    
    	new_OMExport new_e;
    	old_OMExport e1, e2;
    	e1.ProtocolVersion = 12;
    	e2.ProtocolVersion = 7;
    	e1.numElements = 3255;
    	e2.numElements = 1244;
    	e1.sValue = 5.0f;
    	e2.sValue = 3.55f;
    
    	long start, end;
    	char diff[20];
    
    	start = GetTickCount();
    	for (i = 0; i < 10000000;i++)
    	{
    		new_e = GetNewerOMExportAndConvert_1(e1,e2);
    	}
    	end = GetTickCount();
    	itoa(end-start,diff,10);
    	MessageBox(0,diff,"Methode 1",MB_OK);
    
    	start = GetTickCount();
    	for (i = 0; i < 10000000;i++)
    	{
    		new_e = GetNewerOMExportAndConvert_2(&e1,&e2);
    	}
    	end = GetTickCount();
    	itoa(end-start,diff,10);
    	MessageBox(0,diff,"Methode 2",MB_OK);
    
    	start = GetTickCount();
    	for (i = 0; i < 10000000;i++)
    	{
    		new_e = GetNewerOMExportAndConvert_3(e1,e2);
    	}
    	end = GetTickCount();
    	itoa(end-start,diff,10);
    	MessageBox(0,diff,"Methode 3",MB_OK);
    
    	return 0;
    }
    

    Die Messung sagt mit, dass Methode 1 etwas schneller ist als 2, das wiederum schneller ist als Methode 3.

    Kann man daraus schließe, das man bei kleinen Structs(wo ist die Grenze?) generell nach Methode 1 arbeiten sollte und nur wenn man an einem Parameter was veränern will Methode 2 oder 3?
    Warum sollte ich überhaupt mit Methode 3 arbeiten, wenn Methode 2 doch auch immer geht und schneller ist?

    Ich hoffe mir kann dass jemand erklären, danke im voraus.

    Christian P.



  • Hmmm eigentlich sollten Methode 2 und 3 schneller sein, da bei diesen Fällen eindeutig weniger kopiert wird bei der Parameterübergabe als bei Methode 1. Möglicherweise spielen noch andere Faktoren mit ein (z. B. Debuginformation, andere Programme, die laufen, ...) 😕 .



  • Wobei Methode 3 natürlich kein C ist 😉

    Ich übergebe structs prinzipiell erstmal als Zeiger. Sollte das unpassend sein, dann schaue ich weiter. Aber meistens fährt man mit Zeigern am besten.

    bzw. in C++ natürlich mit const Referenzen



  • Technisch besteht zwischen 2 und 3 kein unterschied - in beiden Fällen wird die Adresse rübergegeben. Der gemessene Unterschied kann durch Meßungenauigkeit, Cache-Effekte usw. hervorgerufen werden.

    Pass by value ist praktisch immer effektiver, wenn sizeof(T) <= sizeof(T*) ist. (Ausnahme: Die Funktion benötigt die Adresse des übergebenen Parameters - z.B. für einen reinterpret-cast)

    Abhängig von der Funktion und der "Erzeugung" der Aufrufparameter kann man oft bis 2*sizeof(int) gehen, da eine unnötige Adreßnahme den Optimizer ausbremsen kann.

    Auf der Intel/x86-32 bit- Platform sind 16-bit Daten etwas problematisch, aber da begibt man sich schon in die "Abgründer der Optimierung" - hier kann man für so kurze Stücken keine definitive Aussage mehr machen.



  • ...zwischen Methode 2 & 3.
    Methode 2 arbeitet bei der Übergabe mit einer Adresse, d.h. die Funktion arbeitet effektiv auf der "Instanz" des Hauptprogrammes.
    Bei Methode 3 wird eine Referenz erzeugt. Dieses ist, meines Wissens nach, eine vollständige Kopie der "Instanz" aus dem Hauptprogramm. Diese Kopie ist aber mit dem Original aus dem Hauptprogramm verknüpft.
    Der Unterschied in der Geschwindigkeit könnte also auch daher kommen.



  • Bei Methode 3 wird eine Referenz erzeugt. Dieses ist, meines Wissens nach, eine vollständige Kopie der "Instanz" aus dem Hauptprogramm

    Nein. Eine Referenz ist intern wie ein "automtisch dereferenzierte Pointer" implementiert (kann man sich schön im Disassembly anschauen). Man arbeitet auf der übergebenen Instanz.

    So arbeiten zumindest die mir vertrauten Compiler - Watcom 10 & 11, VC6, VC7. Eine Implementation wie von dir erwähnt ist technisch evtl. möglich - z.B. "Rückkopieren" im Epilog und an ein paar anderen Stellen. Ist aber m.E. nicht sinnvoll.)

    -----
    Was ich zum Stil vergessen hatte:

    Das ist sicher geschmackssache, aber ich verwende folgende Faustregeln:

    1. für "const / [in/out]" - Parameter: wenn "NULL" ein möglicher Parameter ist, dann natürlich Pointer. Sonst Referenz
    2. [out] oder [in/out] - Parameter: Wenn aus der restlichen Signatur (z.B. Funktionsname) nicht ersichtlich ist, daß der Parameter verändert werden kann, nehme ich pointer.
    3. Bei Pointer muß die Funktion NULL-Zeiger abtesten, bei referenzen der Aufrufer.


  • Sie benutzen viel zu viele Anglizismen! Wer nur in Anglizismen redet, zeigt, dass er keine Ahnunng davon hat, wovon er spricht. Ausserdem benutzen Sie viel zu wenig Lerzeilen. Vor und nach jeder logischen Struktur MUSS eine Leerzeile folgen! Das hat der Herr Al Turnschuh auch nie verstanden.
    Ich mach ihnen da mal ein Beispiel:

    if(wert == 0)
        ausgabe = 0;
    else
        ausgabe = 1;
    //Leerzeile!!
    //for...
    


  • Wer sind denn sie für einer?



  • Dr. Ulrich Bollerhoff schrieb:

    Sie benutzen viel zu viele Anglizismen! Wer nur in Anglizismen redet, zeigt, dass er keine Ahnunng davon hat, wovon er spricht.

    Blödsinn.

    Vor und nach jeder logischen Struktur MUSS eine Leerzeile folgen!

    Blödsinn.

    if(wert == 0)
        ausgabe = 0;
    else
        ausgabe = 1;
    //Leerzeile!!
    //for...
    

    Blödsinn.

    ausgabe = (wert!=0);
    


  • Dass mein Senf hier auch noch drin steht:
    Ich verwende übergabe per Zeiger nur, wenn der Wert in der Funktion geändert wird. Per const Referenz, wenn der Wert nciht geändert wird, aber kopieren zu teuer wäre. Und als Value, wenn das kopieren eben nicht teuer ist.


Anmelden zum Antworten