Go言語入門:言語仕様 -Vol.86-

スポンサーリンク
Go言語入門:言語仕様 -Vol.86- 用語解説
Go言語入門:言語仕様 -Vol.86-
この記事は約7分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

整数演算子(Integer operators)

2つの整数値 xy に対して、整数の商 q = x / y と剰余 r = x % y は以下の関係を満たす:

x = q*y + r  かつ  |r| < |y|

ここで x / y はゼロに向かって切り捨てられる(「切り捨て除算」)。

 x     y     x / y     x % y
 5     3       1         2
-5     3      -1        -2
 5    -3      -1         2
-5    -3       1        -2

この規則の唯一の例外として、被除数 x がその x の int 型の最小の負の値である場合、2の補数の整数オーバーフローにより、商 q = x / -1x に等しくなる(かつ r = 0):

                         x, q
int8                     -128
int16                  -32768
int32             -2147483648
int64    -9223372036854775808

除数が定数の場合、ゼロであってはならない。除数が実行時にゼロである場合、実行時パニックが発生する。被除数が非負で除数が定数の2の冪である場合、除算は右シフトで、剰余の計算はビットAND操作で置き換えることができる:

 x     x / 4     x % 4     x >> 2     x & 3
 11      2         3         2          3
-11     -2        -3        -3          1

シフト演算子は、右オペランドで指定されるシフト量だけ左オペランドをシフトする。シフト量は非負でなければならない。シフト量が実行時に負である場合、実行時パニックが発生する。シフト演算子は、左オペランドが符号付き整数の場合は算術シフトを、符号なし整数の場合は論理シフトを実装する。シフト量に上限はない。シフトは、シフト量 n に対して左オペランドを1ずつ n 回シフトしたかのように動作する。その結果、x << 1x*2 と同じであり、x >> 1x/2 と同じであるが負の無限大に向かって切り捨てられる。

整数オペランドに対して、単項演算子 +-^ は以下のように定義される:

+x                          0 + x と同じ
-x    符号反転              0 - x と同じ
^x    ビット反転            m ^ x と同じ。符号なし x の場合 m = "すべてのビットが1"
                                          符号付き x の場合 m = -1

解説

整数の除算と剰余の関係

整数の除算で最も大事なのは、x = q*y + r という関係が常に成り立つことです。これを知っておくと、負の数の除算で迷わなくなります。

// 正 ÷ 正:直感どおり
5 / 3   // 1
5 % 3   // 2    検算: 1*3 + 2 = 5 ✅

// 負 ÷ 正:商はゼロに向かって切り捨て
-5 / 3  // -1   (-1.67 をゼロに向かって切り捨て → -1)
-5 % 3  // -2   検算: (-1)*3 + (-2) = -5 ✅

ポイントは「ゼロに向かって切り捨て」です。-5 / 3 は数学的には -1.67 ですが、ゼロに向かって切り捨てるので -1 になります。

剰余の符号は常に被除数(x)の符号と同じになります。

 5 % 3   //  2(x が正 → 剰余も正)
-5 % 3   // -2(x が負 → 剰余も負)
 5 % -3  //  2(x が正 → 剰余も正)
-5 % -3  // -2(x が負 → 剰余も負)

ゼロ除算

除数がゼロのときの挙動は、定数か実行時かで異なります。

x := 10 / 0    // コンパイルエラー! 定数ゼロによる除算

y := 0
x := 10 / y    // 実行時パニック!

定数ゼロはコンパイル時にエラーとして検出してくれますが、変数がゼロになる場合は実行時にしかわかりません。ユーザー入力を除数に使うときなどは、必ず事前にゼロチェックをしましょう。

if divisor != 0 {
    result = x / divisor
}

最小値 ÷ (-1) のオーバーフロー

これは少しマニアックですが知っておくと安心です。

符号付き整数は2の補数で表現されるため、負の最小値の絶対値は正の最大値より1大きいです。

// int8 の場合:範囲は -128 〜 127
var x int8 = -128
q := x / -1   // 数学的には 128 だが、int8 では表現できない!
// 結果: q == -128(オーバーフローして元の値に戻る)

128 は int8 の最大値 127 を超えるので、オーバーフローが起きて -128 に戻ります。Go ではこれはパニックにならず、黙って折り返します。非常にまれなケースですが、暗号処理やセキュリティ関連のコードでは注意が必要です。

2の冪による除算の最適化

除数が2の冪(2, 4, 8, 16, …)の定数の場合、コンパイラは除算をシフトに、剰余をビットANDに最適化できます。

x / 4    →    x >> 2     // 2ビット右シフト
x % 4    →    x & 3      // 下位2ビットを取り出す(3 = 0b11)

ただし、これが正しいのは x が非負のときだけです。原文の表を見てみましょう。

 x     x / 4     x % 4     x >> 2     x & 3
 11      2         3         2          3      ← 一致 ✅
-11     -2        -3        -3          1      ← 不一致 ❌

x = -11 のとき、-11 / 4 = -2 ですが (-11) >> 2 = -3 です。これは除算が「ゼロに向かう切り捨て」なのに対し、シフトが「負の無限大に向かう切り捨て」だからです。コンパイラはこの違いを理解して正しく最適化してくれるので、プログラマが手動でシフトに書き換える必要はありません。

算術シフトと論理シフト

右シフト >> は、左オペランドの符号によって動作が変わります。

符号付き整数:算術シフト(符号ビットが維持される)

var x int8 = -8    // 0b11111000
x >> 1              // 0b11111100 = -4(符号ビットの1が左から補填される)

符号なし整数:論理シフト(0が補填される)

var x uint8 = 248   // 0b11111000
x >> 1               // 0b01111100 = 124(0が左から補填される)

算術シフトでは x >> 1 は「2で割って負の無限大に向かって切り捨て」と等価です。

var x int = -7
x >> 1    // -4   (-7/2 = -3.5 → 負の無限大方向に切り捨て → -4)
x / 2     // -3   (ゼロ方向に切り捨て)

この違いも、除算とシフトを手動で置き換えるときの落とし穴ですね。

単項演算子 ^(ビット反転)

Go のビット反転は ^ です(C言語の ~ に相当)。

var x uint8 = 0b00001111
y := ^x  // 0b11110000 = 240

符号付き整数の場合は -1 ^ x と定義されています。-1 は2の補数ですべてのビットが1なので、XORするとすべてのビットが反転します。

var x int8 = 5    // 0b00000101
y := ^x            // -6 = 0b11111010
// -1 ^ 5 = 0b11111111 ^ 0b00000101 = 0b11111010 = -6

Go では ~ は型制約の基底型を表す演算子として使われているので、ビット反転には ^ が使われています。C 言語から来た方は注意してください。

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました