当您动态分配内存时,“指针类型”有啥意义?

Posted

技术标签:

【中文标题】当您动态分配内存时,“指针类型”有啥意义?【英文标题】:What is the point of "pointer types" when you dynamically allocate memory?当您动态分配内存时,“指针类型”有什么意义? 【发布时间】:2018-11-26 12:08:35 【问题描述】:

为什么我们有指针类型?例如

int *ptr;

我知道它的类型安全,例如取消引用'ptr',编译器需要知道它取消引用ptr以键入int,而不是char或long等,但正如其他人在这里概述的Why to specify a pointer type?,它也是因为“我们应该知道要读取多少字节。取消引用 char 指针将意味着从内存中取出一个字节,而对于 int 它可能是 4 个字节。”这就说得通了。

但是如果我有这样的东西怎么办:

typedef struct _IP_ADAPTER_INFO 
    struct _IP_ADAPTER_INFO* Next;
    DWORD ComboIndex;
    char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];
    char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];
    UINT AddressLength;
    BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];
    DWORD Index;
    UINT Type;
    UINT DhcpEnabled;
    PIP_ADDR_STRING CurrentIpAddress;
    IP_ADDR_STRING IpAddressList;
    IP_ADDR_STRING GatewayList;
    IP_ADDR_STRING DhcpServer;
    BOOL HaveWins;
    IP_ADDR_STRING PrimaryWinsServer;
    IP_ADDR_STRING SecondaryWinsServer;
    time_t LeaseObtained;
    time_t LeaseExpires;
 IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;

PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

在这里声明类型 PIP_ADAPTER_INFO 有什么意义?毕竟,与前面的示例不同,我们已经为指向的指针分配了足够的内存(使用 malloc),那么在这里定义类型不是多余的吗?我们将从内存中读取已分配的尽可能多的数据。

另外,附注:以下 4 种声明之间有什么区别还是有最佳实践?

PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

PIP_ADAPTER_INFO pAdapterInfo = (PIP_ADAPTER_INFO)malloc(sizeof(IP_ADAPTER_INFO));

IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

IP_ADAPTER_INFO *pAdapterInfo = (PIP_ADAPTER_INFO)malloc(sizeof(IP_ADAPTER_INFO));

【问题讨论】:

首先,should you really cast the result of malloc in C?至于使用typedef 为指针类型定义别名,这在很大程度上是一种 Windows API 约定,除此之外通常不推荐使用(因为它往往会隐藏实际类型,因此更难理解它是指针) . 我认为您的问题不是“'指针类型'有什么意义?”,而是“为什么有些程序员定义结构指针类型定义?”。这是一个非常不同的问题。 因为您使用的是 C++ 编译器来编译 C 代码。 (这被认为是一个坏习惯)在 C 中,您可以将 any 指针类型转换为 void* [函数指针不同] @wildplasser ... C 中的 any 指针类型隐式转换void* ... (SCNR) Typedefing 指针类型被广泛认为是一种不好的做法,只是不要这样做。并且_IP_ADAPTER_INFO 是一个保留 标识符,与任何其他以下划线开头且大写字母或另一个下划线开头的标识符一样。用户代码不应定义此类标识符。 【参考方案1】:

您在这里问了两个不同的问题 - 为什么有不同的指针类型,以及为什么将指针隐藏在 typedef 后面?

不同指针类型的主要原因来自指针运算——如果p 指向T 类型的对象,那么表达式p + 1 指向该类型的下一个对象。如果p 指向一个4 字节的int,那么p + 1 指向下一个int。如果p指向一个128字节的struct,那么p + 1指向下一个128字节的struct,以此类推。指针是具有附加类型语义的内存地址的抽象

至于把指针隐藏在 typedef 后面……

如果类型的用户仍然需要知道类型的“指针性”(即,如果您必须取消引用它,或者如果您曾经将malloc/calloc/realloc 的结果分配给它,等等)。如果你试图抽象出某些东西的“指针性”,你需要做的不仅仅是声明——你需要提供一个完整的 API 来隐藏所有的指针操作。

至于你的最后一个问题,C 中的最佳实践是不要转换malloc 的结果。 C++ 的最佳实践是根本不使用malloc

【讨论】:

【参考方案2】:

我认为这更多是类型定义风格的问题,而不是动态内存分配的问题。

老式的 C 实践是通过标签来描述结构。你说

struct foo 
    ...
;

然后

struct foo foovar;

struct foo *foopointer = malloc(sizeof(struct foo));

但是很多人不喜欢一直输入关键字struct。 (我想我不能错;C 一直倾向于简洁,有时似乎只是为了减少打字。)因此使用typedef 的表单变得非常流行(它影响或受 C++ 影响):

typedef struct 
    ...
 Foo;

然后

Foo foovar;

Foo *foopointer = malloc(sizeof(Foo));

但是,由于不太清楚的原因,将指针也放入 typedef 变得很流行,如下所示:

typedef struct 
    ...
 Foo, *Foop;


Foop foopointer = malloc(sizeof(*Foop));

但这完全取决于风格和个人喜好,服务于人们想象的清晰、方便或有用的东西。 (当然,关于清晰度和便利性的意见,就像对风格的意见一样,可以合理地改变。)我已经看到指针 typedefs 被贬低为一种误导性或微软式的做法,但我不确定我现在可以指责它们。

您还询问了演员表,我们还可以剖析 sizeof 调用作为 malloc 的参数的各种选项。

说不说都无所谓

Foop foopointer = (Foop)malloc(sizeof(*Foop));

Foop foopointer = (Foo *)malloc(sizeof(*Foop));

第一个可能更清楚,因为您不必回头检查FoopFoo * 是否相同。但它们在 C 语言中的实践都很差,而且至少在某些圈子中,它们自 1990 年代以来就已被弃用。这些强制转换在纯 C 中被认为是分散注意力和不必要的——尽管它们当然在 C++ 中是必需的,或者我想如果你使用 C++ 编译器来编译 C 代码。 (当然,如果您直接编写 C++ 代码,您通常会使用 new 而不是 malloc。)

但是你应该在sizeof() 中添加什么?哪个更好,

Foop foopointer = malloc(sizeof(*Foop));

Foop foopointer = malloc(sizeof(Foo));

同样,第一个更容易阅读,因为您不必回头检查FoopFoo * 是否相同。但同理,还有第三种形式可以更清楚:

Foop foopointer = malloc(sizeof(*foopointer));

现在您知道了,无论foopointer 指向什么类型,您都在为它分配正确的空间。不过,如果最清楚foopiinter 实际上是一个指向某种类型的指针,则这个习语效果最好,这意味着变体

Foo *foopointer = malloc(sizeof(*foopointer));

甚至

struct foo *foopointer = malloc(sizeof(*foopointer));

还可以被认为更清楚——这可能是人们认为指针 typedef 不太有用的原因之一。


底线,如果你还和我在一起:如果你觉得PIP_ADAPTER_INFO 没有用,就不要使用它——使用IP_ADAPTER_INFO(在需要时使用明确的*)反而。有人认为PIP_ADAPTER_INFO 可能有用,这就是它存在的原因,但支持使用它的论据并不太令人信服。

【讨论】:

甚至:struct foo *foopointer = malloc(sizeof *foopointer);,它规定了 foopointer 不是一种类型。 @Steve ,好吧,我想我明白了......所以你是说int * ptrIP_ADAPTER_INFO *pAdapterInfo 不相等吗?第一个是关于类型安全,正如我的第一个链接所建议的,编译器要取消引用/读取的数据长度;而后者只是因为人们开始使用 typedefs 而随着时间的推移而发生的“懒惰”? 仍然不能 100% 确定您在问什么。在 C 中,“事物”和“指向事物的指针”之间的区别非常重要。你必须知道你有一个指针;某处必须有*。当您的意思是x->m 时,您不能说x.m,反之亦然。将 * 隐藏在 typedef 后面是方便还是混淆,这取决于您的意见。另请参阅@alk 的回答。【参考方案3】:

动态分配内存时“指针类型”的意义何在?

至少对于您显示的示例没有。

因此,后续问题将是在某些情况下typedefing 指针是否有意义。

答案是:是的。

如果有人需要opaque data type,那绝对是有道理的。

一个很好的例子是pthread_t type which defines a handle to a POSIX thread。

根据实现,它被定义为

typedef struct bla pthread_t; typedef struct foo * pthread_t; typedef long pthread_t;

并以此抽象出实现的类型,因为用户不感兴趣,这可能不是您在问题中显示的struct 的意图。

【讨论】:

【参考方案4】:

为什么我们有指针类型?

适应各种类型的大小和编码可能不同的架构。 C 可以很好地移植到许多平台,甚至是新平台。


如今,指向函数的指针与指向对象的指针具有不同的大小并不罕见。 object 指针转换为void *,但函数指针可能不会。

指向char 的指针不必与指向intunionstruct 的指针大小相同。这在今天是不常见的。规格细节如下(我的重点):

指向void 的指针应具有与 指向字符类型的指针。类似地,指向兼容类型的合格或不合格版本的指针应具有相同的表示和对齐要求。全部 指向结构类型的指针应具有相同的表示和对齐要求 作为彼此。所有指向联合类型的指针都应具有相同的表示形式和 对齐要求彼此。 指向其他类型的指针不必相同 表示或对齐要求。 C11dr §6.2.5 28

【讨论】:

以上是关于当您动态分配内存时,“指针类型”有啥意义?的主要内容,如果未能解决你的问题,请参考以下文章

c 链表和动态内存分配

当您在 main 中动态分配内存时,如何处理函数中的 assert()?

C 内存的动态分配怎么用?有啥建议吗?内存分配中栈与堆到底有啥不同啊?

c和c ++中的动态内存分配和堆有啥区别

动态内存分配如何工作[重复]

C 中静态内存分配与动态内存分配的成本