16

我是一名 Python 资深人士,但对 C 的涉猎不多。在互联网上半天没有找到任何适合我的东西后,我想我会在这里提问并获得我需要的帮助。

我想做的是编写一个简单的 C 函数,它接受一个字符串并返回一个不同的字符串。我打算用几种语言(Java、Obj-C、Python 等)绑定这个函数,所以我认为它必须是纯 C?

这是我到目前为止所拥有的。请注意,尝试在 Python 中检索值时出现段错误。

你好ç

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

const char* hello(char* name) {
    static char greeting[100] = "Hello, ";
    strcat(greeting, name);
    strcat(greeting, "!\n");
    printf("%s\n", greeting);
    return greeting;
}

主文件

import ctypes
hello = ctypes.cdll.LoadLibrary('./hello.so')
name = "Frank"
c_name = ctypes.c_char_p(name)
foo = hello.hello(c_name)
print c_name.value # this comes back fine
print ctypes.c_char_p(foo).value # segfault

我读到段错误是由 C 释放最初为返回的字符串分配的内存引起的。也许我只是在叫错树?

什么是完成我想要的正确方法?

4

5 回答 5

18

您的问题是在堆栈上分配了问候语,但是当函数返回时堆栈被破坏了。您可以动态分配内存:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

const char* hello(char* name) {
    char* greeting = malloc(100);
    snprintf("Hello, %s!\n", 100, name)
    printf("%s\n", greeting);
    return greeting;
}

但这只是战斗的一部分,因为现在您有内存泄漏。你可以用另一个 ctypes 调用 free() 来插入它。

...或者更好的方法是阅读与 python 的官方 C 绑定(http://docs.python.org/2/c-api/ 上的 python 2.x 和http://docs.python.org/2/c-api/的 python 3.x /docs.python.org/3/c-api/)。让您的 C 函数创建一个 python 字符串对象并将其交还。它将由python自动收集垃圾。由于您正在编写 C 端,因此您不必玩 ctypes 游戏。

...编辑..

我没有编译和测试,但我认为这个 .py 会起作用:

import ctypes

# define the interface
hello = ctypes.cdll.LoadLibrary('./hello.so')
# find lib on linux or windows
libc = ctypes.CDLL(ctypes.util.find_library('c'))
# declare the functions we use
hello.hello.argtypes = (ctypes.c_char_p,)
hello.hello.restype = ctypes.c_char_p
libc.free.argtypes = (ctypes.c_void_p,)

# wrap hello to make sure the free is done
def hello(name):
    _result = hello.hello(name)
    result = _result.value
    libc.free(_result)
    return result

# do the deed
print hello("Frank")
于 2013-02-14T21:08:02.527 回答
6

在 hello.c 中,您返回一个本地数组。您必须返回一个指向数组的指针,该数组必须使用 malloc 动态分配。

char* hello(char* name)
{ 
    char hello[] = "Hello ";
    char excla[] = "!\n";
    char *greeting = malloc ( sizeof(char) * ( strlen(name) + strlen(hello) + strlen(excla) + 1 ) );
    if( greeting == NULL) exit(1);
    strcpy( greeting , hello);
    strcat(greeting, name);
    strcat(greeting, excla);
    return greeting;
}
于 2013-02-14T20:58:15.893 回答
4

我今天遇到了同样的问题,发现您必须int通过设置方法来覆盖默认返回类型()restype。请参阅此处的 ctype 文档中的返回类型

import ctypes
hello = ctypes.cdll.LoadLibrary('./hello.so')
name = "Frank"
c_name = ctypes.c_char_p(name)
hello.hello.restype = ctypes.c_char_p # override the default return type (int)
foo = hello.hello(c_name)
print c_name.value
print ctypes.c_char_p(foo).value
于 2019-05-13T17:40:56.620 回答
2

这就是发生的事情。以及为什么它会破裂。当调用 hello() 时,C 堆栈指针向上移动,为函数所需的任何内存腾出空间。除了一些函数调用开销外,您所有的函数本地变量都在那里管理。以便static char greeting[100], 表示增加的堆栈的 100 字节用于该字符串。您可以使用一些操作该内存的函数。在您将一个指针放在堆栈上,指向问候语内存。然后你从调用中返回,此时堆栈指针被收回到调用之前的原始位置。因此,在您调用期间位于堆栈上的那 100 个字节,随着堆栈的进一步操作,基本上可以再次获取。包括指向该值并且您返回的地址字段。到那时,谁知道它会发生什么,但它可能设置为零或其他值。当您尝试访问它时,就好像它仍然是可用内存一样,您会遇到段错误。

为了解决问题,您需要以不同的方式管理该内存。你可以让你的函数alloc吃掉堆上的内存,但你需要确保它free()在以后通过你的绑定得到'ed。或者,您可以编写您的函数,以便绑定语言传递给它一个要使用的内存。

于 2013-02-14T21:00:44.340 回答
1

我也遇到了同样的问题,但使用了不同的方法。我想在与某个值匹配的字符串列表中找到一个字符串。

基本上,我用列表中最长字符串的大小初始化了一个 char 数组。然后将其作为参数传递给我的函数以保存相应的值。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void find_gline(char **ganal_lines, /*line array*/
                size_t size,        /*array size*/
                char *idnb,         /* id number for check */
                char *resline) {
  /*Iterates over lines and finds the one that contains idnb
    then affects the result to the resline*/
  for (size_t i = 0; i < size; i++) {
    char *line = ganal_lines[i];
    if (strstr(line, idnb) != NULL) {
      size_t llen = strlen(line);
      for (size_t k = 0; k < llen; k++) {
        resline[k] = line[k];
      }
      return;
    }
  }
  return;
}

这个函数被相应的python函数包装:



def find_gline_wrap(lines: list, arg: str, cdll):
    ""
    # set arg types
    mlen = maxlen(lines) # gives the length of the longest string in string list
    linelen = len(lines)
    line_array = ctypes.c_char_p * linelen

    cdll.find_gline.argtypes = [
        line_array,
        ctypes.c_size_t,
        ctypes.c_char_p,
        ctypes.c_char_p,
    ]
    #
    argbyte = bytes(arg, "utf-8")

    resbyte = bytes("", "utf-8")

    ganal_lines = line_array(*lines)
    size = ctypes.c_size_t(linelen)
    idnb = ctypes.c_char_p(argbyte)
    resline = ctypes.c_char_p(resbyte * mlen)
    pdb.set_trace()
    result = cdll.find_gline(ganal_lines, size, idnb, resline)
    # getting rid of null char at the end
    result = resline.value[:-1].decode("utf-8")
    return result
于 2020-01-03T03:40:09.000 回答