You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go生成C共享库后释放C.CString内存时代码不可达问题排查

Go C共享库Python调用时内存释放崩溃问题

我用Go编写C共享库供Python调用,功能已实现,但调用C.free(unsafe.Pointer(result))后,Go与Python后续代码均无法执行,Python在free函数处直接停止。

Go端代码(带日志)

import (
    "fmt"
    "runtime/debug"
    "sync/atomic"
    "unsafe"
)

// #include <stdbool.h>
// #include <stdint.h>
// #include <stdio.h>
// #include <stdlib.h>
import "C"

var allocCount int32

//export Test
func Test(s *C.char) *C.char {
    goS := C.GoString(s)
    res := goS + "!"
    fmt.Println(res)
    atomic.AddInt32(&allocCount, 1)
    return C.CString(res)
}

//export FreeResult
func FreeResult(result *C.char) C.bool {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("Recovered in FreeResult: %v\n", r)
            fmt.Printf("Stack trace:\n%s\n", debug.Stack())
        }
    }()

    fmt.Printf("Entered FreeResult\n")
    fmt.Printf("Result: %v\n", result)
    fmt.Printf("Allocated memory before freeing: %d\n", atomic.LoadInt32(&allocCount))

    if result != nil {
        C.free(unsafe.Pointer(result))
        atomic.AddInt32(&allocCount, -1)
        fmt.Println("Freed result")
        fmt.Println("Allocated memory after freeing: ", atomic.LoadInt32(&allocCount))
        return C.bool(true)
    } else {
        fmt.Println("Error: result is nil")
        return C.bool(false)
    }
}

//export GetAllocCount
func GetAllocCount() C.int {
    return C.int(atomic.LoadInt32(&allocCount))
}

func main() {}

Python端调用代码

import ctypes

library = ctypes.cdll.LoadLibrary('./lib.dll')

test = library.Test
test.argtypes = [ctypes.c_char_p]
test.restype = ctypes.c_char_p

free_result = library.FreeResult
free_result.argtypes = [ctypes.c_char_p]
free_result.restype = ctypes.c_bool

get_alloc_count = library.GetAllocCount
get_alloc_count.argtypes = []
get_alloc_count.restype = ctypes.c_int

res = test(b"Hello")
print(f"Result from test: {res}")
print(f"Alloc count after test: {get_alloc_count()}")
print(f"Pointer value before freeing: {res}")
res = free_result(res)
print(f"Free result: {res}")
print(f"Alloc count after free: {get_alloc_count()}")

编译命令(Windows)

go build -buildmode=c-shared -o lib.dll lib.go

实际运行输出

Hello!
Result from test: b'Hello!'
Alloc count after test: 1
Pointer value before freeing: b'Hello!'
Entered FreeResult
Result: 0x1e52441b950
Allocated memory before freeing: 1

问题原因

Windows下不同C runtime之间的内存分配/释放不兼容:

  • 若用MinGW编译Go共享库,其使用MinGW的C runtime(libgcc),C.CString通过该runtime的malloc分配内存。
  • Python默认使用MSVC的C runtime(msvcrt.dll),调用的C.free是MSVC runtime的函数。
    跨runtime释放内存会直接触发崩溃,导致后续代码无法执行。

解决方案

方案1:使用MSVC编译Go共享库

确保Go和Python使用相同的C runtime:

  1. 打开Visual Studio对应的命令提示符(如x64 Native Tools Command Prompt)。
  2. 执行编译命令:
go build -buildmode=c-shared -o lib.dll lib.go

编译后的库使用MSVC runtime,和Python的runtime匹配,C.CString分配的内存可正常用C.free释放。

方案2:统一用C标准库分配内存

修改Go的Test函数,直接调用C的malloc分配内存,确保内存分配和释放使用同一runtime:

//export Test
func Test(s *C.char) *C.char {
    goS := C.GoString(s)
    res := goS + "!"
    fmt.Println(res)
    
    // 用C的malloc分配足够内存(包含结束符)
    bufSize := C.size_t(len(res) + 1)
    cPtr := C.malloc(bufSize)
    if cPtr == nil {
        return nil
    }
    
    // 将Go字符串复制到C内存
    C.memcpy(cPtr, unsafe.Pointer(C.CString(res)), bufSize)
    // 释放C.CString临时分配的内存
    C.free(unsafe.Pointer(C.CString(res)))
    
    atomic.AddInt32(&allocCount, 1)
    return (*C.char)(cPtr)
}

这种方式下,内存由C标准库分配,无论用MinGW还是MSVC编译,只要FreeResult中调用的C.free是同一库的函数,就能正常释放。


内容的提问来源于stack exchange,提问作者Stanley Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 17:54:52