go - 通过 cgo 调用 fts_open 时出现分段违规错误
问题描述
我正在测试 cgo,每个简单的 hello world 都像代码一样运行良好。
但我对下面的 C 代码有疑问。
C代码是遍历目录树并对文件大小求和。
如果我使用 go 命令构建,那么构建是可以的,没有错误。
但是在运行时,发生了“分段冲突”错误
bash$./walkdir
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x1 pc=0x7f631e077c1a]
. . . .
-------------------------------------------------------------
package main
/*
#include <stdint.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(&path, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.get_total_size(C.CString("/usr")))
}
解决方案
fts_open
定义如下:
fts_open()
该fts_open()
函数接受一个指向字符指针数组的指针,该数组命名一个或多个路径,这些路径构成要遍历的逻辑文件层次结构。数组必须由null
指针终止。
C 不直接支持数组;它只有指针。在您的情况下,您传递fts_open
了一个有效的指针,但它不位于将NULL
指针作为紧随其后的元素的数组中,因此fts_open
继续扫描过去的内存&path
- 寻找NULL
指针,并最终尝试在某个地址读取内存禁止这样做(通常是因为该地址处的页面未分配)。
修复它的一种方法是创建该数组并在 C 端对其进行初始化。
看起来您正在使用相当最新的 C 标准,所以让我们使用直接文字来初始化数组:
package main
/*
#include <stddef.h> // for NULL
#include <stdint.h>
#include <stdlib.h> // for C.free
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
char * path_argv[2] = {path, NULL};
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
cpath := C.CString("/usr")
defer C.free(unsafe.Pointer(cpath))
fmt.Println(C.get_total_size(cpath))
}
请注意,您的程序有一个错误和一个可能的问题:
- 一个错误是调用通过从链接的 C 库
C.CString
执行调用来分配一块内存malloc(3)
,而您没有释放该内存块。 - 该符号
NULL
在“stddef.h”中定义;编译时您可能会或可能不会收到错误。
我已经在我的示例中解决了这两个问题。
对我们示例的进一步改进可能是利用fts_*
函数在一次运行中扫描多个路径的能力;fts_open
如果我们要实现它,为 Go 的第一个参数分配数组会更有意义:
package main
/*
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char * const *path_argv)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
fmt.Println(getTotalSize("/usr", "/etc"))
}
func getTotalSize(paths ...string) uint64 {
argv := make([]*C.char, len(paths)+1)
for i, path := range paths {
argv[i] = C.CString(path)
defer C.free(unsafe.Pointer(argv[i]))
}
return uint64(C.get_total_size(&argv[0]))
}
请注意,这里我们没有明确地将 的最后一个参数清零,argv
因为与 C 相反,Go 用零初始化每个分配的内存块,所以一旦argv
分配,它的所有内存都已经清零了。
推荐阅读
- javascript - Prettier : 防止 Prettier 删除括号
- python - 将谷歌云连接到谷歌 colab
- layer - 我们可以使用 Wayland 协议中的侦听器获取 Weston 新层 ID 吗?
- java - 如何在 Harmony OS 的 Paint 对象中设置 Xfermode?
- spring-boot - 春天云网关;在类路径上发现 Spring MVC,与 Spring Cloud Gateway 不兼容问题
- c# - 有没有办法将 INSERT INTO SELECT 与 SQL 中的普通 VALUES 命令结合起来?
- swift - Xcode Swift Firebase 实时数据库未更新
- android - 如何从异步提供的参数中初始化记忆值?
- java-web-start - 我们要在 https://jwt.io/ 中验证 JWS 签名吗?
- node.js - npm 脚本和命令行的节点版本不同