What does passing function parameters by reference mean?

What does passing function parameters by reference mean?

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

Recommended Posts

What does passing function parameters by reference mean?
[Java] The word passing by reference is bad!