[インデックス 14453] ファイルの概要
このコミットは、Go言語のCgoツールにおけるbool
型のメモリ配置(アライメント)に関するバグ修正です。具体的には、Cgoが生成するコードにおいて、bool
型が誤ったアライメントを持つことで、C関数呼び出し時に引数が正しく渡されない問題(Issue 4417)を解決します。
コミット
commit dd01e9281d681c18f6e70bf032d622603bec6a67
Author: Vladimir Nikishenko <vova616@gmail.com>
Date: Wed Nov 21 13:04:38 2012 -0800
cmd/cgo: fix alignment of bool.
Fixes #4417.
R=golang-dev, iant, minux.ma, bradfitz
CC=golang-dev, vova616
https://golang.org/cl/6782097
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dd01e9281d681c18f6e70bf032d622603bec6a67
元コミット内容
このコミットは、cmd/cgo
ツールにおけるbool
型のアライメント問題を修正します。これにより、GoとCの間でbool
型の値が正しくやり取りされるようになります。この修正は、GoのIssue 4417を解決するものです。
変更の背景
Go言語は、C言語のコードをGoプログラムから呼び出すためのcgo
ツールを提供しています。cgo
は、GoとCの間のデータ型変換や関数呼び出しの橋渡しを行います。このコミットが行われた当時、cgo
がbool
型を扱う際に、そのメモリ上のアライメント(配置)が正しく設定されていませんでした。
具体的には、cgo
がbool
型に対してポインタサイズ(例えば32ビットシステムでは4バイト、64ビットシステムでは8バイト)のアライメントを適用しようとしていました。しかし、C言語の_Bool
型(またはstdbool.h
で定義されるbool
型)は通常1バイトのサイズを持ち、アライメントも1バイトであることが一般的です。この不一致により、GoからC関数を呼び出す際に、bool
型の引数の後に続く引数が期待されるメモリ位置に配置されず、結果としてC関数が誤った値を受け取ってしまうというバグが発生していました(Issue 4417)。
この問題は、特に複数のbool
型引数やbool
型と他の型の引数が混在するC関数をGoから呼び出す場合に顕著に現れ、プログラムの誤動作を引き起こす可能性がありました。
前提知識の解説
Cgo
Cgoは、GoプログラムからC言語の関数を呼び出したり、C言語のコード内でGoの関数を使用したりするためのGoのツールです。GoとCの間の相互運用性を提供し、既存のCライブラリをGoから利用することを可能にします。Cgoは、Goのソースコード内にCのコードを直接記述できる特殊な構文(import "C"
)を提供し、ビルド時にGoとCのコードをリンクします。
メモリのアライメントとパディング
コンピュータのメモリは、バイト単位でアドレス指定されますが、CPUは通常、特定のデータ型を特定のバイト境界(アライメント)に配置することを効率的とします。例えば、4バイトの整数は4の倍数のアドレスに配置されると、CPUは一度のメモリアクセスでその整数全体を読み書きできるため、パフォーマンスが向上します。
- アライメント (Alignment): データがメモリ上で配置される際の、特定のバイト境界への制約です。例えば、「4バイトアライメント」とは、データのアドレスが4の倍数でなければならないことを意味します。
- パディング (Padding): アライメント要件を満たすために、構造体などのメンバ間に挿入される未使用のバイトのことです。例えば、1バイトの
char
と4バイトのint
が続く構造体では、char
の後に3バイトのパディングが挿入され、int
が4バイト境界に配置されるように調整されることがあります。
アライメントが正しくないと、CPUがデータを効率的に読み書きできなかったり、最悪の場合、アライメント違反エラーが発生してプログラムがクラッシュしたりすることがあります。特に、異なる言語(GoとC)間でデータをやり取りする際には、それぞれの言語が持つアライメント規則を正しく理解し、適切に処理することが不可欠です。
dwarf.Type
とcgo
の型変換
Goのコンパイラやツールチェーンは、プログラムのデバッグ情報や型情報を扱うためにDWARF (Debugging With Attributed Record Formats) 形式を利用することがあります。dwarf.Type
は、このDWARF形式で表現される型情報を抽象化したものです。
cgo
ツールは、Goの型とCの型の間で変換を行う際に、これらの型情報(サイズ、アライメントなど)を考慮します。src/cmd/cgo/gcc.go
ファイルは、cgo
がCの型情報を解析し、Goの型システムにマッピングする際のロジックを含んでいます。このファイル内のtypeConv
構造体は、CのDWARF型をGoの内部表現に変換する役割を担っています。
技術的詳細
このバグは、src/cmd/cgo/gcc.go
内のtypeConv
構造体のType
メソッドが、Cのdwarf.BoolType
(Cのbool
型に対応)のアライメントを誤って設定していたことに起因します。
元のコードでは、dwarf.BoolType
のアライメントをc.ptrSize
(ポインタのサイズ、例えば64ビットシステムでは8バイト)に設定していました。これは、Goのbool
型が内部的に1バイトとして扱われることが多いものの、Goのコンパイラが特定の状況でbool
型をワードサイズ(ポインタサイズ)にパディングすることがあるため、その慣習に合わせたものかもしれません。しかし、C言語のbool
型(_Bool
またはstdbool.h
のbool
)は、標準で1バイトのサイズを持ち、アライメントも1バイトです。
GoからC関数を呼び出す際、cgo
はGoの引数をCのABI(Application Binary Interface)に従ってメモリに配置します。もしbool
型がGo側で8バイトアライメントとして扱われ、C側で1バイトアライメントとして期待される場合、引数のメモリレイアウトにずれが生じます。
例えば、C.c_bool(true, true, 10, true, false)
のようなC関数呼び出しを考えます。
Cの関数シグネチャは static int c_bool(bool a, bool b, int c, bool d, bool e)
です。
もしbool
が8バイトアライメントと誤って解釈されると、a
とb
の間に7バイトのパディングが挿入され、b
とc
の間にもパディングが挿入される可能性があります。これにより、c
(整数)が期待される位置に配置されず、C関数がc
として全く異なるメモリ上の値を読み取ってしまうことになります。
このコミットでは、dwarf.BoolType
のアライメントを明示的に1
バイトに設定することで、この問題を解決しています。これにより、cgo
はCのbool
型をCのABIに準拠した1バイトアライメントで扱うようになり、GoとCの間でのbool
型引数の受け渡しが正しく行われるようになります。
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
-
misc/cgo/test/cgo_test.go
:TestBoolAlign(t *testing.T)
という新しいテスト関数が追加され、既存のテストスイートに組み込まれました。
-
misc/cgo/test/issue4417.go
: (新規ファイル)- GoとCの相互運用性をテストするための新しいファイルが追加されました。
stdbool.h
をインクルードし、複数のbool
型引数とint
型引数を持つC関数c_bool
を定義しています。testBoolAlign
関数内で、様々なbool
値の組み合わせでC.c_bool
を呼び出し、int
型引数c
が正しく返されることを検証しています。
-
src/cmd/cgo/gcc.go
:func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type
メソッド内のdwarf.BoolType
を処理する箇所が変更されました。- 変更前:
t.Align = c.ptrSize
- 変更後:
t.Align = 1
コアとなるコードの解説
src/cmd/cgo/gcc.go
の変更
--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -1078,7 +1078,7 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
case *dwarf.BoolType:
t.Go = c.bool
- t.Align = c.ptrSize
+ t.Align = 1
case *dwarf.CharType:
if t.Size != 1 {
この変更がこのコミットの核心です。src/cmd/cgo/gcc.go
は、CgoがCの型情報をGoの型に変換する際のロジックを定義しています。typeConv
構造体のType
メソッドは、DWARF形式で表現されたCの型(dtype
)を受け取り、それをGoの内部表現である*Type
に変換します。
case *dwarf.BoolType:
のブロックは、Cのbool
型を処理する部分です。
t.Go = c.bool
: これは、Go側でこのCのbool
型がどのように表現されるかを示しています。t.Align = c.ptrSize
からt.Align = 1
への変更: ここが修正点です。- 変更前は、
bool
型のアライメントをポインタサイズ(c.ptrSize
)に設定していました。これは、Goの内部的なパディング規則や、一部のアーキテクチャでの最適化を意図したものかもしれませんが、Cのbool
型のアライメント要件とは一致しませんでした。 - 変更後は、
bool
型のアライメントを明示的に1
バイトに設定しています。これにより、Cのbool
型がメモリ上で1バイト境界に配置されることが保証され、CのABIに準拠した正しい引数渡しが可能になります。
- 変更前は、
この修正により、GoとCの間でbool
型の値が正しくやり取りされるようになり、Issue 4417で報告されたような引数のずれによるバグが解消されます。
misc/cgo/test/issue4417.go
のテストコード
// run
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Issue 4417: cmd/cgo: bool alignment/padding issue.
// bool alignment is wrong and causing wrong arguments when calling functions.
//
package cgotest
/*
#include <stdbool.h>
static int c_bool(bool a, bool b, int c, bool d, bool e) {
return c;
}
*/
import "C"
import "testing"
func testBoolAlign(t *testing.T) {
b := C.c_bool(true, true, 10, true, false)
if b != 10 {
t.Fatalf("found %d expected 10\n", b)
}
b = C.c_bool(true, true, 5, true, true)
if b != 5 {
t.Fatalf("found %d expected 5\n", b)
}
b = C.c_bool(true, true, 3, true, false)
if b != 3 {
t.Fatalf("found %d expected 3\n", b)
}
b = C.c_bool(false, false, 1, true, false)
if b != 1 {
t.Fatalf("found %d expected 1\n", b)
}
b = C.c_bool(false, true, 200, true, false)
if b != 200 {
t.Fatalf("found %d expected 200\n", b)
}
}
このテストファイルは、問題の再現と修正の検証のために作成されました。
- Cのコードブロックで、
stdbool.h
をインクルードし、bool
型の引数を複数持つC関数c_bool
を定義しています。この関数は、3番目の引数c
(int
型)の値をそのまま返します。 - Goの
testBoolAlign
関数では、C.c_bool
を様々なbool
値の組み合わせで呼び出しています。 - 重要なのは、
c_bool
関数がbool
引数の間にint
引数c
を挟んでいる点です。もしbool
のアライメントが正しくないと、a
とb
のbool
引数のパディングによって、c
がメモリ上でずれてしまい、c_bool
が期待するint
値(例えば10
)ではなく、別のメモリ上の値を読み取って返してしまうことになります。 - テストでは、
c_bool
が常に正しいint
値を返すことをアサートしています。これにより、bool
型のアライメントが正しく設定され、引数渡しが正常に行われていることを確認できます。
このテストは、修正が正しく機能していることを保証するための重要な役割を果たしています。
関連リンク
- Go Issue 4417: https://code.google.com/p/go/issues/detail?id=4417 (古いGoのIssueトラッカーのリンクですが、このコミットで参照されています)
- Go CL 6782097: https://golang.org/cl/6782097 (このコミットに対応するGoのコードレビューリンク)
参考にした情報源リンク
- Go言語の公式ドキュメント (Cgoに関する情報): https://pkg.go.dev/cmd/cgo
- メモリのアライメントとパディングに関する一般的な情報 (C言語の文脈):
- DWARF形式に関する情報: https://dwarfstd.org/
stdbool.h
に関する情報: https://ja.cppreference.com/w/c/language/bool