对于不透明指针,C 中的链接如何工作?

the*_*ish 2 c linker struct pointers opaque-pointers

因此,我对各种事物的链接感到有些困惑。对于这个问题,我将重点讨论不透明指针。

我将用一个例子来说明我的困惑。假设我有这三个文件:

主程序

#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;
}
Run Code Online (Sandbox Code Playgroud)

对象文件

#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;
};
Run Code Online (Sandbox Code Playgroud)

对象文件

struct obj;

struct obj *make_obj(void);

void setid(struct obj *o, int i);

int getid(struct obj *o);

struct obj *myobj;
Run Code Online (Sandbox Code Playgroud)

由于预处理器指令,这些基本上会变成两个文件:

(我知道从技术上讲,stdio.h 和 stdlib.h 会让它们的代码替换预处理器指令,但为了可读性,我没有费心替换它们)

主程序

#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;
}
Run Code Online (Sandbox Code Playgroud)

对象文件

#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;
};
Run Code Online (Sandbox Code Playgroud)

现在这就是我有点困惑的地方。如果我尝试在 main.c 中创建 struct obj,即使 main.c 有声明,我也会收到不完整类型错误struct obj;

即使我将代码更改为 use extern,它仍然无法编译:

主程序

#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;
}
Run Code Online (Sandbox Code Playgroud)

对象文件

#include <stdlib.h>

struct obj{
    int id;
};
Run Code Online (Sandbox Code Playgroud)

据我所知,main.c 和 obj.c 不进行结构通信(与某些只需要在其他文件中声明的函数或变量不同)。

因此,main.c 与 struct obj 类型没有链接,但由于某种原因,在前面的示例中,它能够创建一个指向该类型的指针就好struct obj *myobj;。怎么,为什么?我觉得我错过了一些重要的信息。关于什么可以或不能从一个 .c 文件转移到另一个 .c 文件的规则是什么?

附录

为了解决可能的重复问题,我必须强调,我不是问不透明指针是什么,而是问它在文件链接方面如何发挥作用。

Jon*_*ler 5

将评论转换为半连贯的答案。

\n\n

第二个问题的main.c出现是因为它没有以下细节struct obj;它知道该类型存在,但对其包含的内容一无所知。您可以创建和使用指向struct obj;的指针 您无法取消引用这些指针,甚至无法复制结构,更不用说访问结构内的数据,因为不知道它有多大。这就是为什么你有obj.c. 他们提供你需要的服务\xe2\x80\x94对象分配、释放、访问和修改内容(除了缺少对象释放;也许还free(obj);可以,但最好提供一个\'析构函数\ ')。

\n\n

请注意,即使您使用不透明指针,也obj.c应包括obj.h以确保 \xe2\x80\x94 之间的一致性obj.cmain.c

\n\n
\n

我并不是 100% 理解你所说的“确保一致性”;这意味着什么以及为什么它很重要?

\n
\n\n

目前,您可以使用struct obj *make_obj(int initializer) { \xe2\x80\xa6 }in obj.c,但因为您不包含obj.hin obj.c,编译器无法告诉您您的代码 inmain.c将在没有初始化程序 \xe2\x80\x94 的情况下调用它,从而导致准随机(不确定) )用于“初始化”结构的值。如果包含obj.hin obj.c,则编译器将报告标头中的声明与源文件中的定义之间的差异,并且代码将无法编译。一旦标头被修复,中的代码main.c就不会编译 \xe2\x80\x94 。头文件是将系统粘合在一起的“粘合剂”,确保函数定义和使用该函数的位置(引用)之间的一致性。标头中的声明确保它们全部一致。

\n\n
\n

另外,我认为指针特定于类型的全部原因是因为指针需要的大小可能会根据类型而变化。指针怎么可能指向未知大小的东西呢?

\n
\n\n

至于为什么你可以在不知道所有细节的情况下拥有指向类型的指针,这是 C 的一个重要功能,它提供了单独编译的模块的互操作。所有指向结构(任何类型)的指针必须具有相同的大小和对齐要求。您可以通过简单地说出适当的地方来指定结构类型是否存在struct WhatEver;。这通常是在文件范围内,而不是在函数内;在函数内部定义(或可能重新定义)结构类型有复杂的规则。然后您可以使用指向该类型的指针,而无需为编译器提供更多信息。

\n\n

如果没有结构的详细主体(struct WhatEver { \xe2\x80\xa6 };其中大括号和它们之间的内容至关重要),您将无法访问结构中的内容,也无法创建struct WhatEver\xe2\x80\x94 类型的变量,但可以创建指针(struct WhatEver *ptr = NULL;)。这对于“类型安全”很重要。尽可能避免void *作为通用指针类型,并且通常可以避免 \xe2\x80\x94 ,但并不总是如此,但通常如此。

\n\n
\n

哦,好吧,所以obj.hinobj.c是确保所使用的原型与定义匹配的一种方法,如果不匹配,则会引发错误消息。

\n
\n\n

是的。

\n\n
\n

我仍然没有完全遵循具有相同大小和对齐方式的所有指针。结构的大小和对齐方式对于该特定结构来说不是唯一的吗?

\n
\n\n

这些结构都不同,但指向它们的指针大小都相同。

\n\n
\n

并且指针可以具有相同的大小,因为结构指针无法取消引用,因此它们不需要特定的大小?

\n
\n\n

如果编译器知道结构的详细信息(存在包含该部分的结构类型的定义{\xc2\xa0\xe2\x80\xa6\xc2\xa0}),则可以取消引用指针(并且可以定义结构类型的变量以及指向它的指针)课程)。如果编译器不知道详细信息,您只能定义(和使用)指向该类型的指针。

\n\n
\n

另外,出于好奇,为什么要避免将其void *作为通用指针呢?

\n
\n\n

你会避免,void *因为你失去了所有类型的安全性。如果您有声明:

\n\n
extern void *delicate_and_dangerous(void *vptr);\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么如果你编写调用,编译器就不会抱怨:

\n\n
bool *bptr = delicate_and_dangerous(stdin);\nstruct AnyThing *aptr = delicate_and_dangerous(argv[1]);\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您有声明:

\n\n
extern struct SpecialCase *delicate_and_dangerous(struct UnusualDevice *udptr);\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么编译器会告诉您何时使用错误的指针类型调用它,例如stdin(a FILE *) 或argv[1](achar *如果您在main()) 等,或者您分配给错误类型的指针变量。

\n