[インデックス 14946] ファイルの概要
このコミットは、Go言語のcgo
ツールに関する公式ドキュメント(src/cmd/cgo/doc.go
)の更新です。主な目的は、cgo
を使用してC言語の関数を呼び出す際の2つの重要な側面について、より明確な情報を提供することです。具体的には、C.free
関数を使用する際にstdlib.h
のインクルードが必要であること、およびvoid
を返すC関数からもerrno
変数をエラーとして取得できることを明記しています。これにより、cgo
を利用する開発者がC言語との連携において遭遇しうる一般的な疑問や問題に対するガイダンスを強化しています。
コミット
commit faaa7c07d7351fcd8a10fd1df27272b09d681061
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Jan 22 02:52:34 2013 +0800
cmd/cgo: doc updates
1. note that to use C.free <stdlib.h> must be included
2. can also extract errno from a void C function
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/6935045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/faaa7c07d7351fcd8a10fd1df27272b09d681061
元コミット内容
cmd/cgo: doc updates
1. note that to use C.free <stdlib.h> must be included
2. can also extract errno from a void C function
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/6935045
変更の背景
この変更は、GoプログラムからC言語のコードを呼び出すためのcgo
ツールのドキュメントを改善することを目的としています。cgo
はGoとCの間の相互運用性を提供しますが、C言語のメモリ管理やエラーハンドリングの慣習はGoとは異なるため、開発者が混乱しやすい点が存在します。
-
C.free
とstdlib.h
の関連性:cgo
を通じてC言語の関数を呼び出す際、Goのstring
をCのchar*
に変換するためにC.CString
のようなヘルパー関数が提供されます。この関数はCのヒープにメモリを割り当てるため、使用後はC.free
を使って解放する必要があります。しかし、C言語においてfree
関数は標準ライブラリのstdlib.h
で宣言されています。cgo
の初期のドキュメントでは、このstdlib.h
のインクルードが明示されていなかったため、開発者がC.free
を呼び出そうとした際にコンパイルエラーや未定義の動作に遭遇する可能性がありました。この更新は、この重要な前提条件を明確にすることで、開発者の手間を省き、より堅牢なコードを書く手助けをします。 -
void
関数からのerrno
抽出: C言語では、多くのシステムコールやライブラリ関数がエラー発生時にグローバル変数errno
を設定します。Goのcgo
は、C関数を呼び出した際に、その戻り値とerrno
をGoの多値戻り値(result, err
)として取得する機能を提供しています。しかし、void
を返すC関数(つまり、明示的な戻り値がない関数)の場合に、errno
をどのように取得するかが不明瞭でした。この変更は、void
関数であっても_, err := C.voidFunc()
のようにアンダースコア(_
)を使って戻り値を無視しつつ、errno
をエラーとして取得できることを明示することで、エラーハンドリングの柔軟性を高めています。
これらのドキュメントの更新は、cgo
の利用者がよりスムーズにGoとCの連携を実装できるよう、具体的な使用上の注意点と機能の利用方法を補足するものです。
前提知識の解説
1. cgo
cgo
は、Go言語のプログラムからC言語のコードを呼び出す(またはその逆)ためのGoツールです。Goのソースファイル内に特別なコメントブロック(import "C"
の直前)を使ってCコードを記述したり、既存のCライブラリをリンクしたりすることができます。cgo
は、GoとCの間のデータ型の変換、関数呼び出し規約の調整、およびメモリ管理の橋渡しを行います。
import "C"
: Goのソースファイル内でimport "C"
と記述することで、cgo
が有効になります。このインポートの直前のコメントブロックにCのコード(関数宣言、構造体、マクロなど)を記述できます。C.
プレフィックス:cgo
によってGoからCのシンボル(関数、変数、型など)を参照する際には、C.
プレフィックスを使用します。例:C.puts("Hello from C!")
、C.int
、C.struct_stat
。- データ型変換: GoとCの間でデータ型をやり取りする際には、
cgo
が自動的に変換を試みますが、複雑な型(例: Goのstring
とCのchar*
)では明示的な変換関数(C.CString
,C.GoString
)が必要になります。 - メモリ管理:
cgo
を介してCの関数が割り当てたメモリは、Goのガベージコレクタの管理外です。そのため、Cのmalloc
などで割り当てられたメモリは、Cのfree
関数を使って明示的に解放する必要があります。
2. C言語のerrno
errno
は、C言語の標準ライブラリで定義されているグローバル変数(またはマクロ)で、システムコールやライブラリ関数がエラーを報告するために使用されます。
- 定義:
errno
は通常、<errno.h>
ヘッダファイルで定義されています。これは整数型であり、エラーが発生すると特定のエラーコードが設定されます。 - 使用方法: Cの関数が失敗した場合、その関数は通常、特定の値(例:
-1
やNULL
)を返し、同時にerrno
にエラーの種類を示す値を設定します。開発者は関数の戻り値をチェックし、エラーが示された場合にerrno
の値を調べて具体的なエラー原因を特定します。 - スレッドセーフティ: 現代のシステムでは、
errno
は通常、スレッドローカルストレージ(TLS)として実装されており、各スレッドが独自のerrno
を持つため、スレッドセーフです。 perror()
とstrerror()
:errno
の値を人間が読めるエラーメッセージに変換するために、perror()
関数(標準エラー出力にメッセージを出力)やstrerror()
関数(エラーメッセージ文字列を返す)が利用されます。
3. C言語のmalloc
とfree
malloc
とfree
は、C言語の標準ライブラリ<stdlib.h>
で提供される動的メモリ管理関数です。
malloc(size_t size)
: 指定されたsize
バイトのメモリブロックをヒープから割り当て、そのブロックの先頭へのポインタを返します。メモリの割り当てに失敗した場合はNULL
を返します。割り当てられたメモリの内容は初期化されません。free(void *ptr)
:malloc
、calloc
、またはrealloc
によって以前に割り当てられたメモリブロックを解放します。解放されたメモリは再利用可能になります。NULL
ポインタをfree
しても安全ですが、既に解放されたメモリやmalloc
などで割り当てられていないメモリをfree
すると未定義の動作を引き起こします。- 重要性: C言語では、プログラムが実行時に必要なメモリ量を動的に調整するためにこれらの関数が不可欠です。特に、データ構造のサイズがコンパイル時に不明な場合や、大量のデータを一時的に処理する場合に利用されます。
技術的詳細
1. C.free
とstdlib.h
の必要性
cgo
は、GoのコードからCの関数を呼び出すためのメカニズムを提供しますが、それはCのコンパイラとリンカを内部的に利用してGoとCのコードを結合することで実現されます。GoのC.CString
関数は、Goのstring
をCのchar*
(ヌル終端文字列)に変換するために、Cのmalloc
関数を使用してメモリを割り当てます。この割り当てられたメモリは、Goのガベージコレクタの管理外にあるため、使用後に明示的に解放する必要があります。
C言語において、malloc
やfree
といった動的メモリ管理関数は、標準ライブラリの<stdlib.h>
ヘッダファイルでそのプロトタイプが宣言されています。Cのコンパイラがfree
関数の存在と正しいシグネチャを認識するためには、このヘッダファイルをインクルードする必要があります。
cgo
のGoファイル内でCのコードを記述する際、import "C"
の直前のコメントブロックにCのヘッダファイルをインクルードすることができます。例えば、C.free
を使用するためには、以下のように記述する必要があります。
package main
/*
#include <stdlib.h> // free関数の宣言が含まれる
#include <string.h> // strlen関数の宣言が含まれる
*/
import "C"
import "fmt"
func main() {
goStr := "Hello from Go!"
cStr := C.CString(goStr) // Cのヒープにメモリを割り当て
fmt.Printf("Go string: %s\n", goStr)
fmt.Printf("C string: %s\n", C.GoString(cStr))
C.free(unsafe.Pointer(cStr)) // 割り当てたメモリを解放
fmt.Println("Memory freed.")
}
このコミットは、cgo
のドキュメントにこの重要な情報(C.free
を使用する際にはstdlib.h
をインクルードする必要があること)を追記することで、開発者がcgo
をより安全かつ正確に利用できるようにしています。
2. void
関数からのerrno
抽出
C言語の関数は、エラーが発生した場合にerrno
グローバル変数にエラーコードを設定することが一般的です。cgo
は、Goの多値戻り値のメカニズムを利用して、C関数の戻り値とerrno
の値をGoのerror
型として同時に取得する機能を提供しています。
通常、戻り値を持つC関数をGoから呼び出す場合、以下のように記述します。
package main
/*
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int my_atoi(const char *s) {
char *endptr;
long val = strtol(s, &endptr, 10);
if (endptr == s || *endptr != '\0') {
errno = EINVAL; // 無効な引数
return 0;
}
return (int)val;
}
*/
import "C"
import (
"fmt"
"syscall"
)
func main() {
// 戻り値とerrnoを両方取得
n, err := C.my_atoi(C.CString("123"))
if err != nil {
fmt.Printf("Error calling my_atoi(\"123\"): %v (errno: %d)\n", err, err.(syscall.Errno))
} else {
fmt.Printf("Result of my_atoi(\"123\"): %d\n", n)
}
n2, err2 := C.my_atoi(C.CString("abc"))
if err2 != nil {
fmt.Printf("Error calling my_atoi(\"abc\"): %v (errno: %d)\n", err2, err2.(syscall.Errno))
} else {
fmt.Printf("Result of my_atoi(\"abc\"): %d\n", n2)
}
}
このコミットが明確にしているのは、C関数がvoid
を返す場合(つまり、明示的な戻り値がない場合)でも、errno
をGoのerror
として取得できるという点です。これは、Cのシステムコールやライブラリ関数の中には、成功時には何も返さず、エラー時にのみerrno
を設定するものがあるため、非常に重要です。
例として、Cでvoid
を返す関数を考えます。
// Cのコード
#include <stdio.h>
#include <errno.h>
void voidFunc(int should_fail) {
if (should_fail) {
errno = EPERM; // Operation not permitted
} else {
errno = 0; // Clear errno on success
}
printf("voidFunc called with should_fail = %d\n", should_fail);
}
このvoidFunc
をGoから呼び出し、errno
を取得するには、以下のように記述します。
package main
/*
#include <stdio.h>
#include <errno.h>
void voidFunc(int should_fail) {
if (should_fail) {
errno = EPERM; // Operation not permitted
} else {
errno = 0; // Clear errno on success
}
printf("voidFunc called with should_fail = %d\n", should_fail);
}
*/
import "C"
import (
"fmt"
"syscall"
)
func main() {
// 成功するケース
_, err1 := C.voidFunc(C.int(0)) // 戻り値は_で無視
if err1 != nil {
fmt.Printf("Error calling voidFunc(0): %v (errno: %d)\n", err1, err1.(syscall.Errno))
} else {
fmt.Println("voidFunc(0) succeeded.")
}
// 失敗するケース
_, err2 := C.voidFunc(C.int(1)) // 戻り値は_で無視
if err2 != nil {
fmt.Printf("Error calling voidFunc(1): %v (errno: %d)\n", err2, err2.(syscall.Errno))
} else {
fmt.Println("voidFunc(1) succeeded.")
}
}
このコミットは、cgo
のドキュメントに_, err := C.voidFunc()
という具体的な例を追加することで、この機能の存在と使用方法を明確にし、開発者がvoid
を返すC関数からのエラーハンドリングを適切に行えるようにしています。
コアとなるコードの変更箇所
diff --git a/src/cmd/cgo/doc.go b/src/cmd/cgo/doc.go
index 1bb48f44e9..6f3343de54 100644
--- a/src/cmd/cgo/doc.go
+++ b/src/cmd/cgo/doc.go
@@ -65,11 +65,13 @@ struct_, union_, or enum_, as in C.struct_stat.
Go structs cannot embed fields with C types.
-Any C function that returns a value may be called in a multiple
-assignment context to retrieve both the return value and the
-C errno variable as an error. For example:
+Any C function (even void functions) may be called in a multiple
+assignment context to retrieve both the return value (if any) and the
+C errno variable as an error (use _ to skip the result value if the
+function returns void). For example:
n, err := C.atoi("abc")
+ _, err := C.voidFunc()
In C, a function argument written as a fixed size array
actually requires a pointer to the first element of the array.
@@ -83,7 +85,8 @@ by making copies of the data. In pseudo-Go definitions:
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
- // freed, such as by calling C.free.
+ // freed, such as by calling C.free (be sure to include stdlib.h
+ // if C.free is needed).
func C.CString(string) *C.char
// C string to Go string
コアとなるコードの解説
このコミットは、src/cmd/cgo/doc.go
ファイル内の2つの主要なセクションにドキュメントの更新を行っています。
-
void
関数からのerrno
抽出に関する説明の追加:- 変更前:
この記述は、戻り値を持つC関数からAny C function that returns a value may be called in a multiple assignment context to retrieve both the return value and the C errno variable as an error. For example: n, err := C.atoi("abc")
errno
を取得できることを示唆していましたが、void
関数については触れていませんでした。 - 変更後:
この変更により、「Any C function (even void functions) may be called in a multiple assignment context to retrieve both the return value (if any) and the C errno variable as an error (use _ to skip the result value if the function returns void). For example: n, err := C.atoi("abc") _, err := C.voidFunc()
void
関数であっても」errno
をエラーとして取得できることが明確にされました。また、void
関数の場合は戻り値をアンダースコア(_
)で無視する具体的な例_, err := C.voidFunc()
が追加され、使用方法がより分かりやすくなりました。これは、C言語のシステムコールなどでvoid
を返しつつerrno
を設定するパターンに対応するために重要な情報です。
- 変更前:
-
C.free
とstdlib.h
の必要性に関する注意書きの追加:- 変更前:
// Go string to C string // The C string is allocated in the C heap using malloc. // It is the caller's responsibility to arrange for it to be // freed, such as by calling C.free. func C.CString(string) *C.char
C.CString
が割り当てたメモリをC.free
で解放する必要があることは述べられていましたが、C.free
がどのヘッダファイルで宣言されているかについては言及がありませんでした。 - 変更後:
この変更により、「// Go string to C string // The C string is allocated in the C heap using malloc. // It is the caller's responsibility to arrange for it to be // freed, such as by calling C.free (be sure to include stdlib.h // if C.free is needed). func C.CString(string) *C.char
C.free
が必要な場合はstdlib.h
をインクルードするようにしてください」という明確な指示が追加されました。これにより、開発者がC.free
を使用する際に必要なCヘッダのインクルードを忘れずに行うことが促され、コンパイルエラーや実行時エラーを防ぐことができます。
- 変更前:
これらのドキュメントの更新は、cgo
の利用者がGoとCの間の相互運用性に関する一般的な落とし穴を避け、より正確で堅牢なコードを記述できるようにするための重要な改善です。
関連リンク
- Go言語公式ドキュメント - cgo:
- https://pkg.go.dev/cmd/cgo
- https://go.dev/blog/c-go-cgo (A Tour of Cgo)
- C言語
errno
: - C言語
malloc
/free
:
参考にした情報源リンク
- Go言語の公式ドキュメントおよび
cgo
に関するブログ記事 - C言語の標準ライブラリ関数(
errno
,malloc
,free
)に関するmanページやC++リファレンスサイト cgo
の動作原理に関する一般的なプログラミング知識