[JAVA] Was bedeutet es, Funktionsparameter als Referenz zu übergeben?

Was bedeutet es, Funktionsparameter als Referenz zu übergeben?

Bei Funktionsaufrufen gibt es zwei Möglichkeiten, Funktionsparameter (Methodenparameter) zu übergeben: "Wert übergeben" und "Referenz übergeben".

Java und JavaScript werden jedoch nicht als Sprachspezifikation als Referenz übergeben, sondern immer als Wert.

Dies ist ein Beispiel für die Übergabe als Referenz, aber nehmen wir zunächst C # als Beispiel. Im folgenden Beispiel wird die Referenzübergabe verwendet, um die Werte zweier Parameter auszutauschen.

using System;

public class SwapFunc
{
  static void Main(String[] args)
  {
    int x = 0;
    int y = 1;

    //Tauschen Sie die x- und y-Werte aus
    Swap(ref x, ref y);

    Console.WriteLine("x = {0:d}, y = {1:d}\n", x, y);
  }

  //Eine Methode zum Austauschen der Werte von x und y
  static void Swap(ref int x, ref int y)
  {
    var u = x;
    x = y;
    y = u;
  }
}

Der IL (Zwischencode) hierfür lautet wie folgt:

Ich habe das Verhalten in den Kommentaren geschrieben, aber ob es korrekt ist oder nicht, ich verwende eine Stapelmaschine (virtuelle Maschine), um Daten für Berechnungen auf den Stapelrahmen zu pushen und zu platzieren.

Wenn Sie es so ausführen, wie es ist, ist es langsam, daher wird es normalerweise in nativen Code konvertiert und ausgeführt.

In Main liegt der Teil, der sich auf das Übergeben als Referenz bezieht, um IL_0005 und IL_0006. Hier werden die Adressen der Daten x und y auf dem Stapelrahmen auf den Stapel geladen.

Der Teil, der sich auf das Übergeben als Referenz auf der Swap-Seite der Funktion bezieht, wird mit der Anweisung ldind erstellt. Diese Anweisung betrachtet den Inhalt des angegebenen Operanden als Adresse und lädt die Daten, an die diese Adresse gesendet wurde, auf den Stapel.

//  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.18020
//  Copyright (c) Microsoft Corporation. All rights reserved.



// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly Swap
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                             63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module Swap.exe
// MVID: {DDFC4E86-9AF5-4AC3-927E-267DAB2AEE1E}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x04700000


// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit SwapFunc
       extends [mscorlib]System.Object
{
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    //Codegröße 39(0x27)
    .maxstack  3
    .locals init (int32 V_0,
             int32 V_1)
    IL_0000:  nop
    IL_0001:  ldc.i4.0  'Drücken Sie 0.
    IL_0002:  stloc.0  'Pop 0 und Stack Frame V._Speichern Sie auf 0
    IL_0003:  ldc.i4.1  'Schieben Sie einen.
    IL_0004:  stloc.1  'Pop 1 und Stapelrahmen V._Speichern Sie auf 1
    IL_0005:  ldloca.s   ' V_0  V_0-Adresse drücken.
    IL_0007:  ldloca.s   ' V_1  V_Drücken Sie die Adresse 1.
    IL_0009:  call       void SwapFunc::Swap(int32&,  'Rufen Sie Swap an
                                             int32&)
    IL_000e:Nein, tu nichts.
    IL_000f:  ldstr      "x = {0:d}, y = {1:d}\n"  'Formatadresse drücken
    IL_0014:  ldloc.0  ' V_Drücken Sie einen Wert von 0.
    IL_0015:  box        [mscorlib]System.Int32 'Boxen Sie die Oberseite des Stapels
    IL_001a:  ldloc.1  ' V_Drücken Sie einen Wert von 1.
    IL_001b:  box        [mscorlib]System.Int32 'Boxen Sie die Oberseite des Stapels
    IL_0020:  call       void [mscorlib]System.Console::WriteLine(string, ' Console.Rufen Sie WriteLine auf
                                                                  object,
                                                                  object)
    IL_0025:  nop  'nichts tun.
    IL_0026:  ret  'Geh zurück.
  } // end of method SwapFunc::Main

  .method private hidebysig static void  Swap(int32& x,
                                              int32& y) cil managed
  {
    //Codegröße 12(0xc)
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.0  'Push-Argument 0.
    IL_0002:  ldind.i4  'Schieben Sie den Inhalt des Ziels mit dem Stapel oben als Adresse.
    IL_0003:  stloc.0  'In lokaler Variable 0 gespeichert.
    IL_0004:  ldarg.0  'Push-Argument 0.
    IL_0005:  ldarg.1  'Push-Argument 1.
    IL_0006:  ldind.i4  'Schieben Sie den Inhalt des Ziels mit dem Stapel oben als Adresse.
    IL_0007:  stind.i4  'Speichern Sie am Ziel mit dem Stapel oben als Adresse.
    IL_0008:  ldarg.1 'Push-Argument 1.
    IL_0009:  ldloc.0 'Lokale Variable 0 drücken.
    IL_000a:  stind.i4 'Schieben Sie den Inhalt des Ziels.
    IL_000b:  ret
  } // end of method SwapFunc::Swap

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    //Codegröße 7(0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method SwapFunc::.ctor

} // end of class SwapFunc


// =============================================================

// ***********Die umgekehrte Montage ist abgeschlossen***********************
//Warnung:Win32-Ressourcendatei C.:\workspace\dotNET\IL\Swap\Swap.Ich habe res erstellt.

Schauen wir uns als nächstes ein Beispiel für die C-Sprache an.

In der Sprache C werden VMs (virtuelle Maschinen) nicht verwendet, dh sie generieren plötzlich nativen Code.

In diesem Abschnitt werden die in PCs verwendeten x86 und x64 beschrieben. Bei RISC, IBM usw. sollte die Grundidee dieselbe sein, sie unterscheiden sich jedoch, da die Aufrufkonventionen unterschiedlich sind.

Zuerst die C-Quelle, die wie die folgende aussieht.

#include <stdio.h>

void swap(int*, int*);

int main(int argc, char* argv[]) {
  int x = 0;
  int y = 1;

  swap(&x, &y);

  printf("x = %d, y = %d\n", x, y);

  return 0;
}

void swap(int* x, int* y) {
  int u = *x;
  *x = *y;
  *y = u;
}

Das Kompilierungsergebnis davon ist wie folgt. (Für x86)

	.file	"Swap.c"
	.section	.rodata
.LC0:
	.string	"x = %d, y = %d\n"
	.text
.globl main
	.type	main, @function
main:
	leal	4(%esp), %ecx
	andl	$-16, %esp
	pushl	-4(%ecx)
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%ecx
	subl	$36, %esp
	movl	$0, -8(%ebp) #Speichern Sie 0 an der lokalen Adresse des Stapelrahmens(x)
	movl	$1, -12(%ebp) #Speichern Sie 1 unter der lokalen Adresse des Stapelrahmens(y)
	leal	-12(%ebp), %eax #Laden Sie die Adresse von y in EAX
	movl	%eax, 4(%esp) #Laden Sie EAX auf den Stapel.
	leal	-8(%ebp), %eax #Laden Sie die Adresse von x in EAX
	movl	%eax, (%esp) #Laden Sie EAX auf den Stapel.
	call	swap #Rufen Sie die Swap-Funktion auf.
	movl	-12(%ebp), %eax
	movl	-8(%ebp), %edx
	movl	%eax, 8(%esp)
	movl	%edx, 4(%esp)
	movl	$.LC0, (%esp)
	call	printf
	movl	$0, %eax
	addl	$36, %esp
	popl	%ecx
	popl	%ebp
	leal	-4(%ecx), %esp
	ret
	.size	main, .-main
.globl swap
	.type	swap, @function
swap:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$16, %esp
	movl	8(%ebp), %eax #Parameter werden später geladen(x)In EAX geladen
	movl	(%eax), %eax #Laden Sie die Daten des Ziels von EAX in EAX
	movl	%eax, -4(%ebp) #Speichern Sie EAX in lokalen Variablen.
	movl	12(%ebp), %eax #Zuerst geladene Parameter(y)In EAX geladen
	movl	(%eax), %edx #Laden Sie die Daten des Ziels von EAX in EDX
	movl	8(%ebp), %eax #Parameter werden später geladen(x)In EAX geladen
	movl	%edx, (%eax) #Speichern Sie EDX dort, wo EAX hingegangen ist
	movl	12(%ebp), %edx #Zuerst geladene Parameter(y)In EDX geladen
	movl	-4(%ebp), %eax #Laden Sie den Inhalt lokaler Variablen in EAX
	movl	%eax, (%edx) #Speichern Sie EAX dort, wo EDX hingegangen ist.
	leave
	ret
	.size	swap, .-swap
	.ident	"GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-54)"
	.section	.note.GNU-stack,"",@progbits

Für x64 sind die Funktionsaufrufkonventionen unterschiedlich, und Ganzzahlen und Gleitkommazahlen werden ohne Optimierung durch Register geleitet.

Dies liegt daran, dass x64 die Anzahl der Universalregister von 8 auf 16 erhöht hat und die überschüssigen Register als Funktionsparameter verwendet werden.

	.file	"Swap.c"
	.section	.rodata
.LC0:
	.string	"x = %d, y = %d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$32, %rsp
	movl	%edi, -20(%rbp)
	movq	%rsi, -32(%rbp)
	movl	$0, -8(%rbp)
	movl	$1, -4(%rbp)
	leaq	-4(%rbp), %rdx #RDX enthält die Adresse von y.
	leaq	-8(%rbp), %rax #RAX enthält die Adresse von x.
	movq	%rdx, %rsi #Die Parameter werden basierend auf der Aufrufkonvention im RSI-Register übergeben.
	movq	%rax, %rdi #Die Parameter werden basierend auf der Aufrufkonvention an das RDI-Register übergeben.
	call	swap
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	movl	%eax, %esi
	movl	$.LC0, %edi
	movl	$0, %eax
	call	printf
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.globl	swap
	.type	swap, @function
swap:
.LFB1:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movq	%rdi, -24(%rbp)
	movq	%rsi, -32(%rbp)
	movq	-24(%rbp), %rax
	movl	(%rax), %eax
	movl	%eax, -4(%rbp)
	movq	-32(%rbp), %rax
	movl	(%rax), %edx
	movq	-24(%rbp), %rax
	movl	%edx, (%rax)
	movq	-32(%rbp), %rax
	movl	-4(%rbp), %edx
	movl	%edx, (%rax)
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	swap, .-swap
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
	.section	.note.GNU-stack,"",@progbits

Für Sprachen, die nicht als Referenz übergeben werden können, wird die Übergabe als Referenz einfach verwendet, weil die Sprachspezifikation keinen Referenzausdruck enthält.

Wenn Parameter als Wert übergeben werden, können Werte in Allzweckregister geladen werden, insbesondere Ganzzahlen und Gleitkommazahlen, die als Wert übergeben werden können, und Werte können nicht in Allzweckregister, insbesondere Arrays und Allgemein, geladen werden Das Objekt wird als Referenz übergeben.

Daher kann der als Referenz übergebene Typ verwendet werden, um dieselbe Funktion wie die Swap-Funktion zu erreichen.

Der folgende Code ist ein Beispiel für die Verwendung von JavaScript, um die gleiche Funktionalität wie beim Übergeben von Referenzen zu erzielen. Da das Array als Referenz übergeben wird, tauscht die Swap-Funktion den Inhalt der beiden Parameter aus.

'use strict';

const swap = (x, y) => {
    let u = x[0];
    x[0] = y[0];
    y[0] = u;
};

var a = [0];
var b = [1];

swap(a, b);

console.log("%i, %i", a, b);

Recommended Posts

Was bedeutet es, Funktionsparameter als Referenz zu übergeben?
[Java] Das Referenzwort ist schlecht!