第一章:Go调用C代码的底层机制与编译模型
Go 通过 cgo 工具实现与 C 代码的无缝互操作,其本质并非运行时动态链接,而是在编译阶段深度整合 C 和 Go 的构建流程。整个过程由 go build 隐式驱动,当源文件中包含 import "C" 伪包且存在紧邻的 C 注释块(如 /* #include <stdio.h> */)时,cgo 自动介入解析、预处理、生成绑定代码并协调双编译器协同工作。
cgo 的三阶段编译流水线
- 解析与代码生成:
cgo扫描import "C"上方的 C 注释块,提取头文件包含、类型定义和函数声明;据此生成_cgo_gotypes.go(含 Go 可见的封装类型与函数签名)和_cgo_main.c(用于验证 C 符号可见性)。 - C 编译:调用系统 C 编译器(如
gcc或clang)编译所有 C 源码及_cgo_main.c,产出目标文件(.o)和静态库(若需)。 - 链接整合:Go 链接器将 Go 目标文件与 C 目标文件/库合并,最终生成单一可执行文件或共享库——C 代码被完全静态链接,无运行时
dlopen开销。
关键约束与内存边界
- C 代码中分配的内存(如
malloc)必须由 C 函数释放,Go 的runtime不管理其生命周期; - Go 字符串和切片传入 C 前需显式转换为
*C.char或unsafe.Pointer,且需确保底层数据在 C 调用期间不被 GC 移动(常配合C.CString+C.free使用); - 所有 C 类型在 Go 中均映射为
C.前缀标识(如C.size_t,C.int),不可直接使用原生 Go 类型替代。
快速验证示例
# 创建 hello.go
echo 'package main
/*
#include <stdio.h>
void say_hello() { printf("Hello from C!\\n"); }
*/
import "C"
func main() { C.say_hello() }' > hello.go
# 构建并运行(cgo 自动触发)
go run hello.go # 输出:Hello from C!
该模型使 Go 程序能安全复用成熟 C 生态(如 OpenSSL、SQLite),同时保持二进制分发简洁性——最终产物不含外部 .so 依赖,仅需系统级 C 运行时(如 libc)。
第二章:Cgo编译环境配置的5大致命陷阱
2.1 CGO_ENABLED=0误设导致静态链接失效的复现与修复
当项目依赖 net 或 os/user 等需调用系统解析器的包时,错误设置 CGO_ENABLED=0 会导致 DNS 解析失败或用户查找崩溃——因 Go 会退回到纯 Go 实现,但部分行为(如 getpwuid)无法静态模拟。
复现步骤
- 执行
CGO_ENABLED=0 go build -o app . - 运行
./app并调用user.Current()→ panic:user: lookup current user: no such file or directory
关键差异对比
| 场景 | DNS 解析方式 | /etc/passwd 读取 |
链接类型 |
|---|---|---|---|
CGO_ENABLED=1 |
libc getaddrinfo |
libc getpwuid |
动态 |
CGO_ENABLED=0 |
纯 Go DNS stub | 仅尝试 /etc/passwd(若不存在则失败) |
静态 |
# 正确构建:保留 cgo 以支持系统调用,同时强制静态链接
CGO_ENABLED=1 go build -ldflags '-extldflags "-static"' -o app .
此命令启用 cgo(保障
net,user等包功能),并通过-extldflags "-static"指示gcc静态链接 libc —— 实现功能完整 + 无运行时依赖的平衡。
graph TD A[CGO_ENABLED=0] –> B[禁用所有 libc 调用] B –> C[纯 Go 替代逻辑] C –> D[缺失 /etc/passwd 时 user.Current 失败] A –> E[DNS 使用 UDP stub,无 /etc/resolv.conf 则超时]
2.2 CFLAGS/LDFLAGS未同步Go构建标签引发的符号未定义崩溃
当 Go 程序通过 cgo 调用 C 代码,并依赖外部库(如 OpenSSL)时,若构建标签(如 -tags=openssl111)与 CFLAGS/LDFLAGS 中指定的头文件路径、库版本不一致,链接阶段将成功,但运行时因符号解析失败而 panic。
构建参数错配典型场景
- Go 构建标签启用
openssl111分支逻辑 - 但
CFLAGS指向 OpenSSL 3.0 头文件,LDFLAGS链接libcrypto.so.3 - 导致 Go 代码调用
SSL_CTX_new()(存在于 1.1.1,签名兼容),却实际加载了 OpenSSL 3.0 的符号表——该函数在 3.0 中被重命名为SSL_CTX_new_ex,原符号未导出。
关键诊断命令
# 检查二进制实际引用的符号
nm -D your_binary | grep SSL_CTX_new
# 输出为空 → 符号未解析
此命令验证运行时符号是否存在。
nm -D列出动态符号表;若SSL_CTX_new缺失,说明链接器未正确绑定——根源是头文件(声明)与库(定义)版本割裂。
同步校验表
| 维度 | 正确做法 | 错误示例 |
|---|---|---|
| 构建标签 | -tags=openssl111 |
-tags=openssl3(但无对应实现) |
| CFLAGS | -I/usr/include/openssl-1.1.1 |
-I/usr/include/openssl(指向 3.x) |
| LDFLAGS | -L/usr/lib/openssl-1.1.1 -lcrypto |
-lcrypto(默认链接 libcrypto.so.3) |
graph TD
A[Go源码含//go:build openssl111] --> B[cgo识别标签→编译openssl111分支]
B --> C[CFLAGS指定1.1.1头路径→编译通过]
C --> D[LDFLAGS链接libcrypto.so.3→链接通过]
D --> E[运行时符号查找失败→SIGSEGV]
2.3 头文件路径嵌套引用缺失-I参数导致的#include失败链式报错
当项目存在多层头文件依赖(如 main.cpp → utils.h → core/config.h),而编译时未通过 -I 指定包含路径,预处理器将无法解析嵌套 #include,触发链式报错——首个错误常掩盖真实根源。
典型错误现象
main.cpp:2:10: fatal error: utils.h: No such file or directory
#include "utils.h"
^~~~~~~~~
但若 utils.h 实际位于 ./include/,且其内部含 #include "core/config.h",则缺失 -I./include 将使两层引用同时失效。
编译参数对比表
| 场景 | 编译命令 | 结果 |
|---|---|---|
缺失 -I |
g++ main.cpp |
找不到 utils.h |
| 正确配置 | g++ -I./include main.cpp |
成功解析全部嵌套 |
链式失败流程
graph TD
A[main.cpp #include "utils.h"] --> B{预处理器查找 utils.h}
B -- -I未指定 --> C[失败:No such file]
B -- -I./include --> D[找到 utils.h]
D --> E[解析其 #include "core/config.h"]
E --> F{查找 core/config.h}
F -- ./include 在搜索路径中 --> G[成功]
2.4 混合使用#cgo LDFLAGS与//export时链接顺序颠倒引发的undefined reference
链接器视角下的符号依赖链
当 //export 声明的 Go 函数被 C 代码调用,而同时通过 #cgo LDFLAGS 链接外部静态库(如 -lmylib)时,gcc 默认按命令行从左到右解析库依赖。若 libmylib.a 内部引用了 MyGoCallback(即 //export MyGoCallback 导出的符号),但 -lmylib 出现在 -lgobuild(含 Go 符号表)之前,链接器将跳过未解析符号,最终报 undefined reference to 'MyGoCallback'。
典型错误配置示例
/*
#cgo LDFLAGS: -L./lib -lmylib -lgobuild
#include "mylib.h"
*/
import "C"
//export MyGoCallback
func MyGoCallback() { /* ... */ }
🔍 逻辑分析:
-lmylib在前 → 链接器扫描libmylib.a时MyGoCallback尚未注册进符号表;-lgobuild在后 → 已错过解析时机。cgo不自动重排LDFLAGS顺序。
正确顺序规则
- ✅ 必须将含 Go 导出符号的构建单元(如
-lgobuild或隐式 Go object)置于所有依赖它的库之前 - ❌ 禁止在
LDFLAGS中将-lxxx放在 Go 相关链接项左侧
| 位置 | 参数片段 | 是否安全 | 原因 |
|---|---|---|---|
| 左侧 | -lmylib -lgobuild |
❌ | mylib 提前扫描,未见 MyGoCallback |
| 右侧 | -lgobuild -lmylib |
✅ | Go 符号先注册,mylib 可成功解析引用 |
修复方案流程
graph TD
A[编写 //export 函数] --> B[生成 Go object 符号表]
B --> C[链接时将 -lgobuild 置于最左]
C --> D[再链接 -lmylib 等依赖库]
D --> E[符号解析成功]
2.5 macOS上Clang默认启用Werror+隐式函数声明警告触发的静默编译中断
macOS Ventura 及后续系统中,Xcode 自带 Clang 默认启用 -Werror=implicit-function-declaration(即使未显式指定 -Werror),导致未声明即调用的 C 函数直接终止编译。
隐式声明为何被严格拦截?
C99 起已废弃隐式函数声明,Clang 将其视为潜在内存/ABI 危险源。例如:
// main.c
int main() {
return printf("hello"); // ❌ 未 #include <stdio.h>
}
逻辑分析:Clang 在语义分析阶段检测到
printf无可见声明,触发implicit-function-declaration警告;因该警告被默认映射为 error(见clang -cc1 -help | grep "error:"),编译立即退出,无.o输出。
关键行为对比表
| 系统环境 | 是否默认 -Werror=implicit-function-declaration |
典型表现 |
|---|---|---|
| macOS (Xcode 14+) | ✅(内置 driver 策略) | error: implicitly declaring library function 'printf' |
| Ubuntu 22.04 (clang-14) | ❌(需显式加 -Werror=...) |
仅 warning,继续编译 |
缓解路径选择
- ✅ 推荐:补全头文件(
#include <stdio.h>) - ⚠️ 临时绕过:
clang -Wno-error=implicit-function-declaration - ❌ 禁用警告本身:
-Wno-implicit-function-declaration(削弱安全检查)
第三章:C代码内存生命周期管理的三大越界雷区
3.1 Go字符串转C字符串后未显式free导致的堆内存泄漏与崩溃复现
Cgo字符串转换的典型误用
// ❌ 危险:C.CString返回的指针未释放
func badConvert(s string) *C.char {
return C.CString(s) // 分配在C堆,Go GC不管理
}
C.CString 在C堆分配内存并复制Go字符串内容,返回 *C.char;必须配对调用 C.free(unsafe.Pointer(p)),否则永久泄漏。
内存泄漏链路示意
graph TD
A[Go string] -->|C.CString| B[C heap malloc]
B --> C[返回 *C.char]
C --> D[Go函数返回后无free]
D --> E[持续累积 → OOM/崩溃]
关键修复模式
- ✅ 正确做法:作用域内配对释放
- ✅ 或使用
defer C.free(unsafe.Pointer(cstr)) - ❌ 禁止跨goroutine传递未托管C指针
| 场景 | 是否需手动free | 风险等级 |
|---|---|---|
| C.CString结果 | 是 | ⚠️ 高 |
| C.GoString结果 | 否(返回Go字符串) | ✅ 安全 |
3.2 C回调函数中非法访问已回收Go内存(如slice.Data)的段错误现场还原
当Go代码通过C.export导出函数供C调用,且回调中持有[]byte的&slice[0]指针时,若Go侧slice被GC回收,C端继续解引用将触发SIGSEGV。
数据同步机制
Go需确保内存生命周期覆盖C回调执行期:
- ❌ 错误:直接传
&s[0]且无runtime.KeepAlive(s) - ✅ 正确:用
C.CBytes()复制数据,或runtime.Pinner(Go 1.22+)固定内存
// 危险示例:s可能在回调前被回收
func ExportedCB() {
s := make([]byte, 1024)
C.c_callback((*C.char)(unsafe.Pointer(&s[0])), C.int(len(s)))
// s在此处离开作用域 → 可能被GC回收
runtime.KeepAlive(s) // 必须显式延长生命周期!
}
&s[0]仅在s存活时有效;KeepAlive(s)阻止编译器优化掉s的存活期,确保C回调期间底层数组不被回收。
典型崩溃链路
| 阶段 | 行为 |
|---|---|
| Go侧调用 | 传递&slice[0]指针 |
| GC触发 | 回收slice底层数组 |
| C回调执行 | 解引用已释放地址 → SIGSEGV |
graph TD
A[Go创建slice] --> B[取&s[0]传入C]
B --> C[C回调中读写该地址]
A --> D[Go函数返回→slice逃逸分析失败]
D --> E[GC回收底层数组]
E --> C
C --> F[段错误]
3.3 使用C.malloc分配内存后由Go GC误回收引发的use-after-free核心转储
当 Go 代码通过 C.malloc 分配内存但未显式告知运行时该内存需被追踪时,Go GC 可能错误判定其为不可达对象并回收——此时若 Go 代码后续仍通过 *C.char 等指针访问,即触发 use-after-free。
根本原因:GC 可见性缺失
Go 的垃圾收集器仅管理 Go 堆内存;C.malloc 返回的指针属于 C 堆,无 Go 指针引用时,GC 无法感知其存活依赖。
典型错误模式
p := C.CString("hello") // 实际调用 C.malloc + strcpy
// ... 未调用 C.free,也未保留 Go 指针引用
runtime.KeepAlive(p) // ❌ 无效:p 是 C 指针,非 Go 对象
C.CString内部调用C.malloc,返回*C.char。该值本身是 Go 中的 uintptr 衍生指针,不构成对 C 堆内存的 GC 引用;runtime.KeepAlive(p)仅延长p变量生命周期,不阻止 GC 回收其所指向的 C 堆块。
正确方案对比
| 方法 | 是否防止 GC 误回收 | 说明 |
|---|---|---|
C.free(p) 显式释放 |
✅(主动释放) | 需严格配对,且不能晚于最后一次使用 |
C.CBytes + unsafe.Slice + runtime.KeepAlive |
❌ 仍危险 | C.CBytes 同样基于 C.malloc,Go 无引用链 |
unsafe.Pointer 转为 Go 切片并持有 |
✅(需配合 runtime.KeepAlive) |
仅当 unsafe.Slice 创建的切片被 Go 变量持有时,GC 才视为活跃引用 |
graph TD
A[Go 调用 C.malloc] --> B[C 堆分配内存]
B --> C[返回 *C.char]
C --> D[无 Go 指针引用该内存]
D --> E[GC 认定不可达]
E --> F[回收 C 堆块]
F --> G[后续解引用 → SIGSEGV]
第四章:跨语言ABI与类型系统的四大不兼容陷阱
4.1 C结构体字段对齐差异(attribute((packed)) vs Go struct tag)导致的内存踩踏
C语言默认按自然对齐(如int对齐到4字节边界),而Go通过//go:packed或字段tag(如json:"x" align:"1")控制布局——但Go原生不支持任意字节对齐,仅能通过unsafe+手动偏移模拟。
对齐行为对比
| 语言 | 关键机制 | 实际对齐效果 | 风险点 |
|---|---|---|---|
| C | __attribute__((packed)) |
强制1字节对齐,禁用填充 | CPU异常(ARM未对齐访问)、缓存行撕裂 |
| Go | struct{ x uint32; y byte } |
自动插入3字节填充 | 与C packed结构二进制不兼容 |
// C: packed结构 —— 占用5字节
typedef struct __attribute__((packed)) {
uint32_t a; // offset 0
uint8_t b; // offset 4 ← 紧邻,无填充
} c_packed_t;
逻辑分析:
a占4字节(0–3),b紧接在offset 4;若该结构体嵌入更大数组,后续字段地址可能落在非对齐边界,触发ARM架构的Alignment fault。
// Go: 无法等效表达 —— 编译器强制插入填充
type GoStruct struct {
A uint32 `align:"1"` // ❌ 无效tag,Go忽略
B byte
} // 实际布局:A(0–3), padding(4–7), B(8)
参数说明:Go struct tag中
align、pack等均为非法tag,仅json/xml等反射标签生效;真实控制需用unsafe.Offsetof+unsafe.Slice手工构造。
4.2 C枚举值在不同平台符号扩展不一致引发的int32/int64类型断言panic
C标准未规定enum底层类型的符号性,导致gcc(x86_64)常选int32_t,而aarch64或clang -target arm64-apple-darwin可能默认使用int64_t——当枚举值为负(如ENUM_ERR = -1),跨平台传递至Go时触发类型断言失败。
符号扩展差异实证
// enum_def.h
enum Status { OK = 0, ERROR = -1 };
// 编译后:x86_64 → (int32_t)-1 → 0xFFFFFFFF
// aarch64 → (int64_t)-1 → 0xFFFFFFFFFFFFFFFF
该差异使C.enum_Status(-1)在CGO中被解释为int64时高位填充全1,但Go侧若强制int32(x)断言,将panic:interface conversion: interface {} is int64, not int32。
典型错误链路
- CGO导出函数返回
C.enum_Status - Go调用方执行
status := int32(C.get_status()) - 在ARM64平台因底层
C.int映射为int64,触发类型不匹配panic
| 平台 | sizeof(enum) |
C.int映射 |
断言风险 |
|---|---|---|---|
| x86_64 Linux | 4 | int32 |
低 |
| Apple Silicon | 8 | int64 |
高 |
graph TD
A[C.enum_Status ERROR] --> B{x86_64?}
B -->|Yes| C[int32(-1) → OK]
B -->|No| D[int64(-1) → panic on int32 cast]
4.3 C函数指针与Go闭包混用时栈帧逃逸失败的SIGSEGV复现与规避方案
当Go闭包作为C.function_ptr参数传入C代码时,若闭包捕获了局部变量且未显式逃逸,其栈帧可能在C回调执行前被回收。
复现关键代码
// cgo.h
typedef void (*cb_t)(void);
extern cb_t g_callback;
void trigger_cb(void);
// main.go
/*
#cgo LDFLAGS: -ldl
#include "cgo.h"
*/
import "C"
import "unsafe"
func crashDemo() {
x := 42
cb := func() { println(x) } // 闭包捕获x,但未强制逃逸
C.g_callback = (*C.cb_t)(unsafe.Pointer(&cb)) // 危险:栈上闭包地址被C持有
C.trigger_cb() // SIGSEGV:访问已释放栈帧
}
逻辑分析:
cb变量位于Go goroutine栈上,未被new或全局引用,GC无法感知C侧持有其地址;trigger_cb返回后栈帧回收,C回调触发时访问野指针。
规避方案对比
| 方案 | 是否安全 | 原理 | 开销 |
|---|---|---|---|
runtime.KeepAlive(cb) |
❌ 无效 | 仅延长局部变量生命周期至作用域末尾,不阻止栈帧回收 | 无 |
*new(func()) = cb |
✅ 推荐 | 强制闭包逃逸到堆,地址稳定 | 堆分配+GC压力 |
C.register_cb(C.CString(...)) + 全局map |
✅ 可控 | 用ID间接引用堆上闭包,配合sync.Map管理生命周期 | 查表延迟 |
安全调用模式
var callbacks sync.Map // key: uintptr, value: interface{}
func safeRegister(cb func()) uintptr {
ptr := unsafe.Pointer(new(func())) // 堆分配
*(*func())(ptr) = cb
callbacks.Store(ptr, cb)
return uintptr(ptr)
}
// C侧通过uintptr回调,Go侧用callbacks.Load还原
此方式确保闭包生命周期独立于调用栈,彻底规避栈帧提前回收导致的SIGSEGV。
4.4 _cgo_runtime_cgocall栈保护机制被禁用(-gcflags=”-gcno”)后的协程栈溢出崩溃
当使用 -gcflags="-gcno" 禁用 Go 编译器的栈保护(包括 _cgo_runtime_cgocall 的栈溢出检查)时,CGO 调用链中丢失关键防护层。
危险调用模式示例
// #include <stdlib.h>
import "C"
func dangerousCall() {
C.malloc(1 << 30) // 请求 1GB 内存,触发栈帧异常增长
}
此调用绕过
runtime.checkgo栈边界校验,_cgo_runtime_cgocall不再插入stackcheck指令,导致 goroutine 栈无预警溢出至相邻内存页。
影响对比表
| 场景 | 栈保护启用 | -gcno 禁用后 |
|---|---|---|
| CGO 调用深度 > 200 | panic: stack overflow | SIGSEGV(无提示崩溃) |
| malloc 大对象 | 触发 runtime.morestack | 直接覆盖 goroutine 结构体 |
崩溃路径示意
graph TD
A[goroutine 执行 CGO 函数] --> B{_cgo_runtime_cgocall}
B -- -gcno --> C[跳过 stackguard0 检查]
C --> D[栈指针越过 guard page]
D --> E[SIGSEGV / corrupted g struct]
第五章:构建可维护、可测试、可交付的Cgo工程范式
项目结构标准化实践
一个生产级 Cgo 工程应严格区分 Go 层与 C 层职责。推荐采用以下目录布局:
/cmd/ # 主程序入口(纯 Go)
/internal/ # 内部业务逻辑(Go,不可导出)
/pkg/ # 可复用组件(Go + Cgo 封装层)
/c/ # C 源码与头文件(.c/.h/.inc)
/cgo/ # CGO 构建辅助(build.sh, cgo_flags.h)
/testdata/ # C 单元测试输入/输出样本
/go.mod # 显式 require github.com/yourorg/cgo-bridge v0.3.1
该结构确保 go build 不意外编译 C 代码,所有 C 依赖通过 #cgo 指令显式声明。
CGO 构建可重现性保障
在 cgo/ 目录下维护 cgo_flags.h,统一管理编译器标志:
// cgo/flags.h
#ifndef CGO_FLAGS_H
#define CGO_FLAGS_H
#pragma GCC diagnostic ignored "-Wimplicit-function-declaration"
#include <stdlib.h>
#include <string.h>
#endif
并在 Go 文件中引用:
/*
#cgo CFLAGS: -I${SRCDIR}/c -I${SRCDIR}/cgo -std=c99
#cgo LDFLAGS: -L${SRCDIR}/c -lmycrypto -lm
#include "cgo/flags.h"
#include "c/mycrypto.h"
*/
import "C"
C 层单元测试自动化集成
使用 cmake + ctest 构建 C 单元测试,并通过 make test-c 触发: |
测试项 | 命令 | 覆盖率目标 |
|---|---|---|---|
| AES 加密验证 | ./c/test/aes_test --verbose |
≥98% | |
| 内存泄漏检测 | valgrind --leak-check=full ./c/test/mem_test |
零报告 | |
| 边界值压力测试 | ./c/test/boundary_test -n 100000 |
全通过 |
Go 层接口契约测试
定义 pkg/crypto/api.go 中的 Encryptor 接口,并为 C 实现编写契约测试:
func TestEncryptor_Contract(t *testing.T) {
impl := NewCImpl() // 返回 *CImpl,实现 Encryptor
tests := []struct{ input, expected string }{
{"hello", "a1b2c3d4..."},
{"", "e5f6g7h8..."},
}
for _, tt := range tests {
out, err := impl.Encrypt([]byte(tt.input))
require.NoError(t, err)
require.Equal(t, tt.expected, hex.EncodeToString(out))
}
}
交付物清单与校验机制
发布时生成标准化交付包,包含:
libmycrypto.so(Linux) /mycrypto.dll(Windows)mycrypto.h(C 头文件)go.mod锁定版本(含replace指向本地cgo/)SHA256SUMS文件,内容示例:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 libmycrypto.so a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef mycrypto.h
CI/CD 流水线关键检查点
flowchart LR
A[git push] --> B[lint: gofmt + clang-format]
B --> C[build: CGO_ENABLED=1 go build -o bin/app]
C --> D[test: go test -race ./... && make test-c]
D --> E[verify: sha256sum -c SHA256SUMS]
E --> F[deploy: scp to artifact repo]
跨平台 ABI 兼容性策略
针对不同目标平台,预编译 C 库并嵌入资源:
- 使用
//go:embed c/lib/x86_64/libmycrypto.a加载静态库 - 在
pkg/cgo/loader.go中根据runtime.GOARCH+runtime.GOOS动态选择符号表 - 所有 C 函数签名强制使用
int32_t/uint64_t等固定宽度类型,规避long平台差异
错误传播与上下文透传
C 层错误码通过 errno + 自定义 cgo_err_t 结构体统一处理:
typedef struct { int code; char msg[256]; } cgo_err_t;
cgo_err_t crypto_encrypt(const uint8_t* in, size_t len, uint8_t** out);
Go 层封装为标准 error:
func (c *CImpl) Encrypt(data []byte) ([]byte, error) {
var cerr C.cgo_err_t
out := C.crypto_encrypt(&data[0], C.size_t(len(data)), &cerr)
if cerr.code != 0 {
return nil, fmt.Errorf("C encrypt failed [%d]: %s", cerr.code, C.GoString(&cerr.msg[0]))
}
return C.GoBytes(unsafe.Pointer(out), C.int(len(data)*2)), nil
} 