Über die Reihenfolge der Eingabeparameter

Lesezeit: 10 Minuten

Benutzer-Avatar
herohuyongtao

Wenn eine Funktion/Methode viele Eingabeparameter enthält, macht es einen Unterschied, ob sie in unterschiedlichen Reihenfolgen übergeben werden? Wenn ja, in welchen Aspekten (Lesbarkeit, Effizienz, …)? Ich bin eher neugierig, was ich für meine eigenen Funktionen/Methoden tun soll?

Es scheint mir, dass:

  1. Parameter, die von Referenzen/Zeigern übergeben werden, kommen oft vor Parametern, die von Werten übergeben werden. Zum Beispiel:

    void* memset( void* dest, int ch, std::size_t count ); 
    
  2. Zielparameter kommen häufig vor Quellparametern. Zum Beispiel:

    void* memcpy( void* dest, const void* src, std::size_t count );
    
  3. Abgesehen von einigen harten Einschränkungen, dh Parameter mit Standardwerten müssen zuletzt kommen. Zum Beispiel:

    size_type find( const basic_string& str, size_type pos = 0 ) const;
    
  4. Sie sind funktional äquivalent (erreichen das gleiche Ziel), egal in welcher Reihenfolge sie passieren.

  • Beachten Sie auch, dass Parameter mit Standardwerten an letzter Stelle stehen müssen, was die Auswahl einschränken kann.

    – Roger Rowland

    2. Januar 2014 um 10:41 Uhr

  • Meinst du allgemein, wie du mit deinen eigenen Funktionen umgehen sollst? In diesem Fall spielt es keine Rolle, solange der Aufruf mit der Deklaration übereinstimmt.

    – Irgendein Programmierer-Typ

    2. Januar 2014 um 10:41 Uhr

  • In 99% aller Fälle sollte die Optimierung auf Lesbarkeit, Konvention und Konsistenz alle vermeintlichen Unterschiede in Bezug auf die Effizienz übertrumpfen.

    – Frank Osterfeld

    2. Januar 2014 um 10:51 Uhr

  • Sie haben geschlossene Anfragen erhalten, weil Ihre Frage subjektiv ist. Einige Leute ordnen Argumente als Eingabe zuerst, Eingabe/Ausgabe als nächstes, Ausgabe erst zuletzt mit der Begründung einer Datenfluss-Denkweise an. Andere machen genau das Gegenteil, mit der Begründung, dass a=b+c so aussieht. Welcher Weg ist richtig, welcher falsch? Weder. Sie sind subjektive Konventionen. Was falsch ist, ist die Frage “Welcher Weg ist richtig, welcher Weg ist falsch?”

    – David Hamm

    2. Januar 2014 um 12:40 Uhr

  • Meiner Beobachtung nach setzen C-Bibliotheksfunktionen das Ziel vor die Quelle, wie in memcpy während C++ STL das Ziel nach der Quelle setzt, wie z std::copy. Ich denke, der Unterschied ist nur eine Frage des Stils, genauso wie die Intel-Syntax im Vergleich zu AT&T.

    – Siyuan Ren

    2. Januar 2014 um 14:29 Uhr

Benutzer-Avatar
Toni Delroy

Es gibt ein paar Gründe, warum es wichtig sein kann – unten aufgeführt. Der C++-Standard selbst schreibt in diesem Bereich kein bestimmtes Verhalten vor, daher gibt es keine tragbare Möglichkeit, über Leistungsauswirkungen nachzudenken, und selbst wenn etwas in einer ausführbaren Datei nachweislich (etwas) schneller ist, eine Änderung irgendwo im Programm oder am Compiler Optionen oder Version können den früheren Vorteil entfernen oder sogar umkehren. In der Praxis ist es äußerst selten, dass Leute darüber sprechen, dass die Parameterreihenfolge für ihre Leistungsoptimierung von Bedeutung ist. Wenn es Ihnen wirklich wichtig ist, untersuchen Sie am besten die Ausgabe Ihres eigenen Compilers und/oder vergleichen Sie den resultierenden Code.

Ausnahmen

Die Reihenfolge der Auswertung von Ausdrücken, die an Funktionsparameter übergeben werden, ist nicht spezifiziert, und es ist durchaus möglich, dass sie durch Änderungen an der Reihenfolge, in der sie im Quellcode erscheinen, beeinflusst wird, wobei einige Kombinationen in der CPU-Ausführungspipeline besser funktionieren oder früher eine Ausnahme auslösen das einige andere Parametervorbereitung kurzschließt. Dies könnte ein erheblicher Leistungsfaktor sein, wenn einige der Parameter temporäre Objekte sind (z. B. Ergebnisse von Ausdrücken), deren Zuweisung/Erstellung und Zerstörung/Aufhebung der Zuweisung teuer sind. Auch hier könnte jede Änderung am Programm einen zuvor beobachteten Vorteil oder Nachteil entfernen oder rückgängig machen. Wenn Sie sich also darum kümmern, sollten Sie vor dem Aufrufen der Funktion ein benanntes temporäres Parameter für Parameter erstellen, die Sie zuerst auswerten möchten.

Register vs. Cache (Stapelspeicher)

Einige Parameter können in Registern übergeben werden, während andere auf den Stack geschoben werden – was effektiv bedeutet, dass mindestens der schnellste der CPU-Caches eingegeben wird und impliziert, dass ihre Verarbeitung möglicherweise langsamer ist.

Wenn die Funktion trotzdem auf alle Parameter zugreift und die Wahl besteht, Parameter X in ein Register und Y auf den Stapel zu legen oder umgekehrt, spielt es keine Rolle, wie sie übergeben werden, aber da die Funktion Bedingungen haben kann beeinflusst, welche Variablen tatsächlich verwendet werden (wenn Anweisungen, Schalter, Schleifen, die eingegeben werden können oder nicht, frühe Rückkehr oder Unterbrechungen usw.), ist es möglicherweise schneller, wenn eine Variable, die nicht wirklich benötigt wird, auf dem Stapel war, während eine benötigte darin war ein Register.

Sehen http://en.wikipedia.org/wiki/X86_calling_conventions für einige Hintergrundinformationen und Informationen zu Anrufkonventionen.

Ausrichtung und Polsterung

Die Leistung könnte theoretisch durch die Einzelheiten der Parameterübergabekonventionen beeinträchtigt werden: Die Parameter müssen möglicherweise für jeden – oder vielleicht nur mit voller Geschwindigkeit – Zugriff auf den Stapel besonders ausgerichtet werden, und der Compiler kann sich dafür entscheiden, die Werte, die er drückt, aufzufüllen, anstatt sie neu zu ordnen – das ist es Es ist schwer vorstellbar, dass dies signifikant ist, es sei denn, die Daten für Parameter lagen in der Größenordnung der Cache-Seitengrößen

Nicht leistungsbezogene Faktoren

Einige der anderen Faktoren, die Sie erwähnen, können sehr wichtig sein – zum Beispiel neige ich dazu, alle nicht konstanten Zeiger und Referenzen zuerst zu setzen und die Funktion load_xxx zu nennen, damit ich eine konsistente Erwartung habe, welche Parameter geändert werden können und in welcher Reihenfolge geh an ihnen vorbei. Es gibt jedoch keine besonders dominante Konvention.

Genau genommen spielt es keine Rolle – Parameter werden auf den Stack geschoben und die Funktion, die auf sie zugreift, indem sie sie irgendwie vom Stack nimmt.

Bei den meisten C/C++-Compilern können Sie jedoch alternative Aufrufkonventionen angeben. Beispielsweise unterstützt Visual C++ die __fastcall Konvention, die die ersten 2 Parameter in den ECX- und EDX-Registern speichert, was Ihnen (theoretisch) unter den richtigen Umständen eine Leistungsverbesserung bringen sollte.

Es gibt auch __dieser Anruf die speichert die this Zeiger im ECX-Register. Wenn Sie C++ verwenden, kann dies nützlich sein.

Benutzer-Avatar
Bolov

Hier gibt es einige Antworten, in denen Aufrufkonventionen erwähnt werden. Sie haben nichts mit Ihrer Frage zu tun: Egal welche Aufrufkonvention Sie verwenden, die Reihenfolge, in der Sie die Parameter deklarieren, spielt keine Rolle. Es spielt keine Rolle, welche Parameter von Registern und welche von Stack übergeben werden, solange die gleiche Anzahl von Parametern von Registern und die gleiche Anzahl von Parametern von Stack übergeben wird. Bitte beachten Sie, dass Parameter, die größer als die Größe der nativen Architektur sind (4 Byte für 32 Bit und 8 Byte für 64 Bit), von einer Adresse übergeben werden, sodass sie mit der gleichen Geschwindigkeit wie Daten kleinerer Größe übergeben werden .

Nehmen wir ein Beispiel:

Sie haben eine Funktion mit 6 Parametern. Und Sie haben eine Aufrufkonvention, nennen wir sie CA, die einen Parameter pro Register und den Rest (5 in diesem Fall) pro Stack übergibt, und eine zweite Aufrufkonvention, nennen wir sie CB, die 4 Parameter durch Register und den Rest übergibt (in diesem Fall 2) pro Stapel.

Nun, natürlich ist CA schneller als CB, aber es hat nichts mit der Reihenfolge zu tun, in der die Parameter deklariert werden. Für CA ist es so schnell, egal welchen Parameter Sie zuerst deklarieren (durch Register) und welchen Sie als 2., 3…6. (Stack) deklarieren, und für CB ist es so schnell, egal welche 4 Argumente Sie für Register deklarieren und die Sie als letzte 2 Parameter deklarieren.


Nun zu deiner Frage:

Die einzige obligatorische Regel ist, dass optionale Parameter zuletzt deklariert werden müssen. Auf einen optionalen Parameter darf kein nicht optionaler Parameter folgen.

Abgesehen davon können Sie jede beliebige Reihenfolge verwenden, und der einzige starke Rat, den ich Ihnen geben kann, ist konsequent sein. Wählen Sie ein Modell und bleiben Sie dabei.

Einige Richtlinien, die Sie berücksichtigen könnten:

  • Ziel kommt vor Quelle. Dies soll in der Nähe sein destination = source.
  • Die Größe des Puffers kommt nach dem Puffer: f(char * s, unsigned size)
  • Eingabeparameter zuerst, Ausgabeparameter zuletzt (dies widerspricht dem ersten, den ich Ihnen gegeben habe)

Aber es gibt kein „falsch“ oder „richtig“ oder gar eine allgemein akzeptierte Richtlinie für die Reihenfolge der Parameter. Entscheide dich für etwas und sei konsequent.

Bearbeiten

Ich dachte an eine “falsche” Art, Ihre Parameter zu ordnen: in alphabetischer Reihenfolge :).

Bearbeiten 2

Wenn ich zum Beispiel sowohl für CA als auch einen Vektor (100) und einen Int übergebe, ist es besser, wenn Vektor (100) zuerst kommt, dh Register verwenden, um größere Datentypen zu laden. Recht?

Nein. Wie ich bereits erwähnt habe, spielt die Datengröße keine Rolle. Lassen Sie uns über eine 32-Bit-Architektur sprechen (dieselbe Diskussion gilt für jede Architektur 16-Bit, 64-Bit usw.). Analysieren wir den 3. Fall, den wir hinsichtlich der Größe der Parameter in Bezug auf die native Größe der Architektur haben können.

  • Gleiche Größe: 4-Byte-Parameter. Hier gibt es nichts zu reden.
  • Kleinere Größe: Es wird ein 4-Byte-Register verwendet oder 4-Byte werden auf dem Stapel zugewiesen. Also auch hier nichts Interessantes.
  • Größer: (z. B. eine Struktur mit vielen Feldern oder ein statisches Array). Unabhängig davon, welche Methode zum Übergeben dieses Arguments gewählt wird, befinden sich diese Daten im Speicher, und was übergeben wird, ist ein Zeiger (Größe 4 Bytes) auf diese Daten. Wieder haben wir ein 4-Byte-Register oder 4-Byte auf dem Stack.

Die Größe der Parameter spielt keine Rolle.

Bearbeiten 3

Wie @TonyD erklärt hat, ist die Reihenfolge wichtig, wenn Sie nicht auf alle Parameter zugreifen. Siehe seine Antwort.

  • Sehr gute Vorschläge. Ein Kommentar ist, dass Aufrufkonventionen sinnvoll sein können, wenn die Eingabeparameter unterschiedliche Typen haben. Zum Beispiel beides für CAwenn ich a passiere vector<int>(100) und ein intes wird besser sein, wenn vector<int>(100) kommt zuerst, dh verwenden Sie Register, um größere Datentypen zu laden. Recht?

    – herohuyongtao

    2. Januar 2014 um 11:51 Uhr


  • @herohuyongtao Nein, wird es nicht. Wie ich bereits erwähnt habe, wird der Vektor von einer Adresse übergeben, die die gleiche Größe wie ein int hat, daher spielt es keine Rolle, welchen Parameter Sie für die Übergabe als Register und welchen für die Übergabe durch den Stack auswählen.

    – Bolov

    2. Januar 2014 um 11:53 Uhr


  • Aha. Wenn du sagst parameters that are higher in size than the native architecture size (4-bytes for 32-bit and 8-byte for 64-bit) are passed by an addressbedeutet es Typen (vielleicht für a char), die kleiner als die native Architekturgröße sind, werden direkt von ihren Werten übergeben?

    – herohuyongtao

    2. Januar 2014 um 11:59 Uhr

  • Es tut mir leid, aber die Reihenfolge der Parameter kann eine Rolle spielen.

    – Toni Delroy

    2. Januar 2014 um 12:03 Uhr

  • @Fabian Umgekehrt habe ich das noch nie gesehen. Die C-Bibliothek verwendet diese Konvention, der gesamte Code, den ich gesehen habe, verwendet diese Konvention. Siehe memcpy, qsort, strncpy usw. Sie alle haben den Größenparameter nach dem Puffer. Also, ja, es ist eine weit verbreitete Konvention. Der Vorteil ist Konsistenz und das Prinzip des geringsten Erstaunens.

    – Bolov

    2. Februar 2017 um 8:35 Uhr

Benutzer-Avatar
Wechsel

Ich habe irgendwie ein paar verwandte Seiten gefunden.

https://softwareengineering.stackexchange.com/questions/101346/what-is-best-practice-on-ordering-parameters-in-a-function

https://google.github.io/styleguide/cppguide.html#Function_Parameter_Ordering

Erstens beantwortet der C ++ – Stil von Google die Frage nicht wirklich, da er die tatsächliche Reihenfolge innerhalb der Eingabeparameter oder Ausgabeparameter nicht beantwortet.

Die andere Seite schlägt im Grunde vor, dass Bestellparameter in einem Sinne leicht zu verstehen und zu verwenden sind.

Aus Gründen der Lesbarkeit ziehe ich es persönlich vor, Parameter in alphabetischer Reihenfolge zu ordnen. Sie können aber auch an einer Strategie arbeiten, um die Parameter so zu benennen, dass sie schön geordnet sind, damit sie immer noch leicht zu verstehen und zu verwenden sind.

1158000cookie-checkÜber die Reihenfolge der Eingabeparameter

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy