为了账号安全,请及时绑定邮箱和手机立即绑定

通过 cgo 调用fts_open时出现分段冲突错误

通过 cgo 调用fts_open时出现分段冲突错误

Go
SMILET 2022-09-19 20:54:56
我正在测试cgo,每个简单的你好世界,如代码,工作得很好。但我对下面的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")))}
查看完整描述

2 回答

?
侃侃无极

TA贡献2051条经验 获得超10个赞

fts_open 定义如下:

fts_open()
该函数获取指向一个字符指针数组的指针,这些指针命名一个或多个路径,这些路径构成了要遍历的逻辑文件层次结构。数组必须由指针终止。fts_open()null

C没有对数组的直接支持;它只有指针。在你的情况下,你传递一个有效的指针,但它不在一个数组中,该数组有一个指针作为紧随其后的元素,所以继续扫描过去的内存 - 寻找一个指针, 并最终尝试在某个地址读取内存,这是禁止这样做的(通常是因为该地址的页面没有被分配)。fts_openNULLfts_open&pathNULL

修复它的一种方法是创建该数组并在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.CStringmalloc(3)

  • 该符号定义在“标准定义.h”中;编译时可能会或可能不会收到错误。NULL

我已经在我的示例中修复了这两个问题。

对我们的示例的进一步改进可能是利用函数在单次运行中扫描多个路径的能力;如果我们要实现它,那么为Go端的第一个参数分配数组会更有意义:fts_*fts_open

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]))

}

请注意,这里我们没有显式地将最后一个参数清零,因为与C相反,Go用零初始化每个分配的内存块,因此一旦分配,其所有内存都已归零。argvargv


查看完整回答
反对 回复 2022-09-19
?
杨魅力

TA贡献1811条经验 获得超6个赞

您收到错误,因为“fts_open”需要一个指向数组的字符指针,该数组以 NULL 结尾,如 char *argv[] = { 路径,NULL };.。(https://linux.die.net/man/3/fts_open)


package main


/*

#include <stdint.h>

#include <fts.h>

#include <sys/stat.h>


uintmax_t get_total_size(char *path)

{

    uintmax_t total_size = 0;

    char *argv[] = { path, NULL };

    FTS *fts = fts_open(argv, FTS_PHYSICAL, NULL);

    if (fts == NULL)

        return 0;

    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")))

}

因此添加数组指针将修复代码。


使用 GCC 编译时,相同的代码也有效,但fts_open返回 NULL。我猜gcc和cgo之间的优化有一些区别(不是很确定)


我尝试了一些测试结果,并能够发现,当使用GCC编译时,字符**指针被空终止,但在cgo的情况下,它没有以空值终止,所以你得到“SIGSEGV”,因为你的代码正在读取无效的内存引用。


#include <stdio.h>

#include <string.h>


void try(char **p)

{

   while (*p != NULL)

   {

      printf("%zu\n", strlen(*p));

      ++p;

   }

}


void get_total_size(char *path)

{

   try(&path);

}

int main()

{

   get_total_size("/usr");

}

c 代码(有效)


package main

/*

#include <stdio.h>

#include <string.h>


void try(char **p)

{

   while (*p != NULL)

   {

      printf("%zu\n", strlen(*p));

      ++p;

   }

}


void get_total_size(char *path)

{

   try(&path);

}

*/

import "C"


func main() {

    C.get_total_size(C.CString("/usr"))

}

相同的去代码,你会面临错误


查看完整回答
反对 回复 2022-09-19
  • 2 回答
  • 0 关注
  • 95 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号