2

因此,我对各种事物的链接有些困惑。对于这个问题,我将专注于不透明的指针。

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

主程序

#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;
}

对象.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 会让他们的代码替换预处理器指令,但为了可读性我没有费心去替换它们)

主程序

#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;
}

对象.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 中创建一个 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;
}

对象.c

#include <stdlib.h>

struct obj{
    int id;
};

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

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

附录

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

4

1 回答 1

4

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

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

请注意,obj.c应该包括obj.h以确保和之间的一致性obj.c——main.c即使您使用不透明的指针。

我不是 100% 你所说的“确保一致性”。这意味着什么,为什么它很重要?

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

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

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

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

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

是的。

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

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

并且指针可以是相同的大小,因为结构指针不能被取消引用,所以它们不需要特定的大小?

如果编译器知道结构的详细信息(结构类型的定义{ … }存在部分),则可以取消引用指针(当然可以定义结构类型的变量以及指向它的指针) . 如果编译器不知道细节,你只能定义(和使用)指向类型的指针。

另外,出于好奇,为什么要避免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(a FILE *) 或argv[1](a char *if you're in main()) 等,或者你是否分配了错误类型的指针变量。

于 2020-04-12T07:07:07.603 回答