我一直在使用以下代码来创建各种结构,但只给C文件外的人指向它。 (是的,我知道他们可能会乱用它,所以它不完全像Java中的私有关键字,但对我来说没关系。)
无论如何,我一直在使用以下代码,今天我看了它,我真的很惊讶它实际上有效,有人可以解释为什么会这样吗?
在我的C文件中,我创建了我的结构,但是没有在typedef命名空间中给它一个标记:
struct LABall {
int x;
int y;
int radius;
Vector velocity;
};
在H文件中,我把它放在:
typedef struct LABall* LABall;
我显然在c文件中使用#include“LABall.h”,但是我没有在头文件中使用#include“LABall.c”,因为这会破坏单独头文件的整个目的。那么,为什么我还没有实际包含H文件中的LABall *结构的指针呢?它是否与使用文件的struct命名空间有关,即使一个文件没有链接到另一个文件?
谢谢。
答案 0 :(得分:23)
像这样的东西的标准模式是有一个foo.h
文件来定义API,如
typedef struct _Foo Foo;
Foo *foo_new();
void foo_do_something(Foo *foo);
和foo.c
文件提供该API的实现,如
struct _Foo {
int bar;
};
Foo *foo_new() {
Foo *foo = malloc(sizeof(Foo));
foo->bar = 0;
return foo;
}
void foo_do_something(Foo *foo) {
foo->bar++;
}
这隐藏了foo.c
中实现中结构的所有内存布局和大小,并且通过foo.h
公开的接口完全独立于这些内部:caller.c
只有#include "foo.h"
#include "foo.h"
void bleh() {
Foo *f = foo_new();
foo_do_something(f);
}
只需要存储指向某个东西的指针,指针总是大小相同:
broken.c
注意:我已将释放内存作为练习释放给读者。 : - )
当然,这意味着以下文件#include "foo.h"
void broken() {
Foo f;
foo_do_something(&f);
}
将不工作:
Foo
因为此文件中不知道实际创建类型为{{1}}的变量所需的内存大小。
答案 1 :(得分:7)
既然你问这个语言“为什么”的确切原因,我假设你想要一些精确的引用。如果你找到那个学究,只需跳过笔记......
它起作用的原因有两点:
所有指向结构类型的指针具有相同的表示形式(请注意,就标准C而言,所有指针类型的不为真)。[1]因此,编译器有足够的信息来为指针到结构类型的所有用法生成适当的代码。
标签命名空间(struct,enum,union)确实与所有翻译单元兼容。[2]因此,这两个结构(即使一个没有完全定义,即它没有成员声明)是同一个。
(顺便说一下,#import是非标准的。)
[1]根据n1256§6.2.5.27:
所有指向结构类型的指针应具有相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。
[2]根据n1256§6.2.7.1:
在单独的翻译单元中声明的两个结构,联合或枚举类型在其标记和成员满足以下要求时是兼容的:如果使用标记声明一个,则另一个应使用相同的标记声明。如果两者都是完整类型,则适用以下附加要求:[与我们无关]。
答案 2 :(得分:1)
在
typedef struct A* B;
因为所有指针的接口都是相同的,所以知道B意味着指向结构A的指针已经包含了足够的信息。 A的实际实现是无关紧要的(这种技术称为“不透明指针”。)
(顺便说一句,更好地重命名LABall
之一。令人困惑的是,同名用于不兼容的类型。)