Upgrade to Pro — share decks privately, control downloads, hide ads and more …

意外と知らないcgoの世界

Avatar for Hinata Hinata
May 08, 2025
33

 意外と知らないcgoの世界

この発表では普段意識することが少ない「cgo」パッケージについて解説します!cgo とはC言語とGo言語の橋渡しをするパッケージであり、Go言語側でC言語で定義された機能を使うことができます。このスライドではCとGoの連携方法から、cgo パッケージの内部コンパイル順序について解説します✍️

Avatar for Hinata

Hinata

May 08, 2025
Tweet

Transcript

  1. © DMM © DMM 意外と知らない 
 cgoの世界 
 菊地 ひなた

    
 開発統括本部 データアプリケーショングループ ML基盤チーム 2025-05-15
  2. © DMM 自己紹介
 名前 : 菊地 ひなた
 経緯 : 24新卒でDMMに入社


    ML基盤チームに所属
 趣味 : 料理とお酒が大好きです
 最近は肝臓のためにお酒控えてますられませんでした
 シェアハウスにお引越ししました
 2
  3. © DMM お品書き
 1. 自己紹介
 2. cgo package発表の動機
 3. 使い方


    a. Go側でCの関数を定義して呼び出す
 b. Go側からCの定義済み関数を呼び出す
 c. C側から定義済みGo関数を呼び出す
 4. CからGoパッケージを呼び出す
 5. コンパイル順序
 6. まとめ
 
 3
  4. © DMM cgo packageの発表動機
 - Pythonの機械学習フレームワークの裏ではCが動いてることが多い
 ⇒ 調べてみるとGoも同じ
 ⇒ cgo

    package 登場󰢨
 
 
 
 4 [1] https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/ops/array_grad.cc [2] https://github.com/tensorflow/tensorflow/blob/master/tensorflow/go/graph.go tensorflow for pythonで内部利用 される配列の微分用計算core [1] tensorflow for Goにおける 配列の微分用計算core [2] ☝cgoパッケージが
 使われている ☝.ccで記述されてる でもどうやってGoとCが連携して動くんだろう…?
 💡調べてみることにしました!
  5. © DMM cgoって何?
 - C言語とGo言語の橋渡しをする機能
 - C言語で定義された機能をGo言語側で呼び出して使うことができる
 たとえばこんなときに便利:
 • どうしてもCで提供されているライブラリを使いたい


    (例)
 - デバイスドライバの操作[3][4]パッケージを使った機能の実装 
 - マイコンなどのデバイスで独自関数が提供されている時[5]
 
 
 5 [3] ioctlのgoのラッパーhttps://pkg.go.dev/acln.ro/ioctl のlatestは2019, amd64, armのみ対応。実装はcgoを使用
 [4] goでusb接続イベントを拾いたい ではmac OSの提供するIOKit Frameworkをcgoで呼び出している
 [5] 組み込みLinuxでGolangのススメ - Qiita tinygoからcgoでC関数を呼び出す組み込みの勧め

  6. © DMM お品書き
 1. 自己紹介
 2. cgo package発表の動機
 3. 使い方


    a. Go側でCの関数を定義して呼び出す
 b. Go側からCの定義済み関数を呼び出す
 c. C側から定義済みGo関数を呼び出す
 4. CからGoパッケージを呼び出す
 5. コンパイル順序
 6. まとめ
 
 6
  7. © DMM 1. Go側でCの関数を定義して呼び出す
 (例)普通の計算式を定義する場合
 
 7 package main /*

    #include <stdio.h> int myadd(int a, int b) { return a + b; } */ import "C" import "fmt" func main() { // C の関数を呼び出す c := C.myadd(1, 2) fmt.Printf("result value = %d, result type = %T", c, c) } - 使い方
 1. Go側でCの関数を定義して呼び出す 
 2. Go側からCの定義済み関数を呼び出す 
 3. C側から定義済みGo関数を呼び出す 

  8. © DMM (例)普通の計算式を定義する場合
 
 8 package main /* #include <stdio.h>

    int myadd(int a, int b) { return a + b; } */ import "C" import "fmt" func main() { // C の関数を呼び出す c := C.myadd(1, 2) fmt.Printf("result value = %d, result type = %T", c, c) } 1. コメントアウトで C の関数を定義
 1. Go側でCの関数を定義して呼び出す
 2. 直後でcgo
 パッケージをimport
 3. C.”関数名” として定義さ れた関数を呼び出す
 - 使い方
 1. Go側でCの関数を定義して呼び出す 
 2. Go側からCの定義済み関数を呼び出す 
 3. C側から定義済みGo関数を呼び出す 

  9. © DMM 2. Go側からCの定義済み関数を呼び出す
 (例)
 9 - 使い方
 1. Go側でCの関数を定義して呼び出す

    
 2. Go側からCの定義済み関数を呼び出す 
 3. C側から定義済みGo関数を呼び出す package main /* // CFLAGS: Cコンパイラへのオプション // (例: インクルードパス指定 ) // #cgo CFLAGS: -I/opt/local/include // LDFLAGS: リンカへのオプション // (例: ライブラリパス、リンクするライブラリ名指定 ) // #cgo LDFLAGS: -L/opt/local/lib -lcustommath // 標準のmath.hを使用。多くの Unix系システムでは libmのリンクが必要。 #cgo LDFLAGS: -lm #include <math.h> // C 標準数学ライブラリ */ import "C" // cgoを有効化 import "fmt" func main() { inputValue := 49.0 fmt.Printf("Go: Calculating square root of %.2f using C's math library. \n", inputValue ) // Goのfloat64をCのdoubleに変換してC関数を呼び出す cInput := C.double(inputValue ) cResult := C.sqrt(cInput) fmt.Printf("Go: Result from C's sqrt( %.2f) = %.2f\n", cInput, cResult) } コンパイルオプションの指定 
 CFLAGS, CPPFLAGS, CXXFLAGS, FFLAGS and LDFLAGSがある[6] 
 [6] https://pkg.go.dev/cmd/cgo#hdr-Using_cgo_with_the_go_command
  10. © DMM 2. Go側からCの定義済み関数を呼び出す
 (例)
 10 package main /* //

    CFLAGS: C コンパイラへのオプション // (例: インクルードパス指定 ) // #cgo CFLAGS: -I/opt/local/include // LDFLAGS: リンカへのオプション // (例: ライブラリパス、リンクするライブラリ名指定 ) // #cgo LDFLAGS: -L/opt/local/lib -lcustommath // 標準の math.hを使用。多くの Unix系システムでは libmのリンクが必 要。 #cgo LDFLAGS: -lm #include <math.h> // C標準数学ライブラリ */ import "C" // cgoを有効化 import "fmt" func main() { inputValue := 49.0 fmt.Printf("Go: Calculating square root of %.2f using C's math library. \n", inputValue ) // Goのfloat64をCのdoubleに変換してC関数を呼び出す cInput := C.double(inputValue ) cResult := C.sqrt(cInput) fmt.Printf("Go: Result from C's sqrt( %.2f) = %.2f\n", cInput, cResult) } 1. 今回はmath.hをライブラリとして使用するの で-lm (library math)オプションが必要 
 2. math.hで定義済みの関数が呼び出せる 
 - 使い方
 1. Go側でCの関数を定義して呼び出す 
 2. Go側からCの定義済み関数を呼び出す 
 3. C側から定義済みGo関数を呼び出す
  11. © DMM 3. C側から定義済みGo関数を呼び出す
 (例)
 11 - 使い方
 1. Go側でCの関数を定義して呼び出す

    
 2. Go側からCの定義ずみ関数を呼び出す 
 3. C側から定義済みGo関数を呼び出す 
 4. 
 package main /* #include <stdio.h> // Goからエクスポートされる関数の宣言 extern void GoCallbackHandler(int id); // C側からGoの関数を呼び出す関数 (これをGoのmainから呼び出す ) static void triggerGoCallbacks() { printf("C: Triggering Go callback...\n"); GoCallbackHandler(123); // Go 関数を呼び出し printf("C: Triggering Go workers...\n"); } */ import "C" … … import "C" import ( "fmt" ) //export GoCallbackHandler <= C で呼び出す関数を宣言 func GoCallbackHandler (id C.int) { fmt.Printf("Go: Callback handler received ID: %d\n", int(id)) } func main() { C.triggerGoCallbacks () // C関数を呼び出し、そこから Go関数が呼ばれる }
  12. © DMM 3. C側から定義済みGo関数を呼び出す
 (例)
 12 package main /* #include

    <stdio.h> // Goからエクスポートされる関数の宣言 extern void GoCallbackHandler(int id); // C側から Goの関数を呼び出す関数 (これを Goのmainから呼び出す ) static void triggerGoCallbacks() { printf("C: Triggering Go callback...\n"); GoCallbackHandler(123); // Go関数を呼び出し printf("C: Triggering Go workers...\n"); } */ import "C" … … import "C" import ( "fmt" ) //export GoCallbackHandler <= Cで呼び出す関数を宣言 func GoCallbackHandler (id C.int) { fmt.Printf("Go: Callback handler received ID: %d\n", int(id)) } func main() { C.triggerGoCallbacks () // C関数を呼び出し、そこから Go関数が呼ばれる } 1. externでGo関数の宣言 
 3. Go関数の呼び出し 
 2. C側で呼び出す関数を定義 
 4. C関数からGo関数を呼ぶ 
 - 使い方
 1. Go側でCの関数を定義して呼び出す 
 2. Go側からCの定義ずみ関数を呼び出す 
 3. C側から定義済みGo関数を呼び出す 

  13. © DMM - 使い方
 1. GO側でCの関数を定義して呼び出す 
 2. GO側からCの定義ずみ関数を呼び出す 


    3. C側から定義済みGO関数を呼び出す 
 4. 
 3. C側から定義済みGo関数を呼び出す
 (例)
 13 package main /* #include <stdio.h> // Goからエクスポートされる関数の宣言 extern void GoCallbackHandler(int id); // C側からGoの関数を呼び出す関数 (これをGoのmainから呼び出す ) static void triggerGoCallbacks() { printf("C: Triggering Go callback...\n"); GoCallbackHandler(123); // Go 関数を呼び出し printf("C: Triggering Go workers...\n"); } */ import "C" … … import "C" import ( "fmt" ) //export GoCallbackHandler <= C で呼び出す関数を宣言 func GoCallbackHandler (id C.int) { fmt.Printf("Go: Callback handler received ID: %d\n", int(id)) } func main() { C.triggerGoCallbacks () // C関数を呼び出し、そこから Go関数が呼ばれる } 1. externでgo関数の宣言
 3. go関数の呼び出し
 2. C側で呼び出す関数を定義
 4. C関数からGo関数を呼ぶ
 a. Go側でCの関数を定義 して呼び出す
 b. Go側からCの定義済み関数 を呼び出す
 c. C側から定義済みGo関数 を呼び出す
  14. © DMM - 使い方
 1. GO側でCの関数を定義して呼び出す 
 2. GO側からCの定義ずみ関数を呼び出す 


    3. C側から定義済みGO関数を呼び出す 
 4. 
 3. C側から定義済みGo関数を呼び出す
 (例)
 14 package main /* #include <stdio.h> // Goからエクスポートされる関数の宣言 extern void GoCallbackHandler(int id); // C側からGoの関数を呼び出す関数 (これをGoのmainから呼び出す ) static void triggerGoCallbacks() { printf("C: Triggering Go callback...\n"); GoCallbackHandler(123); // Go 関数を呼び出し printf("C: Triggering Go workers...\n"); } */ import "C" … … import "C" import ( "fmt" ) //export GoCallbackHandler <= C で呼び出す関数を宣言 func GoCallbackHandler (id C.int) { fmt.Printf("Go: Callback handler received ID: %d\n", int(id)) } func main() { C.triggerGoCallbacks () // C関数を呼び出し、そこから Go関数が呼ばれる } 1. externでgo関数の宣言
 3. go関数の呼び出し
 2. C側で呼び出す関数を定義
 4. C関数からGo関数を呼ぶ
 Goのパッケージは呼び出せないの?
 a. Go側でCの関数を定義 して呼び出す
 b. Go側からCの定義済み関数 を呼び出す
 c. C側から定義済みGo関数 を呼び出す
  15. © DMM お品書き
 1. 自己紹介
 2. cgo package発表の動機
 3. 使い方


    a. Go側でCの関数を定義して呼び出す
 b. Go側からCの定義済み関数を呼び出す
 c. C側から定義済みGo関数を呼び出す
 4. CからGoパッケージを呼び出す
 5. コンパイル順序
 6. まとめ
 
 15
  16. © DMM CからGoパッケージを呼び出す
 16 (例)
 1. Goを.so(共有ライブラリ)としてビルド
 2. Cをコンパイルする際1をライブラリとしてリンク
 3.

    a.out(実行ファイル)が生成される
 // main.c #include <stdio.h> #include "library.h" // go build -buildmode=c-shared などで生成されたヘッ ダーファイル int main() { printf("C: Calling Go function... \n"); // Goでエクスポートされた関数を呼び出す PrintMessage ("Hello from C!" ); printf("C: Back from Go function.\n"); return 0; } // wrapper.go package main import "C" import "fmt" func PrintMessage (message *C.char) { // Cの文字列 (*C.char) をGoの文字列 (string) に変換 goMessage := C.GoString(message) // Goの fmt パッケージの関数を呼び出す fmt.Println("Message from C:" , goMessage) } func main() {} // ビルドのために空で定義 呼び出されるパッケージ側 
 パッケージを呼び出す側 

  17. © DMM CからGoパッケージを呼び出す
 17 (例)
 1. Goを.so(共有ライブラリ)としてビルド 
 2. Cをコンパイルする際1をライブラリとしてリンク

    
 3. a.out(実行ファイル)が生成される 
 // main.c #include <stdio.h> #include "library.h" // go build -buildmode=c-shared などで生成されたヘッ ダーファイル int main() { printf("C: Calling Go function... \n"); // Goでエクスポートされた関数を呼び出す PrintMessage ("Hello from C!" ); printf("C: Back from Go function.\n"); return 0; } // wrapper.go package main import "C" import "fmt" func PrintMessage (message *C.char) { // Cの文字列 (*C.char) をGoの文字列 (string) に変換 goMessage := C.GoString(message) // Goの fmt パッケージの関数を呼び出す fmt.Println("Message from C:" , goMessage) } func main() {} // ビルドのために空で定義 呼び出されるパッケージ側 
 パッケージを呼び出す側 

  18. © DMM CからGoパッケージを呼び出す
 18 (例)
 1. Goを.so(共有ライブラリ)としてビルド ⇒ liblibrary.so, liblibrary.h

    
 2. Cをコンパイルする際1をライブラリとしてリンク
 3. a.out(実行ファイル)が生成される
 // main.c #include <stdio.h> #include "library.h" // go build -buildmode=c-shared などで生成されたヘッ ダーファイル int main() { printf("C: Calling Go function...\n"); // Goでエクスポートされた関数を呼び出す PrintMessage("Hello from C!"); printf("C: Back from Go function.\n"); return 0; } // wrapper.go package main import "C" import "fmt" func PrintMessage (message *C.char) { // Cの文字列 (*C.char) をGoの文字列 (string) に変換 goMessage := C.GoString(message) // Goの fmt パッケージの関数を呼び出す fmt.Println("Message from C:" , goMessage) } func main() {} // ビルドのために空で定義 呼び出されるパッケージ側 
 パッケージを呼び出す側 
 go build -buildmode=c-shared -o liblibrary.so wrapper.go # linux go build -ldflags="-w -linkmode external -extldflags '-Wl,-install_name,@rpath/liblibrary.so'" wrapper.go # mac
  19. © DMM CからGoパッケージを呼び出す
 19 (例)
 1. Goを.so(共有ライブラリ)としてビルド ⇒ liblibrary.so, liblibrary.h

    
 2. Cをコンパイルする際1をライブラリとしてリンク
 3. a.out(実行ファイル)が生成される
 // main.c #include <stdio.h> #include "library.h" // go build -buildmode=c-shared などで生成されたヘッ ダーファイル int main() { printf("C: Calling Go function...\n"); // Goでエクスポートされた関数を呼び出す PrintMessage("Hello from C!"); printf("C: Back from Go function.\n"); return 0; } // wrapper.go package main import "C" import "fmt" func PrintMessage (message *C.char) { // Cの文字列 (*C.char) をGoの文字列 (string) に変換 goMessage := C.GoString(message) // Goの fmt パッケージの関数を呼び出す fmt.Println("Message from C:" , goMessage) } func main() {} // ビルドのために空で定義 呼び出されるパッケージ側 
 パッケージを呼び出す側 
 go build -buildmode=c-shared -o liblibrary.so wrapper.go # linux go build -ldflags="-w -linkmode external -extldflags '-Wl,-install_name,@rpath/liblibrary.so'" wrapper.go # mac パッケージに含まれる関数定義 
 ビルドのためのmain(空) 
 共有ライブラリの ビルド用オプション 
 共有ライブラリの命名 
 (libから始まる必要がある)[7] 
 [7] 命名規約 (リンカーとライブラリ ) - 共有オブジェクトは
  20. © DMM CからGoパッケージを呼び出す
 20 (例)
 2. Cをコンパイルする際に共有ライブラリをリンク
 
 // main.c

    #include <stdio.h> #include "liblibrary.h" // go build -buildmode=c-shared などで生成されたヘッダー ファイル int main() { printf("C: Calling Go function... \n"); // Goでエクスポートされた関数を呼び出す PrintMessage ("Hello from C!" ); printf("C: Back from Go function. \n"); return 0; } // wrapper.go package main import "C" import "fmt" func PrintMessage( message *C.char) { // Cの文字列 (*C.char) をGoの文字列 (string) に変換 goMessage := C.GoString(message) // Goの fmt パッケージの関数を呼び出す fmt.Println("Message from C:", goMessage) } func main() {} // ビルドのために空で定義 呼び出されるパッケージ側 
 パッケージを呼び出す側 
 gcc main.c -o main_app -L. -llibrary # linuxの場合 gcc main.c -o main_app -L. -llibrary -Wl,-rpath,@executable_path/ # macの場合
  21. © DMM CからGoパッケージを呼び出す
 21 (例)
 2. Cをコンパイルする際に共有ライブラリをリンク
 
 // main.c

    #include <stdio.h> #include "liblibrary.h" // go build -buildmode=c-shared などで生成されたヘッダー ファイル int main() { printf("C: Calling Go function... \n"); // Goでエクスポートされた関数を呼び出す PrintMessage ("Hello from C!" ); printf("C: Back from Go function. \n"); return 0; } // wrapper.go package main import "C" import "fmt" func PrintMessage( message *C.char) { // Cの文字列 (*C.char) をGoの文字列 (string) に変換 goMessage := C.GoString(message) // Goの fmt パッケージの関数を呼び出す fmt.Println("Message from C:", goMessage) } func main() {} // ビルドのために空で定義 呼び出されるパッケージ側 
 パッケージを呼び出す側 
 gcc main.c -o main_app -L. -llibrary # linuxの場合 gcc main.c -o main_app -L. -llibrary -Wl,-rpath,@executable_path/ # macの場合 -L でライブラリのディレクトリを指定 
 -l はlib配下の名称を指定 
 (例) libxxx.soの場合 -lxxx 

  22. © DMM CからGoパッケージを呼び出す
 22 (例)
 3. 生成された実行ファイルを利用
 // main.c #include

    <stdio.h> #include "library.h" // go build -buildmode=c-shared などで生成されたヘッ ダーファイル int main() { printf("C: Calling Go function...\n"); // Goでエクスポートされた関数を呼び出す PrintMessage("Hello from C!"); printf("C: Back from Go function.\n"); return 0; } // wrapper.go package main import "C" import "fmt" //export PrintMessage func PrintMessage( message *C.char) { // Cの文字列 (*C.char) をGoの文字列 (string) に変換 goMessage := C.GoString(message) // Goの fmt パッケージの関数を呼び出す fmt.Println("Message from C:", goMessage) } func main() {} // ビルドのために空で定義 呼び出されるパッケージ側 
 パッケージを呼び出す側 
 # 3. 実行 export LD_LIBRARY_PATH =. # mac/linux 共通 (ライブラリのパスを認識させる必要がある場合がある ) ./main_app
  23. © DMM お品書き
 1. 自己紹介
 2. cgo package発表の動機
 3. 使い方


    a. Go側でCの関数を定義して呼び出す
 b. C側から定義済みGo関数を呼び出す
 c. Go側からCの定義ずみ関数を呼び出す
 4. コンパイル順序
 5. まとめ
 
 25
  24. © DMM cgo packageのコンパイルの順序
 1. CとGoの中間ファイル生成 (cgo) 2. Cコードのコンパイル 3.

    Goコードのコンパイル 26 package main /* #include <stdio.h> int myadd(int a, int b) { return a + b; } */ import "C" import "fmt" func main() { // C の関数を呼び出す c := C.myadd(1, 2) fmt.Printf("[result] value = %d, type = %T", c, c) } go build -a \ # -a : 強制再ビルド -x \ # -x : 出力内容を全表示 main.go > output.txt 対象のソースコード (既出) 
 ビルドコマンド
 4. まとめる 5. リンク
  25. © DMM cgo packageのコンパイルの順序
 1. CとGoの中間ファイル生成 (cgo) 2. Cコードのコンパイル 3.

    Goコードのコンパイル 27 package main /* #include <stdio.h> int myadd(int a, int b) { return a + b; } */ import "C" import "fmt" func main() { // C の関数を呼び出す c := C.myadd(1, 2) fmt.Printf("[result] value = %d, type = %T", c, c) } go build -a \ # -a : 強制再ビルド -x \ # -x : 出力内容を全表示 main.go > output.txt 対象のソースコード (既出) 
 ビルドコマンド
 4. まとめる 5. リンク
  26. © DMM 4. まとめる 5. リンク cgo packageのコンパイルの順序
 1. CとGoの中間ファイル生成

    (cgo) 2. Cコードのコンパイル 3. Goコードのコンパイル 28 go build -a \ # -a : 強制再ビルド -x \ # -x : 出力内容を全表示 main.go > output.txt ビルドコマンド
 package main /* #include <stdio.h> int myadd(int a, int b) { return a + b; } */ import "C" import "fmt" func main() { // C の関数を呼び出す c := C.myadd(1, 2) fmt.Printf("[result] value = %d, type = %T", c, c) } 対象のソースコード (既出) 

  27. © DMM 1. CとGoの中間ファイル生成(cgo)
 cgoがmain.goに対して中間ファイルを生成する 29 … TERM='dumb' CGO_LDFLAGS='' \

    /opt/homebrew/Cellar/go/1.24.2/libexec/pkg/tool/darwin_arm64/cgo \ -objdir $WORK/b001 -importpath command-line-arguments "-ldflags=\"-O2\" \"-g\"" \ -- -I $WORK/b001/ -O2 -g ./main.go … - _cgo_export.c
 GoからC言語側へエクスポートされる関数の実体や、GoとCの 間の関数呼び出しに必要なヘルパー関数
 - _cgo_export.h
 GoからC言語側へエクスポートされる関数や型のプロトタイプ 宣言
 - _cgo_main.c
 main() のエントリーポイントをまとめるCのコード
 - main.cgo2.c
 main.go の import "C" ブロック内に書かれたC関数 (myadd) の定義と、GoからそのC関数を呼び出すためのブ リッジとなるC関数の実体 - _cgo_gotypes.go 
 C側で使うGo型を記述したGoファイル - main.cgo1.go
 main.go のC関数呼び出し部分 (C.myadd(1, 2)) を、_cgo_gotypes.go で定義されたラッパー関数 (_Cfunc_myadd) を使う形に変換した Goコード

  28. © DMM 2. Cコードのコンパイル
 中間Cファイルと他のアセンブラ等からオブジェクトファイル (.o)を生成 30 // _cgo_export.c から

    _x001.o を生成 TERM='dumb' cc ... -o $WORK/b001/_x001.o -c _cgo_export.c // main.cgo2.c から _x002.o を生成 TERM='dumb' cc ... -o $WORK/b001/_x002.o -c main.cgo2.c // _cgo_main.c から _cgo_main.o を生成 TERM='dumb' cc ... -o $WORK/b001/_cgo_main.o -c _cgo_main.c … // gcc_arm64.S: ARM64アーキテクチャ固有のアセンブリコード TERM='dumb' cc -I . ... -o $WORK/b056/_x010.o -c gcc_arm64.S // Appendix _x0NN.o

  29. © DMM 3. Goコードのコンパイル
 31 中間Goファイルをアーカイブファイル (_pkg_.a)にまとめる // ログ抜粋 (.goのコンパイル

    ) /opt/homebrew/Cellar/go/1.24.2/libexec/pkg/tool/darwin_arm64/compile \ -o $WORK/b001/_pkg_.a \ -trimpath "$WORK/b001=>" \ -p main -lang=go1.24 \ -buildid mt8PWloQVyET0ZBQuKBs /mt8PWloQVyET0ZBQuKBs \ -goversion go1.24.2 \ -c=4 -shared -nolocalimports \ -importcfg $WORK/b001/importcfg \ -pack $WORK/b001/_cgo_gotypes.go $WORK/b001/main.cgo1.go $WORK/b001/_cgo_import.go _pkg_.a

  30. © DMM 3. Goコードのコンパイル
 32 中間Goファイルをアーカイブファイル (_pkg_.a)にまとめる // ログ抜粋 (.goのコンパイル

    ) /opt/homebrew/Cellar/go/1.24.2/libexec/pkg/tool/darwin_arm64/compile \ -o $WORK/b001/_pkg_.a \ -trimpath "$WORK/b001=>" \ -p main -lang=go1.24 \ -buildid mt8PWloQVyET0ZBQuKBs /mt8PWloQVyET0ZBQuKBs \ -goversion go1.24.2 \ -c=4 -shared -nolocalimports \ -importcfg $WORK/b001/importcfg \ -pack $WORK/b001/_cgo_gotypes.go $WORK/b001/main.cgo1.go $WORK/b001/_cgo_import.go _pkg_.a
 中間生成ファイル
 生成する静的アーカイブファイル 

  31. © DMM 4. まとめる
 33 アーカイブファイル (.a: Go由来)にオブジェクトファイル (.o: C由来)をまと

    める // ログ抜粋 (オブジェクトファイルのアーカイブへの追加 ) /opt/homebrew/Cellar/go/1.24.2/libexec/pkg/tool/darwin_arm64/pack r \ $WORK/b056/_pkg_.a \ $WORK/b056/_x001.o \ $WORK/b056/_x002.o \ … $WORK/b056/_x010.o _x0NN.o
 _pkg_.a
 中間生成物(Go)から生成した静的アーカイブファイル 
 中間生成物(C)から生成したオブジェクトファイル 

  32. © DMM 5. リンク
 34 まとめた_pkg_.aをGoのリンカと Cのリンカでリンク GOROOT='/opt/homebrew/Cellar/go/1.24.2/libexec' \ /opt/homebrew/Cellar/go/1.24.2/libexec/pkg/tool/darwin_arm64/link

    \ -o $WORK/b001/exe/a.out \ -importcfg $WORK/b001/importcfg.link \ -buildmode=pie \ -buildid=UhZEhrBWBaa_sTRe6ewo/mt8PWloQVyET0ZBQuKBs/u8r6iV97MhPiAOxJs9eq/UhZEhrBWBaa_sTRe6ewo \ -extld=cc \ $WORK/b001/_pkg_.a _pkg_.a
 a.out

  33. © DMM 5. リンク
 35 まとめた_pkg_.aをGoのリンカと Cのリンカでリンク GOROOT='/opt/homebrew/Cellar/go/1.24.2/libexec' \ /opt/homebrew/Cellar/go/1.24.2/libexec/pkg/tool/darwin_arm64/link

    \ -o $WORK/b001/exe/a.out \ -importcfg $WORK/b001/importcfg.link \ -buildmode=pie \ -buildid=UhZEhrBWBaa_sTRe6ewo/mt8PWloQVyET0ZBQuKBs/u8r6iV97MhPiAOxJs9eq/UhZEhrBWBaa_sTRe6ewo \ -extld=cc \ $WORK/b001/_pkg_.a _pkg_.a
 a.out
 出力する実行ファイル
 4まででまとめた_pkg_.a 

  34. © DMM cgo packageのコンパイルの順序
 1. CとGoの中間ファイル生成 (cgo) 2. Cコードのコンパイル 3.

    Goコードのコンパイル 4. まとめる 5. リンク 36 - _cgo_export.c
 - _cgo_export.h
 - _cgo_main.c
 - main.cgo2.c 
 - _cgo_gotypes.go 
 - main.cgo1.go
 
 _x0NN.o
 _pkg_.a
 _x0NN.o
 _pkg_.a
 _pkg_.a
 a.out

  35. © DMM 38 cgoでコンパイルする場合
 C ⇒ .o(オブジェクトファイル) , Go ⇒

    .a(静的ライブラリファイル) 
 .oと.aを連携させて実行ファイルを生成していた

  36. © DMM まとめ
 - cgo パッケージの仕組みについて調べた
 - オブジェクトファイルや静的ライブラリを介して『2言語のコードをまとめた実行ファ イル』を作っていた
 -

    まだまだ深掘りしたい点が出てくる面白いパッケージ
 - この他の深掘りポイント
 - ランタイム問題
 - メモリ管理とGCの動き
 40
  37. © DMM Cコードのコンパイル[8]
 1. cgo フェーズ cgo ツールが走って、 GoコードとCコードを橋渡しするための中間ファイルを生成する。 主に以下のファイルがこの段階で作られる:

    _cgo_export.h: Go側の関数を Cから呼び出すためのヘッダ ファイル _cgo_gotypes.go: C側で使う Go型を記述した Goファイル _cgo_main.c: main() のエントリーポイントをまとめる プロトタイプ宣言が列挙された Cのコード main.cgo1.go, main.cgo2.c: CとGoの相互呼び出しに必要なコード 2. Cコードのコンパイル .c ファイル(例:main.cgo2.c, _cgo_main.c)がコンパイルされて .o(オブジェクトファイル)になる。 例:main.cgo2.c → _x001.o, _cgo_main.c → _cgo_main.o 3. Goコードのコンパイル main.go や main.cgo1.go が .a ライブラリ(静的アーカイブ)にコンパイルされる。 例:_pkg_.a 4. リンク 全ての .o ファイルと .a ファイルをまとめてリンクして、最終的な実行ファイル(例: exe)ができる。 5. ./mainへ exe/a.outをあたかもgoがビルドした./mainかのように移動させる 42 TERM='dumb' cc -I . ... -o $WORK/b056/_x003.o -c gcc_context.c TERM='dumb' cc -I . ... -o $WORK/b056/_x004.o -c gcc_darwin_arm64.c TERM='dumb' cc -I . ... -o $WORK/b056/_x005.o -c gcc_libinit.c TERM='dumb' cc -I . ... -o $WORK/b056/_x006.o -c gcc_setenv.c TERM='dumb' cc -I . ... -o $WORK/b056/_x007.o -c gcc_stack_darwin.c TERM='dumb' cc -I . ... -o $WORK/b056/_x008.o -c gcc_traceback.c TERM='dumb' cc -I . ... -o $WORK/b056/_x009.o -c gcc_util.c TERM='dumb' cc -I . ... -o $WORK/b056/_x010.o -c gcc_arm64.S gcc_context.c: スレッドやゴルーチンのコンテキスト(状態)の切り替えに関連する処理 gcc_darwin_arm64.c: macOS (darwin) の ARM64 環境固有の処理 gcc_libinit.c: Cライブラリ側の初期化に関連する処理 gcc_setenv.c: 環境変数の設定に関連する処理 gcc_stack_darwin.c: C言語側のスタック管理に関連する処理 (macOS固有) gcc_traceback.c: C言語関数呼び出しを含むスタックトレース(エラー発生時の呼び出し履歴)の取得に関連する処理 gcc_util.c: その他のユーティリティ関数 gcc_arm64.S: ARM64アーキテクチャ固有のアセンブリコード [11] https://go.dev/src/runtime/cgo/
  38. © DMM .a, .o, .so, .Sの違い
 43 - .o =

    オブジェクトファイル
 単一のソースコードファイルから生成される機械コードとリンク用の関数名や変数名を含むファイル 
 - .a = アーカイブファイル
 一つ以上のオブジェクトファイル (.o)をまとめたファイル、中身は.oと同様にリンク用の変数名や機械語を含 む
 - .so = 共有オブジェクト
 プログラムの実行時にメモリに読み込まれ、複数のプログラムから共有して利用されることを目的とするファ イル
 - .S = アセンブリ
 コンピュータのCPUが直接理解できる機械語に近い、低レベルな命令. gcc_arm64.Sは条件コンパイルにプリ プロセッサが必要なので大文字(#if define) [8][9] 
 [8] file "gcc_arm64.S" [9] 玄箱PRO、Linux ZaurusでARMアセンブリプログラミング
  39. © DMM cgo packageのコンパイルの順序列挙
 1. cgo フェーズ cgo ツールが走って、 GoコードとCコードを橋渡しするための中間ファイルを生成する。

    主に以下のファイルがこの段階で作られる: _cgo_export.h: Go側の関数をCから呼び出すためのヘッダファイル。 _cgo_gotypes.go: C側で使うGo型を記述したGoファイル。 _cgo_main.c: main() のエントリーポイントをまとめる Cのコード。 main.cgo1.go, main.cgo2.c: CとGoの相互呼び出しに必要なコード。 2. Cコードのコンパイル .c ファイル(例:main.cgo2.c, _cgo_main.c)がコンパイルされて .o(オブジェクトファイル)になる。 例:main.cgo2.c → _x001.o, _cgo_main.c → _cgo_main.o 3. Goコードのコンパイル main.go や main.cgo1.go が .a ライブラリ(静的アーカイブ)にコンパイルされる。 例:_pkg_.a 4. リンク 全ての .o ファイルと .a ファイルをまとめてリンクして、最終的な実行ファイル(例: exe)ができる。 5. ./mainへ exe/a.outをあたかもgoがビルドした./mainかのように移動させる 45