Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13422] ファイルの概要

このコミットは、Go言語のCgoツールにおいて、Goのスライス型をC言語側で適切に表現するための GoSlice 型定義を生成するように変更を加えたものです。これにより、GoのスライスをC言語から呼び出されるGo関数(//export された関数)の引数や戻り値として安全に利用できるようになり、GoとC言語間のスライスデータの受け渡しに関するバグ(Issue 3741)が修正されました。

コミット

  • コミットハッシュ: 33d2b495c5656b060d835bd395a5c736bd7f1e6a
  • 作者: Shenghou Ma minux.ma@gmail.com
  • コミット日時: 2012年6月30日 土曜日 12:40:07 +0800
  • コミットメッセージ:
    cmd/cgo: generate definitions for GoSlice
            Fixes #3741.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6308076
    

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/33d2b495c5656b060d835bd395a5c736bd7f1e6a

元コミット内容

cmd/cgo: generate definitions for GoSlice Fixes #3741.

このコミットは、CgoツールがGoのスライス型のための定義を生成するように修正し、Issue 3741を解決したことを示しています。

変更の背景

Go言語とC言語を連携させるCgoツールにおいて、Goのスライス型をC言語側で扱う際に問題が発生していました。具体的には、Goのスライスを//exportディレクティブを使ってC言語から呼び出せるGo関数の引数や戻り値として使用した場合、Cgoが生成するCヘッダファイルにGoのスライスに対応する適切なC言語の型定義が存在しなかったため、コンパイルエラーや実行時エラーが発生する可能性がありました。

Issue 3741は、このスライスのCgo連携に関するバグを報告していたものと考えられます。Goのスライスは内部的にデータポインタ、長さ(len)、容量(cap)の3つの要素で構成されており、C言語側でこれを正しく解釈するためには、これらの要素を保持できる構造体として定義する必要がありました。このコミットは、そのための GoSlice 型定義をCgoが自動的に生成するようにすることで、この問題を解決することを目的としています。

前提知識の解説

Go言語のスライス

Go言語のスライスは、配列をラップした動的なデータ構造です。内部的には以下の3つの要素で構成されています。

  1. データポインタ (Data Pointer): スライスが参照する基底配列の先頭要素へのポインタ。
  2. 長さ (Length): スライスに含まれる要素の数。len() 関数で取得できます。
  3. 容量 (Capacity): スライスの基底配列が保持できる最大要素数。cap() 関数で取得できます。スライスを拡張する際に、この容量を超えると新しい基底配列が割り当てられます。

スライスはGo言語で非常に頻繁に利用されるデータ型であり、C言語との相互運用においてもその正確な表現が求められます。

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoのツールです。Cgoを使用することで、既存のCライブラリをGoから利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。

Cgoの主な機能と概念は以下の通りです。

  • import "C": Goのソースファイル内で import "C" を記述することで、そのファイル内でC言語のコードを記述したり、Cの関数や型を参照したりできるようになります。
  • C言語コードの記述: import "C" の直前のコメントブロックにC言語のコードを記述できます。
  • //export ディレクティブ: Goの関数定義の直前に //export FunctionName と記述することで、そのGo関数をC言語から呼び出せるようにCgoがラッパー関数を生成します。
  • 型変換: CgoはGoとCの間の型変換を自動的に行いますが、複雑な型(スライス、マップ、チャネルなど)の場合、C言語側で対応する型定義が必要になることがあります。

型定義の重要性 (特にCgoにおける)

C言語は静的型付け言語であり、関数が受け取る引数や返す値の型が厳密に定義されている必要があります。Go言語の型をC言語から利用する場合、CgoはGoの型に対応するC言語の型定義を生成します。例えば、Goの文字列型 string はC言語側では GoString という構造体(char *p; int n;)として定義されます。これは、Goの文字列が内部的にデータポインタと長さで構成されているためです。

同様に、Goのスライスも内部構造を持つため、C言語側でその構造を正確に表現するための型定義が必要となります。この型定義がなければ、C言語のコンパイラはGoのスライスをどのように扱うべきか理解できず、エラーとなります。

技術的詳細

このコミットの技術的な核心は、Cgoツールが生成するCヘッダファイルに、Goのスライスに対応するC言語の構造体 GoSlice の定義を追加した点にあります。

変更は主に以下の2つのファイルで行われています。

  1. src/cmd/cgo/out.go の変更: このファイルはCgoツールの中核部分であり、GoのコードからC言語のコードを呼び出すためのグルーコード(接着コード)や、C言語からGoの関数を呼び出すためのラッパーコード、そしてGoの型に対応するC言語の型定義などを生成する役割を担っています。 このコミットでは、既存の GoString, GoMap, GoChan, GoInterface といったGoの組み込み型に対応するC言語の typedef 定義のリストに、新たに GoSlice の定義が追加されました。

    typedef struct { void *data; int len; int cap; } GoSlice;
    

    この定義は、Goのスライスが持つ「データポインタ (void *data)」、「長さ (int len)」、「容量 (int cap)」の3つの要素を正確にC言語の構造体として表現しています。void *data は、スライスの基底配列の先頭要素へのポインタであり、任意の型のデータを指すことができるように void* となっています。int lenint cap は、それぞれスライスの長さと容量を整数で表します。

  2. misc/cgo/test/issue3741.go の追加: このファイルは、GoSlice の定義が正しく機能するかを検証するためのテストケースです。//export ディレクティブを使って、Goのスライスを引数として受け取ったり、戻り値として返したりするGo関数が定義されています。

    • exportSliceIn(s []byte) bool: スライスを引数として受け取り、その長さと容量が等しいかをチェックします。これは、スライスが基底配列全体を参照している場合に真となります。
    • exportSliceOut() []byte: スライスを戻り値として返します。ここでは []byte{1} という短いスライスを返しています。
    • exportSliceInOut(s []byte) []byte: スライスを引数として受け取り、そのまま戻り値として返します。これは、スライスの受け渡しが正しく行われるかを検証します。

これらのテストケースは、Cgoが生成するCヘッダファイルに GoSlice の定義が含まれることで、C言語側からこれらのGo関数を呼び出し、スライスの受け渡しが期待通りに行われることを確認するために使用されます。

コアとなるコードの変更箇所

--- a/misc/cgo/test/issue3741.go
+++ b/misc/cgo/test/issue3741.go
@@ -0,0 +1,22 @@
+// 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.
+
+package cgotest
+
+import "C"
+
+//export exportSliceIn
+func exportSliceIn(s []byte) bool {
+	return len(s) == cap(s)
+}
+
+//export exportSliceOut
+func exportSliceOut() []byte {
+	return []byte{1}
+}
+
+//export exportSliceInOut
+func exportSliceInOut(s []byte) []byte {
+	return s
+}
diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go
index 44f9f30680..2ab974c979 100644
--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -958,4 +958,5 @@ typedef struct { char *p; int n; } GoString;
 typedef void *GoMap;
 typedef void *GoChan;
 typedef struct { void *t; void *v; } GoInterface;
+typedef struct { void *data; int len; int cap; } GoSlice;
 `

コアとなるコードの解説

misc/cgo/test/issue3741.go

このファイルは新規追加されたテストファイルです。 package cgotestimport "C" が宣言されており、Cgoを利用するテストであることを示しています。

  • //export exportSliceIn func exportSliceIn(s []byte) bool この関数は、C言語から呼び出されることを意図したGo関数です。[]byte 型のスライス s を引数として受け取ります。関数内では len(s) == cap(s) という条件を評価し、その結果を bool 型で返します。これは、スライスがその基底配列の全体を占めているかどうかをチェックする一般的なGoのイディオムです。Cgoがスライスの長さと容量を正しくC言語側に渡し、Go側で正しく受け取れるかを検証します。

  • //export exportSliceOut func exportSliceOut() []byte この関数もC言語から呼び出されるGo関数です。[]byte 型のスライスを戻り値として返します。ここでは []byte{1} という、長さ1、容量1のバイトスライスを生成して返しています。CgoがGoのスライスをC言語側へ正しく変換して返せるかを検証します。

  • //export exportSliceInOut func exportSliceInOut(s []byte) []byte この関数は、スライスを引数として受け取り、そのスライスをそのまま戻り値として返します。これは、C言語からGoへスライスが渡され、Goで処理された後、再びC言語へスライスが返されるという、より複雑なスライスの受け渡しシナリオを検証します。

これらのテスト関数は、Cgoが生成するCヘッダファイルに GoSlice 型が定義されることで、C言語側でこれらのGo関数を呼び出すためのプロトタイプが正しく生成され、GoとCの間でスライスデータが破損することなくやり取りできることを保証します。

src/cmd/cgo/out.go

このファイルは、CgoがGoの型に対応するC言語の型定義を生成する部分です。 追加された行は以下の通りです。

typedef struct { void *data; int len; int cap; } GoSlice;

これはC言語の typedef 宣言であり、GoSlice という新しい型名を定義しています。この GoSlice は、struct(構造体)であり、以下の3つのメンバーを持ちます。

  • void *data: これはポインタであり、Goのスライスの基底配列の先頭要素を指します。void* とすることで、任意の型のスライス([]byte, []int, []string など)に対応できるように汎用性を持たせています。C言語側では、このポインタを適切な型にキャストして利用することになります。
  • int len: これは整数型であり、Goのスライスの現在の長さ(len() で取得できる値)を格納します。
  • int cap: これは整数型であり、Goのスライスの容量(cap() で取得できる値)を格納します。

この typedef 宣言が src/cmd/cgo/out.go に追加されたことで、CgoがGoのソースコードを処理する際に、//export されたGo関数がスライスを引数や戻り値として持つ場合、生成されるCヘッダファイル(通常は _cgo_export.h のような名前)にこの GoSlice 型定義が自動的に含まれるようになります。これにより、C言語のコンパイラはGoのスライスを正しく認識し、GoとCの間でスライスデータを安全に受け渡すためのコードを生成できるようになります。

この変更は、GoのスライスがC言語との境界を越える際の、低レベルなデータ表現の整合性を保証する上で非常に重要です。

関連リンク

  • Gerrit Change-Id: https://golang.org/cl/6308076
  • Go Issue 3741: このコミットが修正した具体的なIssueへの直接リンクは、GoのIssueトラッカーの古いシステムのため、現在のGitHub Issuesでは見つけにくい場合があります。しかし、コミットメッセージの Fixes #3741 は、CgoにおけるGoスライスの取り扱いに関するバグ報告を指しています。

参考にした情報源リンク