【问题标题】:Opaque pointer not accessable from resident .c file常驻 .c 文件无法访问不透明指针
【发布时间】:2021-02-14 17:01:57
【问题描述】:

当从定义文件访问不透明结构内部的结构时,我遇到了一个奇怪的分段错误。我最近刚刚了解了不透明指针,但我的猜测是,我在分配内部结构时做错了。但是,在第一次调用该结构时,它似乎可以工作,并且在同一方法中无法访问它。

所以我在connection.h 中定义了我的不透明结构指针:

#ifndef _Connection_h
#deifne _Connection_h
// Connection opaque pointer
typedef struct connection;
// API
_Bool connection_connect(struct connection *self);
#endif // _Connection_h

然后在connection.c 内部分配struct sockaddr_un 类型的内部serv 结构指针。

#include "../connection.h"

struct connection {
  _Bool (*connect)();
  char *(*get_name)();
  char name[MAX_NAMELEN];
  char hostname[MAX_NAMELEN];
  uint serv_id;
  struct sockaddr_un *serv; // Server socket structure
};

// ** Public Interface **
_Bool connection_connect(struct connection *self) {
  printf("Generic connection (opaque pointer)..\n");
  strcpy(self->name, SERVERNAME);
  // Socket path
  char path[108];
  strcpy(path, SERVERNAME);
  strcat(path, "_socket");
  printf("self->name = %s\n", self->name);
  // Allocate the serv socket structure
  self->serv = malloc(sizeof(*self->serv));
  strcpy(self->serv->sun_path, path);
  self->serv->sun_family = AF_UNIX;
  printf("self->serv->sun_path = %s\n", self->serv->sun_path);
  printf("self->serv->sun_family = %hn\n", self->serv->sun_family);
  
  // Locate the host
  char hostname[MAX_NAMELEN];
  gethostname(hostname, MAX_NAMELEN);
  strcpy(self->hostname, hostname);

  if ((self->serv_id = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
    handle_error("Socket");
  return 1;
}

然后connection接口方法的分配由客户端处理。对于最小的可重现示例,client 类型实现可以像这样处理接口方法分配:

#include "../connection.h"

typedef struct connection {
  void (*connect)();
} *client;

void client_connect(client self) {
  connection_connect(self);
  printf("Client connection..\n");
}

client make_client(client self) {
  client tmp = malloc(sizeof(client));
  tmp->connect = client_connect;
  return tmp;
}

// Main procedure
int main(int argc, char **argv) {
  client c = make_client(c);
  c->connect(c);
  return 0;
}

执行后,分配的结构首先似乎被正确分配,并且它的实例可以访问,直到最后一个printf

Generic connection (opaque pointer)..
self->name = freebay
self->serv->sun_path = freebay_socket
[1]    210938 segmentation fault (core dumped)
(gdb) p self->serv
$1 = (struct sockaddr_un *) 0x66203d2068746170
(gdb) x self->serv
0x66203d2068746170: Cannot access memory at address 0x66203d2068746170

我的问题是,为什么self->serv 在同一方法中使用后无法访问?

【问题讨论】:

  • 你能发一个minimal reproducible example吗?特别是,显示调用connection_connect 的函数的代码。它从哪里得到它传入的指针?

标签: c pointers struct opaque-pointers


【解决方案1】:

你没有分配足够的内存:

self->serv = malloc(sizeof(struct sockaddr_un *));

您正在为指向struct sockaddr_un指针分配足够的空间,而不是一个实例。指针大小小于结构体大小,因此当您尝试写入结构体时,您写入的内存超出了分配内存的末尾,从而触发了undefined behavior

分配空间的正确方法是:

self->serv = malloc(sizeof(struct sockaddr_un));

甚至更好:

self->serv = malloc(sizeof(*self->serv));

因为后者不关心类型是什么。

另外,这是不正确的:

printf("self->serv->sun_family = %hn\n", self->serv->sun_family);

因为%hn 期待的是short int *,而不是short int。将其更改为 %hd


通过您给出的完整示例,我们现在可以看到您的每个 .c 文件中都有两个名称相同的不兼容结构。

您的主文件将struct connection 定义为:

typedef struct connection {
  void (*connect)();
} *client;

而 connection.c 将其定义为:

struct connection {
  _Bool (*connect)();
  char *(*get_name)();
  char name[MAX_NAMELEN];
  char hostname[MAX_NAMELEN];
  uint serv_id;
  struct sockaddr_un *serv; // Server socket structure
};

您的主文件在make_client 中创建了前者的一个实例:

client tmp = malloc(sizeof(client));

这本身是无效的,因为您再次为指针分配空间,而不是结构的实例。它需要是:

client tmp = malloc(sizeof(*tmp));

但即使你修复了这个问题,你也可以将此指针传递给connection_connect,它需要一个指向后一个结构的指针,而不是前者。

所有会创建struct connection 实例并修改其成员的函数都应该驻留在真正定义所在的connection.c 中。这就是不透明结构的工作方式。

【讨论】:

  • 我也是这么想的,之前self->serv = malloc(sizeof(*self->serv))self->serv = malloc(sizeof(struct sockaddr_un))都试过了,错误是一样的。
  • @siery 这本身就是一个问题,但是如果您仍然崩溃,那么您的代码中的其他地方就会出现另一个错误。通过 valgrind 运行它,如果你对内存管理不善,它会告诉你在哪里。
  • 那么,您建议在不透明结构定义内部应该有一种分配方法?类似this
  • @siery 类似的东西,但你犯了同样的错误,分配指针的大小而不是它指向的大小。
  • 你是对的,使用*tmp 的大小就可以了。谢谢。
【解决方案2】:

编译你的代码我发现了这个:

connection.c:37:43: warning: format specifies type 'short *' but the argument has type 'sa_family_t' (aka 'unsigned char') [-Wformat]
        printf("self->serv->sun_family = %hn\n", self->serv->sun_family);
                                         ~~~     ^~~~~~~~~~~~~~~~~~~~~~
1 warning generated.

如果你写:

printf("self->serv->sun_family = %cn\n", self->serv->sun_family);

程序没有错误

【讨论】:

  • 是的,谢谢,这样可以节省一些类型操作。但这并没有改变地址访问问题。
  • 其实去掉self->serv->sun_family的初始化后,这里一切正常。
【解决方案3】:

在将数据复制到它之前,您没有分配self->serv->sun_path。在那之后肯定会出问题。

除非sun_pathchar 的数组。

【讨论】:

  • 嗯,它是一个char[108] 类型的数组。您可以在sys/un.h 中找到定义。
  • 在“server_connection tmp = malloc(sizeof(server_connection));”中为指针(server_connection)而不是结构(连接)分配内存:typedef struct connection {} * server_connection
  • 是的,为了类型安全,server_connection指针现在是一个类型,你可以看到它被分配给server.connection,它的类型是server_connection。我不希望从connection 派生的其他类型能够传递到那里。
  • 我明白这一点,但您应该使用 sizeof(*server_connection) 为 tmp 分配内存,这将为 server_connection 指针指向的连接结构提供足够的空间..
  • connection 类型已经有足够的内存。我以前一直以这种方式分配不透明的指针,没有问题。
猜你喜欢
  • 2011-02-12
  • 2011-07-15
  • 1970-01-01
  • 2016-06-30
  • 1970-01-01
  • 2011-01-28
  • 2021-08-02
  • 2013-07-12
相关资源
最近更新 更多