はじめに
@akif999 さんの書かれた記事『C から Go へコードを移植してハマった話 (そして言語仕様へ)』の中で
「あ、C のこの
a << 8
は、int に拡張されるんやった」
「C のプログラムがうまくいくのは、整数の演算は暗黙に int へ拡張されるからや…」「でも、ナウい Go にはそんな暗黙の動作なんてないんや…」
main.cc |= (unsigned short)(a << 8); c |= (unsigned short)(b << 0);
そうです。
C 言語は、言語が誕生した時代背景や、
言語処理系が CPU へ依存した規格となっていることのせいで、
「未定義動作」や、暗黙の型キャスト、整数の拡張などを頻繁に行います。
との記述があり、「へえ、いまどきのナウい言語は C と違って暗黙の整数拡張を行わないのか」と思ったので検証してみることにしました。
方法
静的型付けを行い、サイズの異なる整数型を持ち、そこそこ使われてる雰囲気の言語/処理系に元記事のプログラムを移植して実行し確認してみました。本来的には言語仕様の書かれたドキュメントを参照すべきですが、面倒なので今回は行っておりません。
整数拡張を行う言語
##Java
class Hogera
{
public static void main(String[] args)
{
byte a = 0x12;
byte b = 0x34;
short c = 0x0000;
c |= a << 8;
c |= b << 0;
System.out.println(String.format("c is 0x%x", c));
}
}
- Cと変わらん
##D
import std.stdio;
void main()
{
ubyte a = 0x12;
ubyte b = 0x34;
ushort c = 0x0000;
c |= a << 8;
c |= b << 0;
writefln("c is 0x%x", c);
}
- Cと変わらん
##C#
using System;
public class Hogera
{
public static void Main()
{
byte a = 0x12;
byte b = 0x34;
ushort c = 0x0000;
c |= (ushort)(a << 8);
c |= (ushort)(b << 0);
Console.WriteLine("c is 0x" + c.ToString("x"));
}
}
- 暗黙の整数拡張は行われる
- サイズの異なる整数型のオブジェクトの代入は型変換が必要
program Hogera(output);
uses sysutils;
var
a: byte = $12;
b: byte = $34;
c: word = $0000;
begin
c := c or a shl 8;
c := c or b shl 0;
writeln('c is $', format('%x', [c]))
end.
- Cと変わらん
##Scala
object Hogera {
def main(args: Array[String]): Unit = {
var a: Byte = 0x12
var b: Byte = 0x34
var c: Short = 0x0000
c = (c | a << 8).asInstanceOf[Short]
c = (c | b << 0).asInstanceOf[Short]
println("c is 0x" + c.toHexString)
}
}
- 暗黙の整数拡張は行われる
- 異なる整数型のオブジェクトの代入は型変換が必要
##Groovy
Byte a = 0x12
Byte b = 0x34
Short c = 0x0000
c |= a << 8
c |= b << 0
println "c is 0x" + Integer.toHexString(c)
- Cと変わらん
#!/usr/bin/perl6
my uint8 $a = 0x12;
my uint8 $b = 0x34;
my uint16 $c = 0x0000;
$c = $c +| $a +< 8;
$c = $c +| $b +< 0;
say $c.fmt('$c is 0x%x');
- Cと変わらん
- 動的型付けも静的型付けもできる謎言語(Groovyもそう)
- 演算子の種類が多杉てキモいな
整数拡張を行わない言語
##Swift
var a:UInt8 = 0x12
var b:UInt8 = 0x34
var c:UInt16 = 0x0000
c |= UInt16(a) << 8
c |= UInt16(b) << 0
print("c is 0x" + String(c, radix:16))
- 暗黙の整数拡張を行わないので型変換が必要
##Rust
fn main() {
let mut a:u8 = 0x12;
let mut b:u8 = 0x34;
let mut c:u16 = 0x0000;
c |= (a as u16) << 8;
c |= (b as u16) << 0;
println!("c is 0x{:x}", c);
}
- 暗黙の整数拡張を行わないので型変換が必要
- mut を付けて宣言されたオブジェクトに代入がされないと警告が出るので驚いた
##Nim
import strutils
var
a: uint8 = 0x12
b: uint8 = 0x34
c: uint16 = 0x0000
c = c or uint16(a) shl 8
c = c or uint16(b) shl 0
echo "c is 0x", toHex(c)
- 暗黙の整数拡張を行わないので型変換が必要
##Crystal
a = 0x12u8
b = 0x34u8
c = 0x0000u16
c = c | a.to_u16 << 8
c = c | b.to_u16 << 0
puts "c is 0x" + c.to_s(16)
- 暗黙の整数拡張を行わないので型変換が必要
##Kotlin
import java.util.*
fun main(args: Array<String>) {
var a: Byte = 0x12
var b: Byte = 0x34
var c: Short = 0x0000
c = (c.toInt() or a.toInt() shl 8).toShort()
c = (c.toInt() or b.toInt() shl 0).toShort()
println("c is 0x" + c.toString(16))
}
- 暗黙の整数拡張を行わないので型変換が必要
- Shortではシフト演算もビット和も許されないようなので一旦Intに変換した
- 異なる整数型のオブジェクトの代入は型変換が必要
##Ada
with Ada.Text_IO, Interfaces;
use Ada.Text_IO, Interfaces;
procedure test is
package Unsigned_16_IO is new Ada.Text_Io.Modular_IO(Unsigned_16);
a: Unsigned_8 := 16#12#;
b: Unsigned_8 := 16#34#;
c: Unsigned_16 := 16#0000#;
begin
c := c or Shift_Left(Interfaces.Unsigned_16(a), 8);
c := c or Shift_Left(Interfaces.Unsigned_16(b), 0);
Put("c is ");
Unsigned_16_IO.Put(Item => c, Base => 16);
New_Line;
end test;
- 暗黙の整数拡張を行わないので型変換が必要
- 異なる整数型のオブジェクトの代入や演算は型変換が必要
- 古いデザインにもかかわらず安全性を重視してデザインされた言語であることがわかる
- 書式は少々古めかしい印象
- 16進数の書き方が特に煩い
Imports System
Public Class Hogera
Public Shared Sub Main()
Dim a As Byte = &H12
Dim b As Byte = &H34
Dim c As UShort = &H0000
c = c Or CType(a, UShort) << 8
c = c Or CType(b, UShort) << 0
System.Console.WriteLine("c is &H" + Hex$(c))
End Sub
End Class
- 異なる整数型のオブジェクトの代入や演算に型変換は不要
- 暗黙の整数拡張を行わず、Byte 型の変数のシフト幅は Byte 型のデータ幅である 8 の剰余が適用されるという誰得な仕様の為に型変換が必要
- 一貫したポリシーがあるわけでもなく醜く拡張され続けた言語デザインにセンスを感じない
##Fortran
PROGRAM HOGERA
INTEGER(1) A
INTEGER(1) B
INTEGER(2) C
DATA A,B,C/Z'12',Z'34',Z'0000'/
C=INT(IOR(INT(C),ISHFT(INT(A),8)),2)
C=INT(IOR(INT(C),ISHFT(INT(B),0)),2)
WRITE (*,'("C IS Z''",Z4,"''")') C
STOP
END
- 暗黙の整数拡張を行わないので型変換が必要
- シフト演算やビット和は小さい整数には許されないようなので一旦INTEGERに変換した
まとめ
以上の言語と C と Go を登場年代順で表にまとめたものが以下です。
言語名 | 登場年 | 暗黙の整数拡張 |
---|---|---|
Fortran | 1957 | × |
C | 1972 | 〇 |
Ada | 1980 | × |
Java | 1995 | 〇 |
Free Pascal | 1997 | 〇 |
C# | 2000 | 〇 |
D | 2001 | 〇 |
Visual Basic .NET | 2001 | × |
Groovy | 2003 | 〇 |
Scala | 2004 | 〇 |
Nim | 2008 | × |
Go | 2009 | × |
Rust | 2010 | × |
Kotlin | 2011 | × |
Crystal | 2014 | × |
Swift | 2014 | × |
Raku | 2015 | 〇 |
(ここ 10年くらいの間に登場した)ナウい言語では暗黙の整数拡張は行わないことは概ね事実のようです。
おわりに
おわりです。