在ANSI C的通用访问器函数中使用void指针

时间:2011-09-04 17:11:53

标签: c pointers struct void

我有一个C代码的工作示例,我用它来教我自己在一个非平凡的应用程序中有效地使用指针。 (我梦想为我依赖的C库贡献一个缺失的功能。)

我的示例代码就像这样:

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

struct config_struct {
  int port;
  char *hostname;
};

typedef struct config_struct config;

void setup(config*);
void change(config*);
void set_hostname(config*, char*);
void get_hostname_into(config*, char**);
void teardown(config*);
void inspect(config*);

int main() {

  char* hostname;
  config* c;
  c = calloc( 1, sizeof(config));

  setup(c);
  inspect(c);

  change(c);
  inspect(c);

  set_hostname(c, "test.com");
  inspect(c);

  get_hostname_into(c, &hostname);
  inspect(c);  
  printf("retrieved hostname is %s (%p)\n", hostname, &hostname);

  teardown(c);
  printf("retrieved hostname is %s (%p) (after teardown)\n", hostname, &hostname);

  return EXIT_SUCCESS;
}

void setup(config* c) {
  c->port     = 9933;
  c->hostname = "localhost";
}

void change(config* c) {
  c->port     = 12345;
  c->hostname = "example.com";
}

void set_hostname(config* c, char* new_hostname) {
  c->hostname = new_hostname;
}

void get_hostname_into(config* c, char** where) {
  *where = c->hostname;
}

void teardown(config* c) {
  free(c);
}

void inspect(config* c) {
  printf("c is at %p\n", c);
  printf("c is %ld bytes\n", sizeof(*c));
  printf("c:port is %d (%p)\n", c->port, &(c->port));
  printf("c:hostname is %s (%p)\n", c->hostname, &(c->port));
}

这是库的本质所需要的(函数是get_session_property(session*, enum Property, void*) - 因此我正在寻找一种取消引用void指针的方法;我能够成功地为int实现这一点,但是我一直试图弄清楚如何为char*void*int做一些事情,但我无法理解如何做到这一点void* to char*

我对该库的成功实施(带有测试)在我的项目的Github分支here上。

我最接近的是:

enum Property { Port, Hostname };
void get_property(config*, enum Property, void*);
void get_property(config* c, enum Property p, void* target) {
  switch(p) {
    case Port:
      {
        int *port;
        port = (int *) target;
        *port = c->port;
      }
      break;
    case Hostname:
      {
        char *hostname;
        hostname = (char *) target;
        *hostname = c->hostname;
      }
      break;
  }
}

哪些不仁慈,但也离开char *get_hostname_into_here null,提出警告(我无法弄清楚):

untitled: In function ‘get_property’:
untitled:33: warning: assignment makes integer from pointer without a cast

我在这里做出的例子的完整源代码;请在回答解释时,或者推荐使用无效指针和/或好C风格的任何阅读时,似乎每个人都有不同的想法,而我在现实世界中认识的几个人只是说“图书馆在做这件事错误,不要使用void指针) - 虽然如果库会使结构公开会很好;对于封装和其他好的理由,我认为在这种情况下,void指针,泛型函数方法是完全合理的。

那么,在调用{{1}之后hostname get_property()函数的char*分支NULL get_property(c, Hostname, &get_hostname_into_here); {{}}} }

char *get_hostname_into_here;
get_property(c, Hostname, &get_hostname_into_here);
printf("genericly retrieved hostname is %s (%p)\n", get_hostname_into_here, &get_hostname_into_here);
// Expect get_hostname_into_here not to be NULL, but it is.

full source code for example (with output)

5 个答案:

答案 0 :(得分:1)

get_property函数应该被更改,以便target是一个双空指针,这意味着您可以更改指针本身(不仅是它所引用的内存):

void get_property (config *c, enum Property p, void **target) {
  switch (p) {
    case Port:
      *((int *) (*target)) = c->port;
      break;

    case Hostname:
      *target = c->config;
      break;
  }
}

然后使用类似的功能:

int port;
int *pport = &port;
char *hostname;
get_propery(c, Port, &pport);
get_propery(c, Hostname, &hostname);

答案 1 :(得分:1)

正如我在上面的评论中所说,不可能给出准确的答案,因为目前还不清楚目标是什么。但我看到两种可能性:

1:target指向char缓冲区

如果是这种情况,那么您似乎需要将字符串的内容复制到该缓冲区中。这是不可能安全的,因为您不知道接收缓冲区有多大。但是,如果你不关心这一点,那么你需要做类似的事情:

strcpy((char *)target, c->hostname);

2:target指向char *

如果是这种情况,那么可能意图是将char *修改为指向现有字符串,或者动态创建新缓冲区,复制字符串,然后修改{{1}指出它。

所以:

char *

或:

char **p = (char **)target;
*p = c->hostname; 

注意

您收到警告消息,因为在此行中:

char **p = (char **)target;
*p = malloc(strlen(c->hostname)+1);
strcpy(p, c->hostname);

*hostname = c->hostname; 的类型为*hostname,而char的类型为c->hostname。编译器告诉你这个转换没有任何意义。如果我是你,我会设置你的编译器将警告视为错误(例如使用GCC的char *标志),因为应始终遵守警告!

答案 2 :(得分:1)

在您提供有关get_property功能的更多详细信息之前,您的问题没有答案。很明显,void *target参数用于传递您应该放置结果的外部“空间” - 所请求属性的值。

收件人空间的性质是什么?

如果是int属性,则表示代码非常清晰:指针指向一些int对象,您应在该对象中放置属性值。这是你正确做的。

但是字符串属性怎么样?这里至少有两种可能性

1)void *target参数指向char []缓冲区的开头,该缓冲区大小足以接收任何属性值。在这种情况下,您的代码应如下所示

case Hostname:
  {
    char *hostname = target;
    strcpy(hostname, c->hostname);
  }
  break;

这种情况下的函数将被称为

char hostname_buffer[1024];
get_property(c, Hostname, hostname_buffer);

这实际上是“正确”的方法,除了您需要采取某些步骤以确保不会通过一些长属性值超出目标缓冲区。

2)void *target参数指向char *类型的指针,该指针应该从属性接收hostname 指针值。 (在这种情况下,target实际上保持char **值。)代码看起来像

case Hostname:
  {
    char **hostname = target;
    *target = c->hostname;
  }
  break;

这种情况下的函数将被称为

char *hostname;
get_property(c, Hostname, &hostname);

这第二个变体对我来说不太好看,因为在这种情况下,你实际上是返回一个指向属性结构内部数据的指针。让外界访问[假设不透明的]数据结构的内部并不是一个好主意。

P.S。通常不需要在C语言中明确地转换为void *个指针。

答案 3 :(得分:0)

让我们考虑一下get_property函数的简化示例,该函数在所有重要方面都是相同的:

void get_property_hostname(config* c, void* target) {
    char * hostname = (char *) target;
    *hostname = c->hostname;
}

在函数的第一行,您将生成一个“char *”指针,该指针指向与“target”相同的位置。在函数的第二行,当您编写*hostname = ...时,您正在写入主机名所指向的char,因此您正在写入target指向的内存的第一个字节。这不是你想要的;您只向函数的调用者提供一个字节的数据。此外,编译器会抱怨,因为赋值的左侧有“char”类型,而右侧有“char *”类型。

至少有三种正确的方法可以在C中返回一个字符串:

1)返回指向原始字符串的指针

如果您这样做,用户将有权访问该字符串,并可以根据需要进行修改。你必须告诉他不要这样做。将const限定符放在其上将有助于实现这一目标。

const char * get_property_hostname(config* c) {
    return c->hostname;
}

2)复制字符串并返回指向重复

的指针

如果这样做,函数的调用者必须在使用它时将重复的字符串传递给free()。请参阅documentation of strdup

const char * get_property_hostname(config * c) {
    return strdup(c->hostname);
}

3)将字符串写入调用者已分配的缓冲区

如果你这样做,那么由函数的调用者决定何时以及如何分配和释放内存。这就是Microsoft Windows操作系统中的许多API所做的事情,因为它为函数的调用者提供了最大的灵活性。

void get_property_hostname(config * c, char * buffer, int buffer_size)
{
    if (strlen(c->hostname)+1 > buffer_size)
    {
        // Avoid buffer overflows and return the empty string.
        buffer[0] = 0;
    }
    else
    {
        strcpy(buffer, c->hostname);
    }
}

然后要使用此功能,您可以执行以下操作:

void foo(){
    char buffer[512];
    get_property_hostname(c, buffer, sizeof(buffer));
    ...
    // buffer is on stack, so it gets freed automatically when foo returns
}

编辑1:我将把它作为练习让您弄清楚如何将此处提供的想法整合回您的通用get_property函数中。如果你花时间了解这里发生了什么,它应该不会太难,但你可能需要添加一些额外的参数。

编辑2:以下是如何调整方法1以使用指向char *而不是使用返回值的void指针:

void get_property_hostname(config* c, void * target) {
    *(char **)target = c->hostname;
}

然后你会这样称呼:

void foobar() {
    char * name;
    get_property_hostname(c, &name);
    ...
}

答案 4 :(得分:0)

get_hostname_into_here定义为:

char *get_hostname_into_here;

您正在传递对它的引用,即char**。在get_property中,您将void*转换为char*而不是char**,然后在分配之前将其解除引用。为了正确获取字符串,请使用:

case Hostname:
{
   char **hostname;
   hostname = (char **) target;
   *hostname = c->hostname;
}
break;