所以,我对各种事物的链接有点困惑。对于这个问题,我将重点讨论不透明的指针。
我将举例说明我的困惑。假设我有这三个文件:
main.c
#include <stdio.h>
#include "obj.h" //this directive is replaced with the code in obj.h
int main()
{
myobj = make_obj();
setid(myobj, 6);
int i = getid(myobj);
printf("ID: %in",i);
getchar();
return 0;
}
目标c
#include <stdlib.h>
struct obj{
int id;
};
struct obj *make_obj(void){
return calloc(1, sizeof(struct obj));
};
void setid(struct obj *o, int i){
o->id = i;
};
int getid(struct obj *o){
return o->id;
};
目标h
struct obj;
struct obj *make_obj(void);
void setid(struct obj *o, int i);
int getid(struct obj *o);
struct obj *myobj;
由于预处理器的指令,这些文件基本上会变成两个文件:
(从技术上讲,我知道stdio.h和stdlib.h会让它们的代码替换预处理器指令,但为了可读性,我没有费心替换它们)
main.c
#include <stdio.h>
//obj.h
struct obj;
struct obj *make_obj(void);
void setid(struct obj *o, int i);
int getid(struct obj *o);
struct obj *myobj;
int main()
{
myobj = make_obj();
setid(myobj, 6);
int i = getid(myobj);
printf("ID: %in",i);
getchar();
return 0;
}
目标c
#include <stdlib.h>
struct obj{
int id;
};
struct obj *make_obj(void){
return calloc(1, sizeof(struct obj));
};
void setid(struct obj *o, int i){
o->id = i;
};
int getid(struct obj *o){
return o->id;
};
现在我有点困惑。如果我试图在main.c中创建一个结构obj,我会得到一个不完整的类型错误,尽管main.c有声明struct obj;
。
即使我把代码改为使用extern
,它也不会编译:
main.c
#include <stdio.h>
extern struct obj;
int main()
{
struct obj myobj;
myobj.id = 5;
int i = myobj.id;
printf("ID: %in",i);
getchar();
return 0;
}
目标c
#include <stdlib.h>
struct obj{
int id;
};
据我所知,main.c和obj.c不通信结构(不像函数或变量,有些函数或变量只需要在另一个文件中声明)。
因此,main.c没有与struct-obj类型的链接,但由于某种原因,在前面的示例中,它能够创建一个指向一个非常好的struct obj *myobj;
的指针。如何,为什么?我觉得我错过了一些重要的信息。关于哪些内容可以或不能从一个.c文件转到另一个文件,有什么规则?
附录
为了解决可能的重复,我必须强调,我不是在问什么是不透明的指针,而是它在链接文件时是如何工作的。
将注释转换为半连贯的答案
第二main.c
的问题出现是因为它不具有struct obj
的细节;它知道该类型存在,但对它包含的内容一无所知。您可以创建和使用指向struct obj
的指针;你不能取消引用那些指针,甚至不能复制结构,更不用说访问结构中的数据了,因为不知道它有多大。这就是为什么你在obj.c
中有函数的原因。它们提供您需要的服务——对象分配、发布、访问和修改内容(除了缺少对象发布;也许free(obj);
可以,但最好提供一个"析构函数")。
请注意,obj.c
应该包括obj.h
,以确保obj.c
和main.c
之间的一致性——即使使用不透明指针也是如此。
我不是你所说的"确保一致性"的100%;这意味着什么,为什么它很重要?
目前,obj.c
中可能包含struct obj *make_obj(int initializer) { … }
,但由于obj.c
中不包含obj.h
,编译器无法告诉您,main.c
中的代码将在没有初始化器的情况下调用它,从而导致使用准随机(不确定)值来"初始化"结构。如果在obj.c
中包含obj.h
,编译器将报告头中的声明与源文件中的定义之间的差异,并且代码不会编译。一旦修复了头,main.c
中的代码也不会编译。头文件是将系统连接在一起的"粘合剂",确保函数定义和使用函数的地方(引用)之间的一致性。标头中的声明确保它们都是一致的。
此外,我认为指针是类型特定的全部原因是因为指针需要的大小可能因类型而异。指针怎么可能指向未知大小的东西?
至于为什么你可以在不知道所有细节的情况下拥有指向类型的指针,这是C的一个重要功能,它提供了单独编译的模块的互通。所有指向结构(任何类型)的指针都必须具有相同的大小和对齐要求。您可以通过在适当的地方简单地说struct WhatEver;
来指定结构类型的存在。这通常在文件范围内,而不是在函数内部;函数内部存在用于定义(或可能重新定义)结构类型的复杂规则。然后,您可以使用指向该类型的指针,而无需为编译器提供更多信息。
如果没有结构的详细主体(struct WhatEver { … };
,其中大括号和它们之间的内容至关重要),您就无法访问结构中的内容,也无法创建类型为struct WhatEver
的变量,但您可以创建指针(struct WhatEver *ptr = NULL;
)。这对于"类型安全"非常重要。尽可能避免将void *
作为通用指针类型,而且通常也可以避免——并非总是如此,但通常都是这样。
哦,好吧,所以
obj.c
中的obj.h
是确保所使用的原型与定义匹配的一种方法,如果不匹配,则会导致错误消息。
是。
我仍然没有完全遵循所有具有相同大小和对齐方式的指针。一个结构的大小和对齐方式对那个特定的结构来说不是唯一的吗?
结构都不同,但指向它们的指针大小都相同。
并且指针可以是相同的大小,因为结构指针不能被取消引用,所以它们不需要特定的大小?
如果编译器知道结构的详细信息(存在{ … }
部分的结构类型定义),则可以取消引用指针(当然,可以定义结构类型的变量以及指向它的指针)。如果编译器不知道详细信息,则只能定义(并使用)指向该类型的指针。
此外,出于好奇,为什么要避免将
void *
作为通用指针?
避免void *
是因为您失去了所有类型的安全性。如果你有申报单:
extern void *delicate_and_dangerous(void *vptr);
如果你编写调用,编译器就不会抱怨:
bool *bptr = delicate_and_dangerous(stdin);
struct AnyThing *aptr = delicate_and_dangerous(argv[1]);
如果你有申报单:
extern struct SpecialCase *delicate_and_dangerous(struct UnusualDevice *udptr);
然后编译器会告诉您,当您使用错误的指针类型(如stdin
(一个FILE *
)或argv[1]
(如果您在main()
中,则为char *
)等)调用它时,或者您是否分配了错误类型的指针变量。