[インデックス 16515] ファイルの概要
このコミットは、Go言語のcmd/cgo
ツールにおける、packed struct(パックド構造体)の扱いに関するバグ修正です。具体的には、GCCの特定のバグ(PR52991)を回避するために、__gcc_struct__
属性を__packed__
属性と併用するように変更しています。これにより、GoとCの間のデータ構造のレイアウトの不一致によって引き起こされる問題が解決されます。
コミット
commit 949228a322e4ccac49abb620a9e33fe3dfde66e6
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sun Jun 9 22:06:29 2013 +0800
cmd/cgo: use gcc_struct attribute for packed structs to work around GCC PR52991.
Fixes #5603.
R=iant, dave
CC=gobot, golang-dev
https://golang.org/cl/9895043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/949228a322e4ccac49abb620a9e33fe3dfde66e6
元コミット内容
このコミットは、cmd/cgo
が生成するCコードにおいて、__packed__
属性を持つ構造体に対して、GCCのバグ(PR52991)を回避するために__gcc_struct__
属性も追加するように変更します。これにより、Goの構造体とCの構造体間でメモリレイアウトの不一致が発生する問題(Go issue #5603)を修正します。
変更の背景
Go言語のcgo
ツールは、GoプログラムからC言語のコードを呼び出すためのメカニズムを提供します。この際、Goの構造体とCの構造体の間でデータをやり取りする場合、両者のメモリレイアウトが一致している必要があります。しかし、コンパイラ(この場合はGCC)の最適化やアライメントのルールによって、GoとCで同じ定義の構造体でもメモリレイアウトが異なる場合があります。
特に問題となったのは、__packed__
属性を持つ構造体でした。__packed__
属性は、構造体のメンバ間にパディング(埋め草)を入れないようにコンパイラに指示するもので、メモリを節約したり、特定のハードウェアインターフェースとの互換性を保つために使用されます。
Go issue #5603は、cgo
が生成するCコードにおいて、__packed__
属性を持つ構造体がGCCによって正しく扱われないという問題が報告されました。これは、GCCのバグであるPR52991に起因していました。PR52991は、GCCが特定の条件下で__packed__
属性を持つ構造体のアライメントを誤って計算するというバグです。このバグにより、Go側で期待される構造体レイアウトと、GCCがコンパイルしたCコード側での構造体レイアウトが食い違い、データ破損やクラッシュの原因となっていました。
このコミットは、この問題を解決するために、__packed__
属性に加えて__gcc_struct__
属性を併用することで、GCCに構造体のアライメントを正しく処理させるように強制します。
前提知識の解説
cgo
cgo
はGo言語に組み込まれたツールで、GoプログラムからC言語の関数を呼び出したり、C言語のコード内でGoの関数を呼び出したりすることを可能にします。GoとCの相互運用性を実現するために、cgo
はGoのソースコード内の特別なコメント(import "C"
ブロック)を解析し、GoとCの間のブリッジコードを生成します。このブリッジコードは、Goの型とCの型を変換し、関数呼び出しを仲介します。
構造体のアライメントとパディング
コンピュータのメモリは、通常、バイト単位でアドレス指定されますが、CPUは特定のバイト境界(例えば、4バイトや8バイト)に配置されたデータに効率的にアクセスできます。この境界を「アライメント(alignment)」と呼びます。
構造体のメンバは、通常、その型のアライメント要件に従ってメモリに配置されます。例えば、4バイトの整数型は4バイト境界に、8バイトの長整数型は8バイト境界に配置されるのが一般的です。このアライメント要件を満たすために、コンパイラは構造体のメンバ間に「パディング(padding)」と呼ばれる未使用のバイトを挿入することがあります。
例:
struct MyStruct {
char c; // 1バイト
int i; // 4バイト (通常4バイト境界にアライメントされる)
};
この構造体では、char c
の後に3バイトのパディングが挿入され、int i
が4バイト境界に配置されることがあります。これにより、構造体全体のサイズは1 + 3 + 4 = 8
バイトになる可能性があります。
__packed__
属性
__packed__
属性は、GCCなどのコンパイラ拡張機能で提供される属性で、構造体のメンバ間にパディングを挿入しないようにコンパイラに指示します。これにより、構造体は可能な限りコンパクトにメモリに配置されます。
例:
struct MyPackedStruct {
char c;
int i;
} __attribute__((__packed__));
この場合、char c
の後にパディングは挿入されず、int i
はchar c
の直後に配置されます。構造体全体のサイズは1 + 4 = 5
バイトになります。
__packed__
属性は、メモリ効率が重要な場合や、特定のデータフォーマット(例えば、ネットワークプロトコルのヘッダやハードウェアレジスタの構造)に厳密に合わせる必要がある場合によく使用されます。
__gcc_struct__
属性
__gcc_struct__
属性は、GCCの内部的な動作に関連する属性で、特定の構造体のアライメントとパディングの計算方法をGCCのデフォルトのルールに従わせるために使用されます。これは、特に他のコンパイラ拡張(例: __packed__
)と組み合わせて使用される場合に、GCCが予期しない動作をしないようにするために役立ちます。
GCC PR52991
GCC PR52991は、GCCのバグトラッカーに登録された問題で、特定の条件下で__packed__
属性を持つ構造体のアライメントが正しく計算されないというものです。このバグは、特にx86アーキテクチャ(386およびamd64)で顕著でした。このバグにより、__packed__
属性が意図した通りに機能せず、構造体のメモリレイアウトが期待と異なる結果となり、GoとCの間のデータ交換で問題を引き起こしました。
Go issue #5603
Go issue #5603は、Goのバグトラッカーに登録された問題で、cgo
を使用してCの関数を呼び出す際に、__packed__
属性を持つ構造体の引数が正しく渡されないというものです。この問題は、GCC PR52991に起因しており、Go側で定義された構造体とC側でコンパイルされた構造体のメモリレイアウトが一致しないために発生しました。
技術的詳細
このコミットの核心は、cmd/cgo/out.go
ファイルにおける変更です。cgo
は、GoのコードからCの関数を呼び出すためのラッパー関数を生成する際に、Goの構造体をCの構造体として表現します。この際、Goの構造体がpackedである場合、生成されるCの構造体にも__packed__
属性を付与します。
しかし、GCC PR52991のバグにより、x86およびamd64アーキテクチャにおいて、__packed__
属性だけではGCCが構造体のアライメントを正しく処理しないことが判明しました。このため、cgo
は、これらのアーキテクチャ向けにCコードを生成する際に、__packed__
属性に加えて__gcc_struct__
属性も追加するように変更されました。
具体的には、out.go
のwriteOutputFunc
関数内で、生成されるCの構造体ポインタの型定義に属性を追加する部分が変更されています。
変更前:
fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__)) *a = v;\n", ctype)
変更後:
extraAttr := ""
if goarch == "amd64" || goarch == "386" {
extraAttr = ", __gcc_struct__"
}
fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__%v)) *a = v;\n", ctype, extraAttr)
この変更により、amd64
または386
アーキテクチャの場合にのみ、__gcc_struct__
属性が追加されます。これにより、GCCは__packed__
属性を持つ構造体のアライメントを正しく計算し、GoとCの間の構造体レイアウトの不一致が解消されます。
また、この修正を検証するために、misc/cgo/test/issue5603.go
という新しいテストファイルが追加されました。このテストは、様々な引数の数のC関数を呼び出し、__packed__
属性を持つ構造体が正しく扱われることを確認します。
コアとなるコードの変更箇所
misc/cgo/test/cgo_test.go
Test5603
関数が追加され、新しいテストケースtest5603
が実行されるようになりました。
misc/cgo/test/issue5603.go
(新規ファイル)
- Go issue #5603を再現し、修正を検証するための新しいテストファイルです。
- Cのコードブロックで、
issue5603exp
というlong long
型の定数と、引数の数が異なる複数のissue5603fooN
関数(void *
型の引数を取る)が定義されています。これらの関数はすべてissue5603exp
を返します。 - Goの
test5603
関数では、これらのC関数を呼び出し、返された値が期待される値(issue5603exp
)と一致するかどうかを検証します。これにより、cgo
がCの関数呼び出しにおいて、特にポインタ引数を含む場合に、データが正しく渡され、返されることを確認します。
src/cmd/cgo/doc.go
__packed__
属性を持つ構造体の例に、__gcc_struct__
属性が追加された例が示されています。これは、cgo
が生成するコードの変更を反映したドキュメントの更新です。
変更前:
} __attribute__((__packed__)) *a = v;
変更後:
} __attribute__((__packed__, __gcc_struct__)) *a = v;
src/cmd/cgo/out.go
writeOutputFunc
関数内で、生成されるCの構造体ポインタの型定義に属性を追加するロジックが変更されました。goarch
がamd64
または386
の場合にのみ、__gcc_struct__
属性が__packed__
属性に追加されるようになりました。
変更前:
fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__)) *a = v;\n", ctype)
変更後:
// Use __gcc_struct__ to work around http://gcc.gnu.org/PR52991 on x86,
// and http://golang.org/issue/5603.
extraAttr := ""
if goarch == "amd64" || goarch == "386" {
extraAttr = ", __gcc_struct__"
}
fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__%v)) *a = v;\n", ctype, extraAttr)
コアとなるコードの解説
このコミットの最も重要な変更は、src/cmd/cgo/out.go
ファイルにあります。
out.go
は、cgo
ツールがGoのソースコードを解析し、Cのコードを生成する際の出力ロジックを扱います。特に、writeOutputFunc
関数は、Goの関数がCの関数を呼び出すためのラッパー関数をC言語で生成する役割を担っています。
Goの構造体がCの関数に引数として渡される場合、cgo
はGoの構造体をCの構造体として表現し、そのメモリレイアウトがCコンパイラによって正しく解釈されるようにする必要があります。Goの構造体が__packed__
属性を持つべきであると判断された場合、cgo
は生成するCの構造体にもこの属性を付与します。
しかし、前述のGCC PR52991のバグにより、x86およびamd64アーキテクチャでは、__packed__
属性だけではGCCが構造体のアライメントを誤って計算してしまう問題がありました。このため、このコミットでは、goarch
(Goのターゲットアーキテクチャ)がamd64
または386
である場合に限り、__packed__
属性に加えて__gcc_struct__
属性も追加するようにしました。
__gcc_struct__
属性は、GCCに対して、その構造体のアライメントとパディングの計算を、GCCの標準的な構造体ルールに従って行うように強制します。これにより、__packed__
属性と__gcc_struct__
属性を併用することで、GCCはバグの影響を受けずに、意図した通りのコンパクトなメモリレイアウトを持つ構造体を生成できるようになります。結果として、GoとCの間の構造体レイアウトの不一致が解消され、cgo
を介したデータ交換が正しく行われるようになります。
この修正は、特定のコンパイラのバグに対するワークアラウンドであり、Goのクロスコンパイルの性質上、ターゲットアーキテクチャに依存する条件分岐(if goarch == "amd64" || goarch == "386"
)が導入されています。これは、問題が特定のアーキテクチャのGCCに限定されていたためです。
関連リンク
- Go issue #5603: https://golang.org/issue/5603
- GCC PR52991: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991
- Go CL 9895043: https://golang.org/cl/9895043
参考にした情報源リンク
- Go issue #5603の議論
- GCC PR52991のバグ報告と議論
- GCCの
__attribute__
に関するドキュメント cgo
の公式ドキュメント- 構造体のアライメントとパディングに関する一般的な情報
- https://golang.org/cl/9895043 (Goのコードレビューシステムにおけるこのコミットの変更履歴)
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 (GCCのバグトラッカーにおけるPR52991の詳細)
- https://golang.org/issue/5603 (GoのイシュートラッカーにおけるIssue 5603の詳細)
- https://pkg.go.dev/cmd/cgo (Go cgoコマンドの公式ドキュメント)
- https://gcc.gnu.org/onlinedocs/gcc/Structure-Attributes.html (GCCの構造体属性に関するドキュメント)
- https://en.wikipedia.org/wiki/Data_structure_alignment (データ構造のアライメントに関するWikipedia記事)