如何在C语言中隐藏结构体的声明?

12
在问题为什么在C语言中我们经常使用typedef定义结构体?中,用户unwind回答说:

在后一种情况下,您无法通过值返回Point,因为其声明对头文件的用户是隐藏的。例如,这是GTK+广泛使用的一种技术。

如何隐藏声明?为什么我不能按值返回Point?

补充:

我明白为什么不能按值返回结构体了,但是,为什么我不能在我的函数中解引用这个指针呢?例如,如果我的结构体有一个名为y的成员,我为什么不能这样做?

 pointer_to_struct->y = some_value; 

我为什么要使用方法来完成它?(例如Gtk +)

谢谢大家,再次抱歉我的英语不好。

6个回答

35

请参考这个库的示例,其中使用了公共头文件、私有头文件和实现文件。

在文件public.h中:

struct Point;

struct Point* getSomePoint();

private.h文件中:

struct Point
{
    int x;
    int y;
}

private.c 文件中:

struct Point* getSomePoint()
{
    /* ... */
}
如果你将这三个文件编译成库,你只需要提供public.h和库对象文件给库的使用者。

getSomePoint必须要返回一个指向Point的指针,因为public.h没有定义Point结构体的大小,只定义了它是个结构体并且存在。库的使用者可以使用Point的指针,但是不能访问其成员或者复制它,因为他们不知道结构体的大小。

关于你进一步提出的问题: 你不能解引用它,因为使用库的程序只有private.h中的信息,其中没有成员声明。因此,它无法访问点结构的成员。

你可以将这看作是C语言的封装特性,就像你会将C++类的数据成员声明为私有一样。


1
@chux-ReinstateMonica 你是正确的,刚刚解决了这个问题。 - Timbo
如果我想在 file.c 中声明一个仅在 file.c 中使用的结构体,这是否可行? - mercury0114
1
如果你在 file.c 文件中声明一个结构体,那么它只能在该声明点以下的 file.c 文件内可见。 - Timbo

6
他的意思是你不能在头文件中通过值返回结构体,因为这需要完整声明结构体。但是在他的示例中,这只会在C文件中发生(使X成为完整类型的声明“隐藏”在C文件中,并未暴露到头文件中)。如果这是结构体的第一个声明,则以下仅声明不完整类型。
struct X;

然后,你可以声明这个函数。
struct X f(void);

但是你无法定义该函数,因为你无法创建该类型的变量,更不用说返回它了(它的大小未知)。

struct X f(void) { // <- error here
  // ...
}

错误发生的原因是 “x” 仍然不完整。如果你只包含头文件,其中包含不完整的声明,那么你无法调用该函数,因为函数调用的表达式将产生一个不完整的类型,这是不允许的。
如果在中间提供一个完整类型的声明 struct X,那么它就是有效的。
struct X;
struct X f(void);

// ...
struct X { int data; };
struct X f(void) { // valid now: struct X is a complete type
  // ...
}

这也适用于使用typedef的方式:它们都命名了相同的(可能是不完整的)类型。一次使用普通标识符X,另一次使用标签struct X

5
在头文件中:
typedef struct _point * Point;

编译器看到这个代码后,它知道:

  • 有一个名为_point的结构体。
  • 有一个指针类型Point可以引用struct _point

编译器不知道:

  • struct _point的具体内容是什么。
  • struct _point包含哪些成员。
  • struct _point的大小。

不仅编译器不知道,我们作为程序员也不知道。这意味着我们不能编写依赖于struct _point这些属性的代码,这样我们的代码就更具可移植性。

根据上述代码,您可以编写如下函数:

Point f() {
   ....
}

因为Point是一个指针,而struct指针的大小都相同,编译器不需要了解它们的其他信息。但是你不能编写一个返回值的函数:

struct _point f() {
  ....
}

因为编译器对于 struct _point 一无所知,尤其是它的大小,在构造返回值时需要这些信息。
因此,我们只能通过指针类型 Point 引用 struct _point。这也是标准C语言有像 FILE 这样只能通过指针访问的类型的原因 - 你不能在代码中创建一个 FILE 结构实例。

4

旧问题,更好的答案:

在头文件中:

typedef struct _Point Point;

在C文件中:
struct _Point
{
   int X;
   int Y;
};

3
那篇文章的意思是:如果你看到了标题。
typedef struct _Point Point;

Point * point_new(int x, int y);

如果您不知道Point的实现细节,那么请参考以下内容。

2

作为使用非透明指针的替代方案(如其他人所提到的),如果你想避免使用堆内存,你可以返回一个不透明的字节包:

// In public.h:
struct Point
{
    uint8_t data[SIZEOF_POINT];  // make sure this size is correct!
};
void MakePoint(struct Point *p);

// In private.h:
struct Point
{
    int x, y, z;
};

void MakePoint(struct Point *p);

// In private.c:
void MakePoint(struct Point *p)
{
    p->x = 1;
    p->y = 2;
    p->z = 3;
}

然后,您可以在客户端代码中在堆栈上创建结构体的实例,但是客户端不知道其中的内容——它只知道它是一个具有给定大小的字节块。当然,如果客户端能够猜测成员的偏移量和数据类型,则仍然可以访问数据,但是在这种情况下,不透明指针也存在相同的问题(尽管客户端不知道对象大小)。
例如,pthreads库中使用的各种结构体对于像pthread_tpthread_cond_t等类型都使用不透明字节的结构体——您仍然可以在堆栈上创建这些结构体的实例(通常是这样做),但是您不知道其中的内容。只需查看您的/usr/include/pthreads.h和它包含的各个文件即可了解更多信息。

4
你可能会遇到对齐问题,公共的“Point结构体”本地对齐需求基于uint8_t数据类型,例如1字节,而私有的“Point结构体”本地对齐需求基于int,可能是4字节。我曾经在这个答案中试图解释这个问题:http://stackoverflow.com/a/17619016/611560。 - Yann Droneaud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接