通过 cgo 调用 fts_open 时出现段冲突错误
Segmentation violation error when calling fts_open via cgo
我正在测试 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
is defined 像这样:
fts_open()
The fts_open()
function takes a pointer to an array of character
pointers naming one or more paths which make up a logical file
hierarchy to be traversed. The array must be terminated by a
null
pointer.
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.CString
通过从链接的 C 库执行对 malloc(3)
的调用来分配一块内存,而您没有释放该内存块。
- 符号
NULL
定义在“stddef.h”;编译时可能会或可能不会出错。
我在示例中解决了这两个问题。
我们示例的进一步改进可能是利用 fts_*
函数的能力在单个 运行 中扫描多个路径;如果我们要实现它,那么在 Go 端为 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]))
}
请注意,这里我们没有明确地将 argv
的最后一个参数置零,因为 — 与 C 相反,— Go 将每个分配的内存块初始化为零,因此一旦 argv
被分配,所有它的内存已经归零。
您收到错误原因“fts_open”需要一个指向数组的字符指针,该数组以 NULL 结尾,如 char *argv[] = { path, 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 returns NULL.I 我猜测 gcc 和 cgo 之间的优化存在一些差异(不太确定)
我尝试了一些测试结果,发现在使用 GCC 编译时,char **指针以 NULL 结尾,但在 cgo 的情况下,它没有以 NULL 结尾,所以你得到的是“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"))
}
同样的go代码你会遇到错误
我正在测试 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
is defined 像这样:
fts_open()
Thefts_open()
function takes a pointer to an array of character pointers naming one or more paths which make up a logical file hierarchy to be traversed. The array must be terminated by anull
pointer.
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.CString
通过从链接的 C 库执行对malloc(3)
的调用来分配一块内存,而您没有释放该内存块。 - 符号
NULL
定义在“stddef.h”;编译时可能会或可能不会出错。
我在示例中解决了这两个问题。
我们示例的进一步改进可能是利用 fts_*
函数的能力在单个 运行 中扫描多个路径;如果我们要实现它,那么在 Go 端为 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]))
}
请注意,这里我们没有明确地将 argv
的最后一个参数置零,因为 — 与 C 相反,— Go 将每个分配的内存块初始化为零,因此一旦 argv
被分配,所有它的内存已经归零。
您收到错误原因“fts_open”需要一个指向数组的字符指针,该数组以 NULL 结尾,如 char *argv[] = { path, 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 returns NULL.I 我猜测 gcc 和 cgo 之间的优化存在一些差异(不太确定)
我尝试了一些测试结果,发现在使用 GCC 编译时,char **指针以 NULL 结尾,但在 cgo 的情况下,它没有以 NULL 结尾,所以你得到的是“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"))
}
同样的go代码你会遇到错误