暗黙的なリターンに関する雑記

Rustの勉強中に「暗黙的なリターン」に関して気になったので、複数の言語で同様の実装をした場合にどのような動作をするのか実験してみた。

実験環境

OS : Windows 10
RAM: 32GB
処理系は各章ごとに記述する。

Rust

Rustでは関数内に明示的なreturn文が存在しない場合、「その関数内の最後の式」を暗黙的な返却値とする。例えば、以下のようなコードをコンパイル・実行すると"5"がターミナルに出力される。

fn main(){
    let i =  return5();
    println!("{}", i);  // 5
}

fn return5()->i32{
    5
}

Java

以下のようなTest.javaを作成し、javac 11.0.8でコンパイルした。

public class Test{
    public static void main(String[] args){
        int i = return5();
        System.out.println(i);
    }
    public static int return5(){
        5
    }
}

結果、「セミコロンがない」というコンパイルエラーで弾かれた。
また、5;というようにセミコロンをつけた場合でも「文ではない」というコンパイルエラーが発生した。

Python

次のようなコードを実行した。Python 3.8.5を利用。

def return5():
    5

i = return5()
print(i)    # None

シンタックスエラーとはならず、iにはNoneが格納された。

PHP

php 8.0.0にて、以下のコードを実行した。

<? php
    function return5(){
        5
    }
    $i = return5();
    echo  "i = ".$i;
?>

こちらもシンタックスエラーとなった。なお、セミコロンを付与した場合、ターミナルには何も出力されなかった。

Go

Go 1.17.1にて実行。

package main

import "fmt"

func main(){
    i := return5()
    fmt.Println(i)
}

func return5() int{
    5
}

結果、「5が評価されたのに未使用である」「関数内にreturn文がない」という2つのエラーが発生。

C

ソースコードは以下の通り。GCC 8.3.0でコンパイルした。

#include <stdio.h>

int return5(){ 5; }

int main()
{
    int i = return5();
    printf("%d\n", i); // 1
}

どこにも定義されていない1が出力された。
この現象に関する詳細な解説は以下の記事を参照のこと。

qiita.com

このように非voidで返り値を持たない関数の動作はC言語では定義されておらず、出力されている 1 はアキュムレータレジスタの値であり、これは環境によって異なるとのこと。
試しにMacのclang12.0.0でコンパイルしてみたところ、出力結果は0となった。

追加実験

アキュムレータレジスタの値が返却されているかどうかを確認するためgcc -g3 test.c -o test.exeデバッグ用の実行ファイルを作成し、gdb test.exeでデバッガにかけてみた。
レジスタの値はprintf()関数が実行される行で確認した。

(gdb) info register
rax            0x1                 1
rbx            0x8                 8
rcx            0x1                 1
rdx            0x1c48f0            1853680
(以下略)

また、アキュムレータレジスタで返却値のやり取りが行われているかどうかを確認す流ために、return5()関数の内容をreturn 5;に変更した上で同様にデバッガを走らせると、次のような結果を得た。

(gdb) info register
rax            0x5                 5
rbx            0x8                 8
rcx            0x1                 1
rdx            0x1c48f0            1853680
(以下略)

この結果から、返却値のやり取りがアキュムレータレジスタで行われていることが確認できた。
なお、デバッガ出力のRAXがアキュムレータレジスタに相当する。

おまけ・レジスタの名前に関する話

x86_64アーキテクチャの汎用レジスタはデータのサイズによって以下のように名前が変わる。(ただし、レジスタ自体は1つ。)
以下はアキュムレータ(Accumulator)レジスタの例。

  • AH, AL(A High, A Lowの略?): それぞれAXの上位8bitと下位8bitの領域
  • AX (A eXtended): AHとALを含む16bitの領域
  • EAX (Extended AX): AXを下位16bitとする32bitの領域
  • RAX (Register AXの略?): EAXを下位32bitとする64bitの領域

さいごに

Google先生に聞いたところ、RubyとかSwiftも関数ブロック内の最後の式が暗黙的に関数の返却値になる仕様とのこと。 さらに、LISP言語に端を発する仕様であることを考えると、かなり古くからある言語仕様のようです。