[JAVA] Que signifie passer des paramètres de fonction par référence?

Que signifie passer des paramètres de fonction par référence?

Dans les appels de fonction, il existe deux façons de passer des paramètres de fonction (méthode): "passer par valeur" et "passer par référence".

Cependant, Java et JavaScript ne passent pas par référence comme spécification de langage, ils sont toujours passés par valeur.

Ceci est un exemple de passage par référence, mais prenons d'abord C # comme exemple. L'exemple suivant utilise le passage par référence pour échanger les valeurs de deux paramètres.

using System;

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

    //Permuter les valeurs x et y
    Swap(ref x, ref y);

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

  //Une méthode pour échanger les valeurs de x et y
  static void Swap(ref int x, ref int y)
  {
    var u = x;
    x = y;
    y = u;
  }
}

L'IL (code intermédiaire) pour cela est le suivant:

J'ai écrit le comportement dans les commentaires, mais qu'il soit précis ou non, j'utilise une machine à pile (machine virtuelle) pour pousser et afficher des données sur des cadres de pile pour les calculs.

Si vous l'exécutez tel quel, il sera lent, il est donc généralement converti en code natif et exécuté.

Dans Main, la partie liée au passage par référence se situe autour de IL_0005 et IL_0006. Ici, les adresses des données x et y sur le frame de pile sont chargées sur la pile.

La partie liée au passage par référence du côté Swap de la fonction consiste à utiliser l'instruction ldind. Cette instruction considère le contenu de l'opérande spécifié comme une adresse et charge les données auxquelles cette adresse a été créée sur la pile.

//  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
    //Taille de code 39(0x27)
    .maxstack  3
    .locals init (int32 V_0,
             int32 V_1)
    IL_0000:  nop
    IL_0001:  ldc.i4.0  'Appuyez sur 0.
    IL_0002:  stloc.0  'Pop 0 et empiler le cadre V_Enregistrer à 0
    IL_0003:  ldc.i4.1  'Poussez-en un.
    IL_0004:  stloc.1  'Pop 1 et empiler le cadre V_Enregistrer sous 1
    IL_0005:  ldloca.s   ' V_0  V_Appuyez sur 0 adresse.
    IL_0007:  ldloca.s   ' V_1  V_Appuyez sur l'adresse 1.
    IL_0009:  call       void SwapFunc::Swap(int32&,  'Échange d'appels
                                             int32&)
    IL_000e:non Ne rien faire.
    IL_000f:  ldstr      "x = {0:d}, y = {1:d}\n"  'Adresse au format push
    IL_0014:  ldloc.0  ' V_Appuyez sur une valeur de 0.
    IL_0015:  box        [mscorlib]System.Int32 'Boxer le haut de la pile
    IL_001a:  ldloc.1  ' V_Appuyez sur une valeur de 1.
    IL_001b:  box        [mscorlib]System.Int32 'Boxer le haut de la pile
    IL_0020:  call       void [mscorlib]System.Console::WriteLine(string, ' Console.Appeler WriteLine
                                                                  object,
                                                                  object)
    IL_0025:  nop  'ne fais rien.
    IL_0026:  ret  'Retourner.
  } // end of method SwapFunc::Main

  .method private hidebysig static void  Swap(int32& x,
                                              int32& y) cil managed
  {
    //Taille de code 12(0xc)
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.0  'Poussez l'argument 0.
    IL_0002:  ldind.i4  'Poussez le contenu de la destination avec le haut de la pile comme adresse.
    IL_0003:  stloc.0  'Stocké dans la variable locale 0.
    IL_0004:  ldarg.0  'Poussez l'argument 0.
    IL_0005:  ldarg.1  'Pousser l'argument 1.
    IL_0006:  ldind.i4  'Poussez le contenu de la destination avec le haut de la pile comme adresse.
    IL_0007:  stind.i4  'Stocker à la destination avec le haut de la pile comme adresse.
    IL_0008:  ldarg.1 'Pousser l'argument 1.
    IL_0009:  ldloc.0 'Appuyez sur la variable locale 0.
    IL_000a:  stind.i4 'Poussez le contenu de la destination.
    IL_000b:  ret
  } // end of method SwapFunc::Swap

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    //Taille de code 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


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

// ***********L'assemblage inversé est terminé***********************
//avertissement:Fichier de ressources Win32 C:\workspace\dotNET\IL\Swap\Swap.J'ai créé res.

Ensuite, regardons un exemple de langage C.

Pour le langage C, les VM (machines virtuelles) ne sont pas utilisées, c'est-à-dire qu'elles génèrent du code natif soudainement.

Cette section décrit les x86 et x64 utilisés dans les PC. Chez RISC, IBM, etc., l'idée de base devrait être la même, mais elles sont différentes parce que chaque convention d'appel est différente.

Tout d'abord, la source C, qui ressemble à celle ci-dessous.

#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;
}

Le résultat de la compilation est le suivant. (Pour 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) #Enregistrer 0 à l'adresse locale du cadre de pile(x)
	movl	$1, -12(%ebp) #Enregistrer 1 à l'adresse locale du cadre de pile(y)
	leal	-12(%ebp), %eax #Chargez l'adresse de y dans EAX
	movl	%eax, 4(%esp) #Chargez EAX sur la pile.
	leal	-8(%ebp), %eax #Chargez l'adresse de x dans EAX
	movl	%eax, (%esp) #Chargez EAX sur la pile.
	call	swap #Appelez la fonction d'échange.
	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 #Paramètres chargés ultérieurement(x)Chargé dans EAX
	movl	(%eax), %eax #Chargez les données de la destination d'EAX dans EAX
	movl	%eax, -4(%ebp) #Enregistrez EAX dans les variables locales.
	movl	12(%ebp), %eax #Premiers paramètres chargés(y)Chargé dans EAX
	movl	(%eax), %edx #Chargez les données de la destination d'EAX dans EDX
	movl	8(%ebp), %eax #Paramètres chargés ultérieurement(x)Chargé dans EAX
	movl	%edx, (%eax) #Enregistrez EDX là où EAX est allé
	movl	12(%ebp), %edx #Premiers paramètres chargés(y)Chargé dans EDX
	movl	-4(%ebp), %eax #Charger le contenu des variables locales dans EAX
	movl	%eax, (%edx) #Enregistrez EAX là où EDX est allé.
	leave
	ret
	.size	swap, .-swap
	.ident	"GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-54)"
	.section	.note.GNU-stack,"",@progbits

Pour x64, les conventions d'appel de fonction sont différentes, et les entiers et les nombres à virgule flottante sont passés à travers les registres sans optimisation.

En effet, x64 a augmenté le nombre de registres à usage général de 8 à 16, et les registres excédentaires sont utilisés comme paramètres de fonction.

	.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 contient l'adresse de y.
	leaq	-8(%rbp), %rax #RAX contient l'adresse de x.
	movq	%rdx, %rsi #Les paramètres sont passés dans le registre RSI en fonction de la convention d'appel.
	movq	%rax, %rdi #Les paramètres sont transmis au registre RDI en fonction de la convention d'appel.
	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

Pour les langages qui ne peuvent pas être passés par référence, le passage par référence est utilisé simplement parce qu'il n'y a pas d'expression de référence dans la spécification de langage.

Lorsque les paramètres sont passés par valeur, les valeurs peuvent être chargées dans des registres à usage général, en particulier des entiers et des nombres à virgule flottante qui peuvent être passés par valeur et les valeurs ne peuvent pas être chargées dans des registres à usage général, en particulier des tableaux et des L'objet est passé par référence.

Par conséquent, le type passé par référence peut être utilisé pour réaliser la même fonction que la fonction swap.

Le code suivant est un exemple d'utilisation de JavaScript pour obtenir les mêmes fonctionnalités que le passage par référence. Puisque le tableau est passé par référence, la fonction swap échange le contenu des deux paramètres.

'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

Que signifie passer des paramètres de fonction par référence?
[Java] Le mot passant par référence est mauvais!