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);
–