关于不透明指针,C 中的链接如何工作?
How does linking work in C with regards to opaque pointers?
所以,我一直对 link 各种事情感到困惑。对于这个问题,我将重点关注不透明指针。
我将用一个例子来说明我的困惑。假设我有这三个文件:
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: %i\n",i);
getchar();
return 0;
}
obj.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;
};
obj.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: %i\n",i);
getchar();
return 0;
}
obj.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 中创建一个结构对象,我会得到一个不完整的类型错误,即使 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: %i\n",i);
getchar();
return 0;
}
obj.c
#include <stdlib.h>
struct obj{
int id;
};
据我所知,main.c 和 obj.c 不通信结构(不像某些函数或变量,它们只需要在另一个文件中声明)。
因此,main.c 没有 link struct obj 类型,但出于某种原因,在前面的示例中,它能够创建指向一个的指针就好了 struct obj *myobj;
.怎么,为什么?我觉得我错过了一些重要的信息。关于什么可以或不能从一个 .c 文件转到另一个文件的规则是什么?
附录
为了解决 possible duplicate,我必须强调,我不是在问什么是不透明指针,而是在问文件 linking.
时它是如何工作的
正在将评论转换为 semi-coherent 答案。
第二个main.c
的问题是因为它没有struct obj
的细节;它知道类型存在,但对它包含的内容一无所知。您可以创建和使用指向 struct obj
的指针;您不能取消引用这些指针,甚至不能复制结构,更不用说访问结构中的数据了,因为不知道它有多大。这就是为什么您拥有 obj.c
中的功能。他们提供您需要的服务——object 分配、发布、访问和修改内容(除了缺少 object 发布;也许 free(obj);
可以,但最好提供'destructor').
请注意 obj.c
应包括 obj.h
以确保 obj.c
和 main.c
之间的一致性 — 即使您使用不透明指针。
I'm not 100% what you mean by 'ensuring consistency'; what does that entail and why is it important?
目前,您可以在 obj.c
中包含 struct obj *make_obj(int initializer) { … }
,但是因为您没有在 obj.c
中包含 obj.h
,编译器无法告诉您main.c
中的代码将在没有初始化器的情况下调用它——导致 quasi-random(不确定的)值被用于 'initialize' 结构。如果在obj.c
中包含obj.h
,编译器将报告header中的声明与源文件中的定义之间的差异,代码将无法编译。 main.c
中的代码也不会编译 — 一旦 header 被修复。 header 文件是 'glue' 将系统保持在一起,确保函数定义和使用函数的地方(引用)之间的一致性。 header 中的声明确保它们都是一致的。
Also, I thought the whole reason why pointers are type-specific was because the pointers need the size which can vary depending on the type. How can a pointer be to something of unknown size?
至于为什么你可以在不知道所有细节的情况下拥有指向类型的指针,这是 C 的一个重要特性,它提供了单独编译的模块的互通性。所有指向结构(任何类型)的指针都必须有 same size and alignment requirements。您可以通过简单地在适当的地方说 struct WhatEver;
来指定结构类型存在。这通常在文件范围内,而不是在函数内;在函数内部定义(或可能重新定义)结构类型有复杂的规则。然后您可以使用指向该类型的指针,而无需为编译器提供更多信息。
如果没有详细的 body 结构(struct WhatEver { … };
,其中大括号和它们之间的内容至关重要),您将无法访问结构中的内容,或创建类型为 struct WhatEver
— 但您可以创建指针 (struct WhatEver *ptr = NULL;
)。这对 'type safety' 很重要。尽可能避免将 void *
作为通用指针类型,而且通常可以避免使用它——并非总是如此,但通常如此。
Oh okay, so the obj.h
in obj.c
is a means of ensuring the prototype being used matches the definition, by causing an error message if they don't.
是的。
I'm still not entirely following in terms of all pointers having the same size and alignment. Wouldn't the size and alignment of a struct be unique to that particular struct?
结构各不相同,但指向它们的指针大小相同。
And the pointers can be the same size because struct pointers can't be dereferenced, so they don't need specific sizes?
如果编译器知道结构的细节({ … }
部分存在结构类型的定义),那么可以取消引用指针(并且可以定义结构类型的变量,当然还有指向它的指针)。如果编译器不知道细节,您只能定义(和使用)指向该类型的指针。
Also, out of curiosity, why would one avoid void *
as a universal pointer?
你避免使用 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
(a FILE *
) 或 argv[1]
(a char *
if你在 main()
) 中,等等,或者你分配给了错误类型的指针变量。
所以,我一直对 link 各种事情感到困惑。对于这个问题,我将重点关注不透明指针。
我将用一个例子来说明我的困惑。假设我有这三个文件:
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: %i\n",i);
getchar();
return 0;
}
obj.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;
};
obj.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: %i\n",i);
getchar();
return 0;
}
obj.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 中创建一个结构对象,我会得到一个不完整的类型错误,即使 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: %i\n",i);
getchar();
return 0;
}
obj.c
#include <stdlib.h>
struct obj{
int id;
};
据我所知,main.c 和 obj.c 不通信结构(不像某些函数或变量,它们只需要在另一个文件中声明)。
因此,main.c 没有 link struct obj 类型,但出于某种原因,在前面的示例中,它能够创建指向一个的指针就好了 struct obj *myobj;
.怎么,为什么?我觉得我错过了一些重要的信息。关于什么可以或不能从一个 .c 文件转到另一个文件的规则是什么?
附录
为了解决 possible duplicate,我必须强调,我不是在问什么是不透明指针,而是在问文件 linking.
时它是如何工作的正在将评论转换为 semi-coherent 答案。
第二个main.c
的问题是因为它没有struct obj
的细节;它知道类型存在,但对它包含的内容一无所知。您可以创建和使用指向 struct obj
的指针;您不能取消引用这些指针,甚至不能复制结构,更不用说访问结构中的数据了,因为不知道它有多大。这就是为什么您拥有 obj.c
中的功能。他们提供您需要的服务——object 分配、发布、访问和修改内容(除了缺少 object 发布;也许 free(obj);
可以,但最好提供'destructor').
请注意 obj.c
应包括 obj.h
以确保 obj.c
和 main.c
之间的一致性 — 即使您使用不透明指针。
I'm not 100% what you mean by 'ensuring consistency'; what does that entail and why is it important?
目前,您可以在 obj.c
中包含 struct obj *make_obj(int initializer) { … }
,但是因为您没有在 obj.c
中包含 obj.h
,编译器无法告诉您main.c
中的代码将在没有初始化器的情况下调用它——导致 quasi-random(不确定的)值被用于 'initialize' 结构。如果在obj.c
中包含obj.h
,编译器将报告header中的声明与源文件中的定义之间的差异,代码将无法编译。 main.c
中的代码也不会编译 — 一旦 header 被修复。 header 文件是 'glue' 将系统保持在一起,确保函数定义和使用函数的地方(引用)之间的一致性。 header 中的声明确保它们都是一致的。
Also, I thought the whole reason why pointers are type-specific was because the pointers need the size which can vary depending on the type. How can a pointer be to something of unknown size?
至于为什么你可以在不知道所有细节的情况下拥有指向类型的指针,这是 C 的一个重要特性,它提供了单独编译的模块的互通性。所有指向结构(任何类型)的指针都必须有 same size and alignment requirements。您可以通过简单地在适当的地方说 struct WhatEver;
来指定结构类型存在。这通常在文件范围内,而不是在函数内;在函数内部定义(或可能重新定义)结构类型有复杂的规则。然后您可以使用指向该类型的指针,而无需为编译器提供更多信息。
如果没有详细的 body 结构(struct WhatEver { … };
,其中大括号和它们之间的内容至关重要),您将无法访问结构中的内容,或创建类型为 struct WhatEver
— 但您可以创建指针 (struct WhatEver *ptr = NULL;
)。这对 'type safety' 很重要。尽可能避免将 void *
作为通用指针类型,而且通常可以避免使用它——并非总是如此,但通常如此。
Oh okay, so the
obj.h
inobj.c
is a means of ensuring the prototype being used matches the definition, by causing an error message if they don't.
是的。
I'm still not entirely following in terms of all pointers having the same size and alignment. Wouldn't the size and alignment of a struct be unique to that particular struct?
结构各不相同,但指向它们的指针大小相同。
And the pointers can be the same size because struct pointers can't be dereferenced, so they don't need specific sizes?
如果编译器知道结构的细节({ … }
部分存在结构类型的定义),那么可以取消引用指针(并且可以定义结构类型的变量,当然还有指向它的指针)。如果编译器不知道细节,您只能定义(和使用)指向该类型的指针。
Also, out of curiosity, why would one avoid
void *
as a universal pointer?
你避免使用 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
(a FILE *
) 或 argv[1]
(a char *
if你在 main()
) 中,等等,或者你分配给了错误类型的指针变量。