いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-654 (cgoメモ-36)(structs.HostLayout)

関連記事

Goメモ-502 (cgoメモ-01)(cgoヘッダ) - いろいろ備忘録日記

Goメモ-506 (cgoメモ-02)(cgoヘッダ) - いろいろ備忘録日記

Goメモ-507 (cgoメモ-03)(C.int) - いろいろ備忘録日記

Goメモ-508 (cgoメモ-04)(C言語の構造体) - いろいろ備忘録日記

Goメモ-509 (cgoメモ-05)(C.CString)(Cの文字列) - いろいろ備忘録日記

Goメモ-510 (cgoメモ-06)(C.GoString)(Cの文字列をGoの文字列へ) - いろいろ備忘録日記

Goメモ-511 (cgoメモ-07)(C.CBytes)([]byteをCのバイト列に) - いろいろ備忘録日記

Goメモ-512 (cgoメモ-08)(C.GoBytes)(Cのバイト列をGoの[]byteへ) - いろいろ備忘録日記

Goメモ-514 (cgoメモ-09)(C.GoStringN)(C.GoStringのサイズ指定版) - いろいろ備忘録日記

Goメモ-515 (cgoメモ-10)([]byteを(void *)へ変換) - いろいろ備忘録日記

Goメモ-516 (cgoメモ-11)([]byteを(char *)へ変換) - いろいろ備忘録日記

Goメモ-518 (cgoメモ-12)(Cのmallocをcgo経由で呼び出し) - いろいろ備忘録日記

Goメモ-519 (cgoメモ-13)(ポインタ演算) - いろいろ備忘録日記

Goメモ-520 (cgoメモ-14)(Goの関数をCの世界に公開 (export)) - いろいろ備忘録日記

Goメモ-522 (cgoメモ-15)(Goでsoファイルを作成してC言語から呼び出し) - いろいろ備忘録日記

Goメモ-525 (cgoメモ-16)(C側にて関数ポインタを引数に要求する関数にGo側で定義した関数を設定) - いろいろ備忘録日記

Goメモ-526 (cgoメモ-17)(cgoとdlopen関数を使って既存ライブラリの呼び出しをフックする) - いろいろ備忘録日記

Goメモ-527 (cgoメモ-18)(cgoを利用している場合のinit関数について) - いろいろ備忘録日記

Goメモ-528 (cgoメモ-19)(cgoを利用して作成したsoファイル経由でのinit関数の呼び出し) - いろいろ備忘録日記

Goメモ-529 (cgoメモ-20)(C言語のNULLをcgoから渡す) - いろいろ備忘録日記

Goメモ-530 (cgoメモ-21)(CGOヘッダーで指定出来るCFLAGS, LDFLAGS) - いろいろ備忘録日記

Goメモ-531 (cgoメモ-22)(CGOで利用するコンパイラを変更する) - いろいろ備忘録日記

Goメモ-532 (cgoメモ-23)(CGOヘッダで使えるSRCDIR変数) - いろいろ備忘録日記

Goメモ-534 (cgoメモ-24)(C側の構造体にて固定要素数の文字配列をGo側で文字列に変換) - いろいろ備忘録日記

Goメモ-535 (cgoメモ-25)(cgo.Handleを用いてCとGoの間で値をやり取りする) - いろいろ備忘録日記

Goメモ-536 (cgoメモ-26)(C側の構造体をGo側で利用する) - いろいろ備忘録日記

Goメモ-537 (cgoメモ-27)(Go側でCの構造体のサイズを知る方法) - いろいろ備忘録日記

Goメモ-539 (cgoメモ-28)(Go側でCの文字列リスト(**char)を扱う) - いろいろ備忘録日記

Goメモ-623 (cgoメモ-29)(memcpyの呼び出し) - いろいろ備忘録日記

Goメモ-627 (cgoメモ-30)(Goでビルドした静的ライブラリのC言語からの利用) - いろいろ備忘録日記

Goメモ-628 (cgoメモ-31)(Cの関数からGoの関数をコールバックする) - いろいろ備忘録日記

Goメモ-629 (cgoメモ-32)(Cの関数からGoの関数をコールバックする(2)) - いろいろ備忘録日記

Goメモ-630 (cgoメモ-33)(LD_PRELOAD を利用したモック) - いろいろ備忘録日記

Goメモ-641 (cgoメモ-34)(C側で定義された配列の操作)(アクセサ関数を用意) - いろいろ備忘録日記

devlights.hatenablog.com

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

これまでのサンプルは以下のリポジトリにアップしています。

github.com

今回は structs.HostLayoutとcgoの関係について。

structs.HostLayout は、Go 1.23で導入されたC言語の構造体とGoの構造体のメモリレイアウトを完全に一致させるためのマーカー構造体です。

構造体定義にて、これを1個目のフィールドとして _ で定義しておくことで、Goコンパイラに対して「この構造体は、このプラットフォームのCコンパイラと同じルールでレイアウトしなさい」という指示することになります。つまり、CのABIが保証されるということになります。なんでこんな構造体が追加されたかというと、従来、Goの構造体レイアウトはコンパイラの最適化に依存しており、将来的なGoのバージョンアップにおいてフィールドの並べ替えやパディングの変更が行われないという保証はないからです。基本的に同じ並びになるはずですが、保証はされていないから。

cgo上で定義したCの構造体にはデフォルトで適用されます。自分で利用することも出来ますので、cgoを使ってCの構造体をGoの構造体にそのままキャストしたい場合は structs.HostLayout を付与しておいたほうが良いです。

使い方はめっちゃ簡単で以下のようにするだけです。

type (
    Go_Struct struct {
        _    structs.HostLayout // 「ホストプラットフォームのCコンパイラと同じルールでレイアウトせよ」と指示
        val1 uint8              // 1byte
        val2 int32              // 4byte
        val3 int64              // 8byte
    }
)

最初のメンバーとして _ structs.HostLayout って入れるだけです。

サンプル

main.go

package main

/*
#cgo CFLAGS: -std=c99

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
   uint8_t val1; // 1byte
                 // パディング 3byte
   int32_t val2; // 4byte
                 // パディング 0byte (8バイト境界になるため)
   int64_t val3; // 8byte
} c_struct;

c_struct* new_c_struct() {
   c_struct *v = (c_struct *)malloc(sizeof(c_struct));
   if (v == NULL) {
       return NULL;
   }

   v->val1 = 1;
   v->val2 = 2;
   v->val3 = 3;

   return v;
}

void free_c_struct(c_struct *v) {
   free(v);
}
*/
import "C"
import (
    "fmt"
    "structs"
    "unsafe"
)

type (
    Go_Struct struct {
        _    structs.HostLayout // 「ホストプラットフォームのCコンパイラと同じルールでレイアウトせよ」と指示
        val1 uint8              // 1byte
        val2 int32              // 4byte
        val3 int64              // 8byte
    }
)

func main() {
    //
    // Cと同じ形の構造体をGo側にも定義し、その構造体にstructs.HostLayoutを適用しておき
    // C側とGo側で全く同じように見えるかどうかを確認する。
    //
    // structs.HostLayoutは、Go1.23で追加された。
    //
    var (
        cPtr  *C.c_struct
        goPtr *Go_Struct
    )
    cPtr = C.new_c_struct()
    defer C.free_c_struct(cPtr)

    // Goのポインタとしてキャスト
    goPtr = (*Go_Struct)(unsafe.Pointer(cPtr))

    // 値を確認
    fmt.Printf("[C ] val1=%v, val2=%v, val3=%v\n", cPtr.val1, cPtr.val2, cPtr.val3)
    fmt.Printf("[Go] val1=%v, val2=%v, val3=%v\n", goPtr.val1, goPtr.val2, goPtr.val3)

    // パディングの確認
    var (
        addrVal1 = uintptr(unsafe.Pointer(&goPtr.val1))
        addrVal2 = uintptr(unsafe.Pointer(&goPtr.val2))
        addrVal3 = uintptr(unsafe.Pointer(&goPtr.val3))
        val1Size = unsafe.Sizeof(goPtr.val1)
        val2Size = unsafe.Sizeof(goPtr.val2)
        padding1 = addrVal2 - addrVal1 - val1Size
        padding2 = addrVal3 - addrVal2 - val2Size
    )
    fmt.Printf("アドレス:  (1) %#x, (2) %#x, (3) %#x\n", addrVal1, addrVal2, addrVal3)
    fmt.Printf("パディング: (1) %v bytes, (2) %v bytes\n", padding1, padding2)
}

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - go run main.go

実行

$ task
task: [default] go run main.go
[C ] val1=1, val2=2, val3=3
[Go] val1=1, val2=2, val3=3
アドレス:  (1) 0x1ee91050, (2) 0x1ee91054, (3) 0x1ee91058
パディング: (1) 3 bytes, (2) 0 bytes

参考情報

個人的Goのおすすめ書籍

個人的に読んでとても勉強になった書籍さんたちです。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。