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

[インデックス 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とは異なるため、開発者が混乱しやすい点が存在します。

  1. C.freestdlib.hの関連性: cgoを通じてC言語の関数を呼び出す際、GoのstringをCのchar*に変換するためにC.CStringのようなヘルパー関数が提供されます。この関数はCのヒープにメモリを割り当てるため、使用後はC.freeを使って解放する必要があります。しかし、C言語においてfree関数は標準ライブラリのstdlib.hで宣言されています。cgoの初期のドキュメントでは、このstdlib.hのインクルードが明示されていなかったため、開発者がC.freeを呼び出そうとした際にコンパイルエラーや未定義の動作に遭遇する可能性がありました。この更新は、この重要な前提条件を明確にすることで、開発者の手間を省き、より堅牢なコードを書く手助けをします。

  2. 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.intC.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の関数が失敗した場合、その関数は通常、特定の値(例: -1NULL)を返し、同時にerrnoにエラーの種類を示す値を設定します。開発者は関数の戻り値をチェックし、エラーが示された場合にerrnoの値を調べて具体的なエラー原因を特定します。
  • スレッドセーフティ: 現代のシステムでは、errnoは通常、スレッドローカルストレージ(TLS)として実装されており、各スレッドが独自のerrnoを持つため、スレッドセーフです。
  • perror()strerror(): errnoの値を人間が読めるエラーメッセージに変換するために、perror()関数(標準エラー出力にメッセージを出力)やstrerror()関数(エラーメッセージ文字列を返す)が利用されます。

3. C言語のmallocfree

mallocfreeは、C言語の標準ライブラリ<stdlib.h>で提供される動的メモリ管理関数です。

  • malloc(size_t size): 指定されたsizeバイトのメモリブロックをヒープから割り当て、そのブロックの先頭へのポインタを返します。メモリの割り当てに失敗した場合はNULLを返します。割り当てられたメモリの内容は初期化されません。
  • free(void *ptr): malloccalloc、またはreallocによって以前に割り当てられたメモリブロックを解放します。解放されたメモリは再利用可能になります。NULLポインタをfreeしても安全ですが、既に解放されたメモリやmallocなどで割り当てられていないメモリをfreeすると未定義の動作を引き起こします。
  • 重要性: C言語では、プログラムが実行時に必要なメモリ量を動的に調整するためにこれらの関数が不可欠です。特に、データ構造のサイズがコンパイル時に不明な場合や、大量のデータを一時的に処理する場合に利用されます。

技術的詳細

1. C.freestdlib.hの必要性

cgoは、GoのコードからCの関数を呼び出すためのメカニズムを提供しますが、それはCのコンパイラとリンカを内部的に利用してGoとCのコードを結合することで実現されます。GoのC.CString関数は、GoのstringをCのchar*(ヌル終端文字列)に変換するために、Cのmalloc関数を使用してメモリを割り当てます。この割り当てられたメモリは、Goのガベージコレクタの管理外にあるため、使用後に明示的に解放する必要があります。

C言語において、mallocfreeといった動的メモリ管理関数は、標準ライブラリの<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つの主要なセクションにドキュメントの更新を行っています。

  1. void関数からのerrno抽出に関する説明の追加:

    • 変更前:
      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")
      
      この記述は、戻り値を持つC関数から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を設定するパターンに対応するために重要な情報です。
  2. C.freestdlib.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に関するブログ記事
  • C言語の標準ライブラリ関数(errno, malloc, free)に関するmanページやC++リファレンスサイト
  • cgoの動作原理に関する一般的なプログラミング知識