In a function call, there are two ways to pass parameters of a function (method): "pass by value" and "pass by reference".
However, Java and JavaScript do not pass by reference as a language specification, they always pass by value.
This is an example of passing by reference, but first I will explain using C # as an example. The following sample uses passing by reference to exchange the values of two parameters.
using System;
public class SwapFunc
{
static void Main(String[] args)
{
int x = 0;
int y = 1;
//Swap x and y values
Swap(ref x, ref y);
Console.WriteLine("x = {0:d}, y = {1:d}\n", x, y);
}
//Methods for exchanging x and y values
static void Swap(ref int x, ref int y)
{
var u = x;
x = y;
y = u;
}
}
The IL (intermediate code) for this is as follows:
I wrote the behavior in the comments, but whether it's accurate or not, I'm using a stack machine (virtual machine) to push and pop data on the stack frame to do the calculations.
If you run it as it is, it will be slow, so it is usually converted to native code and executed.
In Main, the part related to passing by reference is around IL_0005 and IL_0006. Here, the addresses of the data x and y on the stack frame are pushed onto the stack.
The part related to passing by reference on the function Swap side is around using the ldind instruction. This instruction considers the contents of the specified operand as an address, and pushes the data to which the address goes on the stack.
// 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
//Code size 39(0x27)
.maxstack 3
.locals init (int32 V_0,
int32 V_1)
IL_0000: nop
IL_0001: ldc.i4.0 'Push 0.
IL_0002: stloc.0 'Pop 0 and stack frame V_Save to 0
IL_0003: ldc.i4.1 'Push one.
IL_0004: stloc.1 'Pop 1 and stack frame V_Save to 1
IL_0005: ldloca.s ' V_0 V_Push 0 address.
IL_0007: ldloca.s ' V_1 V_Push 1 address.
IL_0009: call void SwapFunc::Swap(int32&, 'Call Swap
int32&)
IL_000e:nop Do nothing.
IL_000f: ldstr "x = {0:d}, y = {1:d}\n" 'Push format address
IL_0014: ldloc.0 ' V_Push a value of 0.
IL_0015: box [mscorlib]System.Int32 'Boxing the top of the stack
IL_001a: ldloc.1 ' V_Push a value of 1.
IL_001b: box [mscorlib]System.Int32 'Boxing the top of the stack
IL_0020: call void [mscorlib]System.Console::WriteLine(string, ' Console.Call WriteLine
object,
object)
IL_0025: nop 'do nothing.
IL_0026: ret 'Go back.
} // end of method SwapFunc::Main
.method private hidebysig static void Swap(int32& x,
int32& y) cil managed
{
//Code size 12(0xc)
.maxstack 2
.locals init (int32 V_0)
IL_0000: nop
IL_0001: ldarg.0 'Push argument 0.
IL_0002: ldind.i4 'Push the contents of the destination with the stack top as the address.
IL_0003: stloc.0 'Stored in local variable 0.
IL_0004: ldarg.0 'Push argument 0.
IL_0005: ldarg.1 'Push argument 1.
IL_0006: ldind.i4 'With the stack top as the address, push the contents of the destination.
IL_0007: stind.i4 'Store to the destination with the stack top as the address.
IL_0008: ldarg.1 'Push argument 1.
IL_0009: ldloc.0 'Push local variable 0.
IL_000a: stind.i4 'Push the contents of the destination.
IL_000b: ret
} // end of method SwapFunc::Swap
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
//Code size 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
// =============================================================
// ***********Disassembled***********************
//warning:Win32 resource file C:\workspace\dotNET\IL\Swap\Swap.I created res.
Next, let's look at an example in C language.
In C language, VM (Virtual Machine) is not used, that is, it generates native code suddenly.
This section describes x86 and x64 used on PCs. RISC, IBM, etc. should have the same basic idea, but they are different because they have different calling conventions.
First, the C source, which looks like this:
#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;
}
The compilation result of this is as follows. (For 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) #Save 0 to the local address of the stack frame(x)
movl $1, -12(%ebp) #Save 1 to the local address of the stack frame(y)
leal -12(%ebp), %eax #Load y's address into EAX
movl %eax, 4(%esp) #Put EAX on the stack.
leal -8(%ebp), %eax #Load x address into EAX
movl %eax, (%esp) #Put EAX on the stack.
call swap #Call the swap function.
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 #Parameters loaded later(x)Loaded into EAX
movl (%eax), %eax #Load the data of the destination of EAX to EAX
movl %eax, -4(%ebp) #Save EAX in a local variable.
movl 12(%ebp), %eax #First loaded parameters(y)Loaded into EAX
movl (%eax), %edx #Load the data of the destination of EAX into EDX
movl 8(%ebp), %eax #Parameters loaded later(x)Loaded into EAX
movl %edx, (%eax) #Save EDX to where EAX went
movl 12(%ebp), %edx #First loaded parameters(y)Loaded into EDX
movl -4(%ebp), %eax #Load the contents of local variables into EAX
movl %eax, (%edx) #Save EAX to where EDX went.
leave
ret
.size swap, .-swap
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-54)"
.section .note.GNU-stack,"",@progbits
For x64, the calling conventions for functions are different, and integers and floating point numbers are passed in registers without optimization.
This is because x64 has increased the number of general-purpose registers from 8 to 16, and the surplus registers are used as function parameters.
.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 contains the address of y.
leaq -8(%rbp), %rax #RAX contains the address of x.
movq %rdx, %rsi #The parameters are passed in the RSI register based on the calling convention.
movq %rax, %rdi #The parameters are passed to the RDI register based on the calling convention.
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
For languages that cannot be passed by reference, passing by reference is used simply because there is no reference expression in the language specification.
When a parameter is passed by value, a value can be loaded into a general-purpose register, specifically an integer or floating-point number, and a value cannot be loaded into a general-purpose register, specifically an array or general. The object is passed by reference.
Therefore, the type passed by reference can be used to achieve the same function as the swap function.
The following code is an example of JavaScript that implements the same functionality as passing by reference. Since the array is passed by reference, the swap function swaps the contents of the two parameters.
'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);
–